Merge pull request #12 from bittner/master

Changed related_name in foreign key "created_by", reformatted code, readme and license
This commit is contained in:
Scot Hacker 2014-06-02 22:51:25 -07:00
commit 4ba595d9cb
14 changed files with 323 additions and 326 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
# tools, IDEs, build folders
/.coverage/
/.idea/
/build/
/dist/
/docs/build/
/*.egg-info/
# Django and Python
*.py[cod]

27
LICENSE
View file

@ -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.
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.

View file

@ -1,3 +1,4 @@
include README
recursive-include todo/media *
include LICENSE
include README.rst
recursive-include todo/static *
recursive-include todo/templates *

16
README
View file

@ -1,16 +0,0 @@
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.
For documentation, see the django-todo wiki pages:
Overview and screenshots
http://github.com/shacker/django-todo/wiki/Overview-and-screenshots
Requirements and installation
http://github.com/shacker/django-todo/wiki/Requirements-and-Installation
This is version 1.3
Version history
http://github.com/shacker/django-todo/wiki/Version-history

21
README.rst Normal file
View file

@ -0,0 +1,21 @@
===========
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>`_
- `Requirements and installation
<http://github.com/shacker/django-todo/wiki/Requirements-and-Installation>`_
- `Version history
<http://github.com/shacker/django-todo/wiki/Version-history>`_

14
setup.py Normal file → Executable file
View file

@ -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',

View file

@ -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'

View file

@ -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)
admin.site.register(Item, ItemAdmin)

View file

@ -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})
)

View file

@ -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)
created_by = models.ForeignKey(User, related_name='created_by')
completed_date = models.DateField(blank=True, null=True)
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)
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,
)
self.author,
self.date,
)

View file

@ -1,4 +1,3 @@
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
STAFF_ONLY = getattr(settings, 'TODO_STAFF_ONLY', False)

View file

@ -1,106 +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: ;
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;
}

View file

@ -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<list_id>\d{1,4})/(?P<list_slug>[\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<list_id>\d{1,4})/(?P<list_slug>[\w-]+)/delete$', 'todo.views.del_list', name="todo-del_list"),
url(r'^task/(?P<task_id>\d{1,6})$', 'todo.views.view_task', name='todo-task_detail'),
url(r'^(?P<list_id>\d{1,4})/(?P<list_slug>[\w-]+)$', 'todo.views.view_list', name='todo-incomplete_tasks'),
url(r'^(?P<list_id>\d{1,4})/(?P<list_slug>[\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<list_id>\d{1,4})/(?P<list_slug>[\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"),
)

View file

@ -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))