From 18df0f2c78c49b78f215e80df22dae1f2c68aa0b Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 31 May 2014 08:44:54 +0200 Subject: [PATCH 1/8] Renamed README, adapted MANIFEST --- MANIFEST.in | 5 +++-- README => README.rst | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename README => README.rst (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 4af6088..a13f7f3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ -include README -recursive-include todo/media * +include LICENSE +include README.rst +recursive-include todo/static * recursive-include todo/templates * diff --git a/README b/README.rst similarity index 100% rename from README rename to README.rst From 97d27f758984ecfaaa9cbfa07744cc9c8302fad0 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 31 May 2014 09:19:27 +0200 Subject: [PATCH 2/8] Reformatted readme and license Move some setup values to package Added .gitignore --- .gitignore | 10 ++++++++++ LICENSE | 27 +++++++++++++++++++++------ README.rst | 25 ++++++++++++++++--------- setup.py | 14 +++++++++----- todo/__init__.py | 8 ++++++++ 5 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 .gitignore mode change 100644 => 100755 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f1d4d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# tools, IDEs, build folders +/.coverage/ +/.idea/ +/build/ +/dist/ +/docs/build/ +/*.egg-info/ + +# Django and Python +*.py[cod] diff --git a/LICENSE b/LICENSE index 7e49e96..68eef42 100644 --- a/LICENSE +++ b/LICENSE @@ -1,12 +1,27 @@ -Copyright (c) 2010, Scot Hacker, Birdhouse Arts +Copyright (c) 2010, Scot Hacker, Birdhouse Arts and individual contributors. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -Neither the name of Birdhouse Arts nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + 3. Neither the name of Birdhouse Arts nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst index b0fc565..55ca065 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,23 @@ -django-todo is a pluggable multi-user, multi-group task management and -assignment application for Django. It can serve as anything from a -personal to-do system to a complete, working ticketing system for organizations. +=========== +django todo +=========== + +django-todo is a pluggable multi-user, multi-group task management and +assignment application for Django. It can serve as anything from a personal +to-do system to a complete, working ticketing system for organizations. + +Documentation +============= For documentation, see the django-todo wiki pages: -Overview and screenshots -http://github.com/shacker/django-todo/wiki/Overview-and-screenshots +- `Overview and screenshots + `_ -Requirements and installation -http://github.com/shacker/django-todo/wiki/Requirements-and-Installation +- `Requirements and installation + `_ This is version 1.3 -Version history -http://github.com/shacker/django-todo/wiki/Version-history +- `Version history + `_ diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index d1de46a..8f80d41 --- a/setup.py +++ b/setup.py @@ -1,12 +1,16 @@ +# !/usr/bin/env python +# from setuptools import setup, find_packages - +import todo + setup( name='django-todo', - version='1.3', + version=todo.__version__, description='A multi-user, multi-group task management and assignment system for Django.', - author='Scot Hacker', - author_email='shacker@birdhouse.org', - url='http://github.com/shacker/django-todo', + author=todo.__author__, + author_email=todo.__email__, + url=todo.__url__, + license=todo.__license__, packages=find_packages(), classifiers=[ 'Development Status :: 4 - Beta', diff --git a/todo/__init__.py b/todo/__init__.py index e69de29..0132098 100644 --- a/todo/__init__.py +++ b/todo/__init__.py @@ -0,0 +1,8 @@ +"""django todo""" +__version__ = '1.4.dev' + +__author__ = 'Scot Hacker' +__email__ = 'shacker@birdhouse.org' + +__url__ = 'https://github.com/shacker/django-todo' +__license__ = 'BSD License' From 01942901401c35652a683f7d9e36810fa0f60173 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 31 May 2014 09:36:54 +0200 Subject: [PATCH 3/8] Bugfix hashbang --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8f80d41..dcdd7bd 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ -# !/usr/bin/env python -# +#!/usr/bin/env python + from setuptools import setup, find_packages import todo From e894b14a96ea020e4ba90b5fabfe02a0f02be5f2 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 31 May 2014 18:09:27 +0200 Subject: [PATCH 4/8] Reformatting only (PEP8) --- todo/admin.py | 3 +- todo/forms.py | 55 ++++----- todo/models.py | 61 +++++----- todo/settings.py | 1 - todo/urls.py | 35 +++--- todo/views.py | 310 +++++++++++++++++++++-------------------------- 6 files changed, 210 insertions(+), 255 deletions(-) diff --git a/todo/admin.py b/todo/admin.py index bbe6aee..8f034bc 100644 --- a/todo/admin.py +++ b/todo/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from todo.models import Item, User, List, Comment + class ItemAdmin(admin.ModelAdmin): list_display = ('title', 'list', 'priority', 'due_date') list_filter = ('list',) @@ -10,4 +11,4 @@ class ItemAdmin(admin.ModelAdmin): admin.site.register(List) admin.site.register(Comment) -admin.site.register(Item,ItemAdmin) \ No newline at end of file +admin.site.register(Item, ItemAdmin) \ No newline at end of file diff --git a/todo/forms.py b/todo/forms.py index 407a5d1..6da8725 100644 --- a/todo/forms.py +++ b/todo/forms.py @@ -1,11 +1,10 @@ -from django.db import models from django import forms from django.forms import ModelForm -from django.contrib.auth.models import User,Group +from django.contrib.auth.models import User, Group from todo.models import Item, List -import datetime -class AddListForm(ModelForm): + +class AddListForm(ModelForm): # The picklist showing allowable groups to which a new list can be added # determines which groups the user belongs to. This queries the form object # to derive that list. @@ -15,11 +14,9 @@ class AddListForm(ModelForm): class Meta: model = List - - - + + class AddItemForm(ModelForm): - # The picklist showing the users to which a new task can be assigned # must find other members of the groups the current list belongs to. def __init__(self, task_list, *args, **kwargs): @@ -27,57 +24,51 @@ class AddItemForm(ModelForm): # print dir(self.fields['list']) # print self.fields['list'].initial self.fields['assigned_to'].queryset = User.objects.filter(groups__in=[task_list.group]) - + due_date = forms.DateField( - required=False, - widget=forms.DateTimeInput(attrs={'class':'due_date_picker'}) - ) - + required=False, + widget=forms.DateTimeInput(attrs={'class': 'due_date_picker'}) + ) + title = forms.CharField( - widget=forms.widgets.TextInput(attrs={'size':35}) - ) + widget=forms.widgets.TextInput(attrs={'size': 35}) + ) class Meta: model = Item - class EditItemForm(ModelForm): - # The picklist showing the users to which a new task can be assigned # must find other members of the groups the current list belongs to. - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs): super(EditItemForm, self).__init__(*args, **kwargs) self.fields['assigned_to'].queryset = User.objects.filter(groups__in=[self.instance.list.group]) class Meta: model = Item - exclude = ('created_date','created_by',) - + exclude = ('created_date', 'created_by',) + - class AddExternalItemForm(ModelForm): """Form to allow users who are not part of the GTD system to file a ticket.""" title = forms.CharField( - widget=forms.widgets.TextInput(attrs={'size':35}) - ) - note = forms.CharField ( + widget=forms.widgets.TextInput(attrs={'size': 35}) + ) + note = forms.CharField( widget=forms.widgets.Textarea(), help_text='Foo', - ) - + ) + class Meta: model = Item - exclude = ('list','created_date','due_date','created_by','assigned_to',) - + exclude = ('list', 'created_date', 'due_date', 'created_by', 'assigned_to',) class SearchForm(ModelForm): """Search.""" q = forms.CharField( - widget=forms.widgets.TextInput(attrs={'size':35}) - ) - - + widget=forms.widgets.TextInput(attrs={'size': 35}) + ) diff --git a/todo/models.py b/todo/models.py index 241c4b2..a79d59f 100644 --- a/todo/models.py +++ b/todo/models.py @@ -1,92 +1,85 @@ from django.db import models -from django.forms.models import ModelForm -from django import forms -from django.contrib import admin -from django.contrib.auth.models import User,Group -import string, datetime +from django.contrib.auth.models import User, Group from django.template.defaultfilters import slugify +import datetime + class List(models.Model): name = models.CharField(max_length=60) - slug = models.SlugField(max_length=60,editable=False) + slug = models.SlugField(max_length=60, editable=False) # slug = models.SlugField(max_length=60) group = models.ForeignKey(Group) - + def save(self, *args, **kwargs): if not self.id: self.slug = slugify(self.name) super(List, self).save(*args, **kwargs) - - def __unicode__(self): return self.name - + # Custom manager lets us do things like Item.completed_tasks.all() objects = models.Manager() - + def incomplete_tasks(self): # Count all incomplete tasks on the current list instance - return Item.objects.filter(list=self,completed=0) - + return Item.objects.filter(list=self, completed=0) + class Meta: - ordering = ["name"] + ordering = ["name"] verbose_name_plural = "Lists" - + # Prevents (at the database level) creation of two lists with the same name in the same group unique_together = ("group", "slug") - - - class Item(models.Model): title = models.CharField(max_length=140) list = models.ForeignKey(List) created_date = models.DateField(auto_now=True, auto_now_add=True) - due_date = models.DateField(blank=True,null=True,) + due_date = models.DateField(blank=True, null=True, ) completed = models.BooleanField() - completed_date = models.DateField(blank=True,null=True) + completed_date = models.DateField(blank=True, null=True) created_by = models.ForeignKey(User, related_name='created_by') assigned_to = models.ForeignKey(User, related_name='todo_assigned_to') - note = models.TextField(blank=True,null=True) + note = models.TextField(blank=True, null=True) priority = models.PositiveIntegerField(max_length=3) - + # Model method: Has due date for an instance of this object passed? def overdue_status(self): "Returns whether the item's due date has passed or not." - if datetime.date.today() > self.due_date : + if datetime.date.today() > self.due_date: return 1 def __unicode__(self): return self.title - + # Auto-set the item creation / completed date def save(self): # If Item is being marked complete, set the completed_date - if self.completed : + if self.completed: self.completed_date = datetime.datetime.now() super(Item, self).save() class Meta: - ordering = ["priority"] - + ordering = ["priority"] -class Comment(models.Model): + +class Comment(models.Model): """ - Not using Django's built-in comments becase we want to be able to save + Not using Django's built-in comments because we want to be able to save a comment and change task details at the same time. Rolling our own since it's easy. """ author = models.ForeignKey(User) task = models.ForeignKey(Item) date = models.DateTimeField(default=datetime.datetime.now) body = models.TextField(blank=True) - - def __unicode__(self): + + def __unicode__(self): return '%s - %s' % ( - self.author, - self.date, - ) \ No newline at end of file + self.author, + self.date, + ) diff --git a/todo/settings.py b/todo/settings.py index f04cf74..b080d5b 100644 --- a/todo/settings.py +++ b/todo/settings.py @@ -1,4 +1,3 @@ -from django.core.exceptions import ImproperlyConfigured from django.conf import settings STAFF_ONLY = getattr(settings, 'TODO_STAFF_ONLY', False) diff --git a/todo/urls.py b/todo/urls.py index 2c9aaa6..c793c78 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -1,21 +1,22 @@ -from django.conf.urls import * -from django.contrib.auth import views as auth_views +from django.conf.urls import patterns, url -urlpatterns = patterns('', - url(r'^mine/$', 'todo.views.view_list',{'list_slug':'mine'},name="todo-mine"), - url(r'^(?P\d{1,4})/(?P[\w-]+)/delete$', 'todo.views.del_list',name="todo-del_list"), +urlpatterns = patterns( + '', + url(r'^mine/$', 'todo.views.view_list', {'list_slug': 'mine'}, name="todo-mine"), + url(r'^(?P\d{1,4})/(?P[\w-]+)/delete$', 'todo.views.del_list', name="todo-del_list"), url(r'^task/(?P\d{1,6})$', 'todo.views.view_task', name='todo-task_detail'), url(r'^(?P\d{1,4})/(?P[\w-]+)$', 'todo.views.view_list', name='todo-incomplete_tasks'), - url(r'^(?P\d{1,4})/(?P[\w-]+)/completed$', 'todo.views.view_list', {'view_completed':1},name='todo-completed_tasks'), - url(r'^add_list/$', 'todo.views.add_list',name="todo-add_list"), - url(r'^search/$', 'todo.views.search',name="todo-search"), - url(r'^$', 'todo.views.list_lists',name="todo-lists"), - - # View reorder_tasks is only called by JQuery for drag/drop task ordering - url(r'^reorder_tasks/$', 'todo.views.reorder_tasks',name="todo-reorder_tasks"), - - url(r'^ticket/add/$', 'todo.views.external_add',name="todo-external-add"), - url(r'^recent/added/$', 'todo.views.view_list',{'list_slug':'recent-add'},name="todo-recently_added"), - url(r'^recent/completed/$', 'todo.views.view_list',{'list_slug':'recent-complete'},name="todo-recently_completed"), -) + url(r'^(?P\d{1,4})/(?P[\w-]+)/completed$', 'todo.views.view_list', {'view_completed': 1}, + name='todo-completed_tasks'), + url(r'^add_list/$', 'todo.views.add_list', name="todo-add_list"), + url(r'^search/$', 'todo.views.search', name="todo-search"), + url(r'^$', 'todo.views.list_lists', name="todo-lists"), + # View reorder_tasks is only called by JQuery for drag/drop task ordering + url(r'^reorder_tasks/$', 'todo.views.reorder_tasks', name="todo-reorder_tasks"), + + url(r'^ticket/add/$', 'todo.views.external_add', name="todo-external-add"), + url(r'^recent/added/$', 'todo.views.view_list', {'list_slug': 'recent-add'}, name="todo-recently_added"), + url(r'^recent/completed/$', 'todo.views.view_list', {'list_slug': 'recent-complete'}, + name="todo-recently_completed"), +) diff --git a/todo/views.py b/todo/views.py index 89c6b70..4dc48ad 100644 --- a/todo/views.py +++ b/todo/views.py @@ -1,11 +1,9 @@ -from django import forms from django.shortcuts import render_to_response from todo.models import Item, List, Comment -from todo.forms import AddListForm, AddItemForm, EditItemForm, AddExternalItemForm, SearchForm +from todo.forms import AddListForm, AddItemForm, EditItemForm, AddExternalItemForm from todo import settings from django.contrib.auth.models import User from django.shortcuts import get_object_or_404 -from django.contrib import auth from django.template import RequestContext from django.http import HttpResponseRedirect, HttpResponse from django.core.urlresolvers import reverse @@ -21,11 +19,10 @@ from django.views.decorators.csrf import csrf_exempt import datetime # Need for links in email templates -current_site = Site.objects.get_current() +current_site = Site.objects.get_current() def check_user_allowed(user): - """ test for user_passes_test decorator """ @@ -35,46 +32,40 @@ def check_user_allowed(user): return user.is_authenticated() - @user_passes_test(check_user_allowed) def list_lists(request): - """ Homepage view - list of lists a user can view, and ability to add a list. """ - # Make sure user belongs to at least one group. group_count = request.user.groups.all().count() if group_count == 0: - messages.error(request, "You do not yet belong to any groups. Ask your administrator to add you to one.") - + messages.error(request, "You do not yet belong to any groups. Ask your administrator to add you to one.") - # Only show lists to the user that belong to groups they are members of. + # Only show lists to the user that belong to groups they are members of. # Superusers see all lists if request.user.is_superuser: - list_list = List.objects.all().order_by('group','name') + list_list = List.objects.all().order_by('group', 'name') else: - list_list = List.objects.filter(group__in=request.user.groups.all).order_by('group','name') - + list_list = List.objects.filter(group__in=request.user.groups.all).order_by('group', 'name') + # Count everything list_count = list_list.count() - + # Note admin users see all lists, so count shouldn't filter by just lists the admin belongs to - if request.user.is_superuser : - item_count = Item.objects.filter(completed=0).count() + if request.user.is_superuser: + item_count = Item.objects.filter(completed=0).count() else: item_count = Item.objects.filter(completed=0).filter(list__group__in=request.user.groups.all()).count() - return render_to_response('todo/list_lists.html', locals(), context_instance=RequestContext(request)) - + return render_to_response('todo/list_lists.html', locals(), context_instance=RequestContext(request)) + @user_passes_test(check_user_allowed) -def del_list(request,list_id,list_slug): - +def del_list(request, list_id, list_slug): """ Delete an entire list. Danger Will Robinson! Only staff members should be allowed to access this view. """ - if request.user.is_staff: can_del = 1 @@ -87,46 +78,42 @@ def del_list(request,list_id,list_slug): del_items = Item.objects.filter(list=list.id) for del_item in del_items: del_item.delete() - + # Kill the list del_list = List.objects.get(id=list.id) del_list.delete() - + # A var to send to the template so we can show the right thing list_killed = 1 - else: - item_count_done = Item.objects.filter(list=list.id,completed=1).count() - item_count_undone = Item.objects.filter(list=list.id,completed=0).count() - item_count_total = Item.objects.filter(list=list.id).count() - + item_count_done = Item.objects.filter(list=list.id, completed=1).count() + item_count_undone = Item.objects.filter(list=list.id, completed=0).count() + item_count_total = Item.objects.filter(list=list.id).count() + return render_to_response('todo/del_list.html', locals(), context_instance=RequestContext(request)) @user_passes_test(check_user_allowed) -def view_list(request,list_id=0,list_slug=None,view_completed=0): - +def view_list(request, list_id=0, list_slug=None, view_completed=0): """ Display and manage items in a task list """ - # Make sure the accessing user has permission to view this list. # Always authorize the "mine" view. Admins can view/edit all lists. - if list_slug == "mine" or list_slug == "recent-add" or list_slug == "recent-complete" : - auth_ok =1 - else: + if list_slug == "mine" or list_slug == "recent-add" or list_slug == "recent-complete": + auth_ok = 1 + else: list = get_object_or_404(List, slug=list_slug) - listid = list.id - - # Check whether current user is a member of the group this list belongs to. - if list.group in request.user.groups.all() or request.user.is_staff or list_slug == "mine" : - auth_ok = 1 # User is authorized for this view - else: # User does not belong to the group this list is attached to - messages.error(request, "You do not have permission to view/edit this list.") + listid = list.id - - # First check for items in the mark_done POST array. If present, change + # Check whether current user is a member of the group this list belongs to. + if list.group in request.user.groups.all() or request.user.is_staff or list_slug == "mine": + auth_ok = 1 # User is authorized for this view + else: # User does not belong to the group this list is attached to + messages.error(request, "You do not have permission to view/edit this list.") + + # First check for items in the mark_done POST array. If present, change # their status to complete. if request.POST.getlist('mark_done'): done_items = request.POST.getlist('mark_done') @@ -136,10 +123,9 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0): p.completed = 1 p.completed_date = datetime.datetime.now() p.save() - messages.success(request, "Item \"%s\" marked complete." % p.title) + messages.success(request, "Item \"%s\" marked complete." % p.title) - - # Undo: Set completed items back to incomplete + # Undo: Set completed items back to incomplete if request.POST.getlist('undo_completed_task'): undone_items = request.POST.getlist('undo_completed_task') for thisitem in undone_items: @@ -148,84 +134,81 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0): p.save() messages.success(request, "Previously completed task \"%s\" marked incomplete." % p.title) - # And delete any requested items if request.POST.getlist('del_task'): deleted_items = request.POST.getlist('del_task') for thisitem in deleted_items: p = Item.objects.get(id=thisitem) p.delete() - messages.success(request, "Item \"%s\" deleted." % p.title) + messages.success(request, "Item \"%s\" deleted." % p.title) - # And delete any *already completed* items + # And delete any *already completed* items if request.POST.getlist('del_completed_task'): deleted_items = request.POST.getlist('del_completed_task') for thisitem in deleted_items: p = Item.objects.get(id=thisitem) p.delete() - messages.success(request, "Deleted previously completed item \"%s\"." % p.title) - + messages.success(request, "Deleted previously completed item \"%s\"." % p.title) thedate = datetime.datetime.now() created_date = "%s-%s-%s" % (thedate.year, thedate.month, thedate.day) - # Get list of items with this list ID, or filter on items assigned to me, or recently added/completed if list_slug == "mine": task_list = Item.objects.filter(assigned_to=request.user, completed=0) completed_list = Item.objects.filter(assigned_to=request.user, completed=1) - + elif list_slug == "recent-add": # We'll assume this only includes uncompleted items to avoid confusion. # Only show items in lists that are in groups that the current user is also in. - task_list = Item.objects.filter(list__group__in=(request.user.groups.all()),completed=0).order_by('-created_date')[:50] + task_list = Item.objects.filter(list__group__in=(request.user.groups.all()), completed=0).order_by( + '-created_date')[:50] # completed_list = Item.objects.filter(assigned_to=request.user, completed=1) - + elif list_slug == "recent-complete": # Only show items in lists that are in groups that the current user is also in. - task_list = Item.objects.filter(list__group__in=request.user.groups.all(),completed=1).order_by('-completed_date')[:50] + task_list = Item.objects.filter(list__group__in=request.user.groups.all(), completed=1).order_by( + '-completed_date')[:50] # completed_list = Item.objects.filter(assigned_to=request.user, completed=1) - else: task_list = Item.objects.filter(list=list.id, completed=0) completed_list = Item.objects.filter(list=list.id, completed=1) - - if request.POST.getlist('add_task') : - form = AddItemForm(list, request.POST,initial={ - 'assigned_to':request.user.id, - 'priority':999, + if request.POST.getlist('add_task'): + form = AddItemForm(list, request.POST, initial={ + 'assigned_to': request.user.id, + 'priority': 999, }) - + if form.is_valid(): # Save task first so we have a db object to play with new_task = form.save() # Send email alert only if the Notify checkbox is checked AND the assignee is not the same as the submittor # Email subect and body format are handled by templates - if "notify" in request.POST : - if new_task.assigned_to != request.user : - + if "notify" in request.POST: + if new_task.assigned_to != request.user: + # Send email - email_subject = render_to_string("todo/email/assigned_subject.txt", { 'task': new_task }) - email_body = render_to_string("todo/email/assigned_body.txt", { 'task': new_task, 'site': current_site, }) + email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': new_task}) + email_body = render_to_string("todo/email/assigned_body.txt", + {'task': new_task, 'site': current_site, }) try: - send_mail(email_subject, email_body, new_task.created_by.email, [new_task.assigned_to.email], fail_silently=False) + send_mail(email_subject, email_body, new_task.created_by.email, [new_task.assigned_to.email], + fail_silently=False) except: messages.error(request, "Task saved but mail not sent. Contact your administrator.") - - messages.success(request, "New task \"%s\" has been added." % new_task.title) - + messages.success(request, "New task \"%s\" has been added." % new_task.title) + return HttpResponseRedirect(request.path) - else: - if list_slug != "mine" and list_slug != "recent-add" and list_slug != "recent-complete" : # We don't allow adding a task on the "mine" view + if list_slug != "mine" and list_slug != "recent-add" and list_slug != "recent-complete": # We don't allow adding a task on the "mine" view form = AddItemForm(list, initial={ - 'assigned_to':request.user.id, - 'priority':999, - } ) + 'assigned_to': request.user.id, + 'priority': 999, + }) if request.user.is_staff: can_del = 1 @@ -234,74 +217,69 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0): @user_passes_test(check_user_allowed) -def view_task(request,task_id): - +def view_task(request, task_id): """ View task details. Allow task details to be edited. """ - task = get_object_or_404(Item, pk=task_id) comment_list = Comment.objects.filter(task=task_id) - + # Before doing anything, make sure the accessing user has permission to view this item. # Determine the group this task belongs to, and check whether current user is a member of that group. # Admins can edit all tasks. if task.list.group in request.user.groups.all() or request.user.is_staff: - + auth_ok = 1 if request.POST: - form = EditItemForm(request.POST,instance=task) + form = EditItemForm(request.POST, instance=task) - if form.is_valid(): - form.save() - - # Also save submitted comment, if non-empty - if request.POST['comment-body']: - c = Comment( - author=request.user, - task=task, - body=request.POST['comment-body'], - ) - c.save() - - # And email comment to all people who have participated in this thread. - email_subject = render_to_string("todo/email/assigned_subject.txt", { 'task': task }) - email_body = render_to_string("todo/email/newcomment_body.txt", { 'task': task, 'body':request.POST['comment-body'], 'site': current_site, 'user':request.user }) + if form.is_valid(): + form.save() - # Get list of all thread participants - task creator plus everyone who has commented on it. - recip_list = [] - recip_list.append(task.created_by.email) - commenters = Comment.objects.filter(task=task) - for c in commenters: - recip_list.append(c.author.email) - # Eliminate duplicate emails with the Python set() function - recip_list = set(recip_list) - - # Send message - try: + # Also save submitted comment, if non-empty + if request.POST['comment-body']: + c = Comment( + author=request.user, + task=task, + body=request.POST['comment-body'], + ) + c.save() + + # And email comment to all people who have participated in this thread. + email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task}) + email_body = render_to_string("todo/email/newcomment_body.txt", + {'task': task, 'body': request.POST['comment-body'], + 'site': current_site, 'user': request.user}) + + # Get list of all thread participants - task creator plus everyone who has commented on it. + recip_list = [] + recip_list.append(task.created_by.email) + commenters = Comment.objects.filter(task=task) + for c in commenters: + recip_list.append(c.author.email) + # Eliminate duplicate emails with the Python set() function + recip_list = set(recip_list) + + # Send message + try: send_mail(email_subject, email_body, task.created_by.email, recip_list, fail_silently=False) - messages.success(request, "Comment sent to thread participants.") - - except: + messages.success(request, "Comment sent to thread participants.") + + except: messages.error(request, "Comment saved but mail not sent. Contact your administrator.") - - - messages.success(request, "The task has been edited.") - - return HttpResponseRedirect(reverse('todo-incomplete_tasks', args=[task.list.id, task.list.slug])) - + + messages.success(request, "The task has been edited.") + + return HttpResponseRedirect(reverse('todo-incomplete_tasks', args=[task.list.id, task.list.slug])) else: form = EditItemForm(instance=task) if task.due_date: thedate = task.due_date else: thedate = datetime.datetime.now() - - else: messages.info(request, "You do not have permission to view/edit this task.") - return render_to_response('todo/view_task.html', locals(), context_instance=RequestContext(request)) @@ -312,11 +290,10 @@ def reorder_tasks(request): """ Handle task re-ordering (priorities) from JQuery drag/drop in view_list.html """ - newtasklist = request.POST.getlist('tasktable[]') # First item in received list is always empty - remove it del newtasklist[0] - + # Items arrive in order, so all we need to do is increment up from one, saving # "i" as the new priority for the current object. i = 1 @@ -325,12 +302,12 @@ def reorder_tasks(request): newitem.priority = i newitem.save() i = i + 1 - + # All views must return an httpresponse of some kind ... without this we get # error 500s in the log even though things look peachy in the browser. return HttpResponse(status=201) - - + + @user_passes_test(check_user_allowed) def external_add(request): """ @@ -340,68 +317,62 @@ def external_add(request): way we don't have to put all students into a group that gives them access to the whole ticket system. """ if request.POST: - form = AddExternalItemForm(request.POST) + form = AddExternalItemForm(request.POST) - if form.is_valid(): - # Don't commit the save until we've added in the fields we need to set - item = form.save(commit=False) - item.list_id = 20 # Hate hard-coding in IDs like this. - item.created_by = request.user - item.assigned_to = User.objects.get(username='roy_baril') - item.save() - - # Send email - email_subject = render_to_string("todo/email/assigned_subject.txt", { 'task': item.title }) - email_body = render_to_string("todo/email/assigned_body.txt", { 'task': item, 'site': current_site, }) - try: - send_mail(email_subject, email_body, item.created_by.email, [item.assigned_to.email], fail_silently=False) - except: - messages.error(request, "Task saved but mail not sent. Contact your administrator." ) + if form.is_valid(): + # Don't commit the save until we've added in the fields we need to set + item = form.save(commit=False) + item.list_id = 20 # Hate hard-coding in IDs like this. + item.created_by = request.user + item.assigned_to = User.objects.get(username='roy_baril') + item.save() - messages.success(request, "Your trouble ticket has been submitted. We'll get back to you soon." ) - - return HttpResponseRedirect(reverse('intranet_home')) - - + # Send email + email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': item.title}) + email_body = render_to_string("todo/email/assigned_body.txt", {'task': item, 'site': current_site, }) + try: + send_mail(email_subject, email_body, item.created_by.email, [item.assigned_to.email], + fail_silently=False) + except: + messages.error(request, "Task saved but mail not sent. Contact your administrator.") + + messages.success(request, "Your trouble ticket has been submitted. We'll get back to you soon.") + + return HttpResponseRedirect(reverse('intranet_home')) else: form = AddExternalItemForm() return render_to_response('todo/add_external_task.html', locals(), context_instance=RequestContext(request)) - @user_passes_test(check_user_allowed) def add_list(request): """ Allow users to add a new todo list to the group they're in. """ - - if request.POST: - form = AddListForm(request.user,request.POST) + if request.POST: + form = AddListForm(request.user, request.POST) if form.is_valid(): try: form.save() - messages.success(request, "A new list has been added." ) + messages.success(request, "A new list has been added.") return HttpResponseRedirect(request.path) except IntegrityError: - messages.error(request, "There was a problem saving the new list. Most likely a list with the same name in the same group already exists." ) - - + messages.error(request, + "There was a problem saving the new list. " + "Most likely a list with the same name in the same group already exists.") else: form = AddListForm(request.user) - + return render_to_response('todo/add_list.html', locals(), context_instance=RequestContext(request)) - - @user_passes_test(check_user_allowed) def search(request): """ Search for tasks """ - - if request.GET: + if request.GET: query_string = '' found_items = None @@ -410,22 +381,21 @@ def search(request): found_items = Item.objects.filter( Q(title__icontains=query_string) | - Q(note__icontains=query_string) + Q(note__icontains=query_string) ) else: # What if they selected the "completed" toggle but didn't type in a query string? # In that case we still need found_items in a queryset so it can be "excluded" below. - found_items = Item.objects.all() - - if request.GET['inc_complete'] == "0" : + found_items = Item.objects.all() + + if request.GET['inc_complete'] == "0": found_items = found_items.exclude(completed=True) - - else : + + else: query_string = None found_items = None return render_to_response('todo/search_results.html', - { 'query_string': query_string, 'found_items': found_items }, - context_instance=RequestContext(request)) - + {'query_string': query_string, 'found_items': found_items}, + context_instance=RequestContext(request)) From d1b18429655f4d9e257ac4842a793de4c32ea8ea Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 31 May 2014 19:12:02 +0200 Subject: [PATCH 5/8] Changed related_name in foreign key created_by (conflict with django-media-tree) --- todo/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/todo/models.py b/todo/models.py index a79d59f..6efca26 100644 --- a/todo/models.py +++ b/todo/models.py @@ -42,7 +42,7 @@ class Item(models.Model): due_date = models.DateField(blank=True, null=True, ) completed = models.BooleanField() completed_date = models.DateField(blank=True, null=True) - created_by = models.ForeignKey(User, related_name='created_by') + created_by = models.ForeignKey(User, related_name='todo_created_by') assigned_to = models.ForeignKey(User, related_name='todo_assigned_to') note = models.TextField(blank=True, null=True) priority = models.PositiveIntegerField(max_length=3) From 870ae5cab4b633e482f37574728221f6a4fa7270 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 31 May 2014 20:01:42 +0200 Subject: [PATCH 6/8] Removed version number (now in package and Wiki) --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 55ca065..4da4741 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,5 @@ For documentation, see the django-todo wiki pages: - `Requirements and installation `_ -This is version 1.3 - - `Version history `_ From 3730494b42bc1ae0b68dc3da5dfdc2f242b71494 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 1 Jun 2014 12:19:12 +0200 Subject: [PATCH 7/8] Fixed invalid CSS --- todo/static/todo/css/styles.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/todo/static/todo/css/styles.css b/todo/static/todo/css/styles.css index 27d80eb..3d354b4 100644 --- a/todo/static/todo/css/styles.css +++ b/todo/static/todo/css/styles.css @@ -31,7 +31,6 @@ a.showlink { color:#474747; } - label { display: block; font-weight: bold; @@ -72,7 +71,7 @@ hr { } table.nocolor, table.nocolor tr, table.nocolor td { - background-color: ; + background-color: transparent; } table#tasktable td, table#tasktable th { From ca598fb794022b630b35d6afa0df70fd7898ca72 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 1 Jun 2014 12:24:31 +0200 Subject: [PATCH 8/8] Whitespace correction (reformatting only) --- todo/static/todo/css/styles.css | 80 ++++++++++++++++----------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/todo/static/todo/css/styles.css b/todo/static/todo/css/styles.css index 3d354b4..9412f0c 100644 --- a/todo/static/todo/css/styles.css +++ b/todo/static/todo/css/styles.css @@ -1,105 +1,105 @@ /*Distributed*/ ul.messages li { - color:green; - font-weight:bold; + color: green; + font-weight: bold; } .overdue { - color:#9A2441; - font-weight:bold; + color: #9A2441; + font-weight: bold; } /* Lighter font for completed items */ #completed li { - color: gray; + color: gray; } a.todo { - text-decoration:none; - color:#474747; + text-decoration: none; + color: #474747; } a.showlink { - text-decoration:underline; + text-decoration: underline; } #tasktable a { - font-weight:bold; - font-family:"Arial"; - text-decoration:none; - color:#474747; + font-weight: bold; + font-family: "Arial"; + text-decoration: none; + color: #474747; } label { - display: block; - font-weight: bold; + display: block; + font-weight: bold; } input { - color: #3A3A3A; - font-family:Verdana; - font-size:14px; + color: #3A3A3A; + font-family: Verdana; + font-size: 14px; } input[type='text'] { - width:300px; + width: 300px; } input#id_priority { - width:30px; + width: 30px; } .todo-button { - border: 1px solid #9B9B9B; - background: #E0E0E0; - padding-bottom:2px; - font-weight:bold; + border: 1px solid #9B9B9B; + background: #E0E0E0; + padding-bottom: 2px; + font-weight: bold; } hr { - color:#E5E5E5; + color: #E5E5E5; } #slideToggle { - color:#4A8251; + color: #4A8251; } .todo-break { - margin-top: 30px; - border-top: 1px dotted gray; + margin-top: 30px; + border-top: 1px dotted gray; } table.nocolor, table.nocolor tr, table.nocolor td { - background-color: transparent; + background-color: transparent; } table#tasktable td, table#tasktable th { - padding: 5px; -/* font-size:0.9em;*/ + padding: 5px; + /* font-size:0.9em;*/ } table#tasktable th { - text-align: left; -/* background-color: #9BCAE4; */ - background-color: #046380; - color: #fff; + text-align: left; + /* background-color: #9BCAE4; */ + background-color: #046380; + color: #fff; } table#tasktable tr.row1 { - background-color: #AEF0BB; + background-color: #AEF0BB; } table#tasktable tr.row2 { - background-color: #B6F3D5; + background-color: #B6F3D5; } .minor { - font-style:italic; - font-size:0.8em; + font-style: italic; + font-size: 0.8em; } .task_note, .task_comments { - width: 70%; - overflow: visible; + width: 70%; + overflow: visible; }