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:
commit
4ba595d9cb
14 changed files with 323 additions and 326 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# tools, IDEs, build folders
|
||||
/.coverage/
|
||||
/.idea/
|
||||
/build/
|
||||
/dist/
|
||||
/docs/build/
|
||||
/*.egg-info/
|
||||
|
||||
# Django and Python
|
||||
*.py[cod]
|
27
LICENSE
27
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.
|
||||
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.
|
||||
|
|
|
@ -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
16
README
|
@ -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
21
README.rst
Normal 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>`_
|
12
setup.py
Normal file → Executable file
12
setup.py
Normal file → Executable 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',
|
||||
|
|
|
@ -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'
|
|
@ -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)
|
|
@ -1,9 +1,8 @@
|
|||
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):
|
||||
# The picklist showing allowable groups to which a new list can be added
|
||||
|
@ -17,9 +16,7 @@ class AddListForm(ModelForm):
|
|||
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):
|
||||
|
@ -29,55 +26,49 @@ class AddItemForm(ModelForm):
|
|||
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})
|
||||
)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
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)
|
||||
|
||||
|
@ -19,8 +17,6 @@ class List(models.Model):
|
|||
|
||||
super(List, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -29,7 +25,7 @@ class List(models.Model):
|
|||
|
||||
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"]
|
||||
|
@ -39,25 +35,22 @@ class List(models.Model):
|
|||
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):
|
||||
|
@ -66,7 +59,7 @@ class Item(models.Model):
|
|||
# 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()
|
||||
|
||||
|
@ -77,7 +70,7 @@ class Item(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)
|
||||
|
@ -87,6 +80,6 @@ class Comment(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return '%s - %s' % (
|
||||
self.author,
|
||||
self.date,
|
||||
)
|
||||
self.author,
|
||||
self.date,
|
||||
)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.conf import settings
|
||||
|
||||
STAFF_ONLY = getattr(settings, 'TODO_STAFF_ONLY', False)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
29
todo/urls.py
29
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<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"),
|
||||
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'^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'^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"),
|
||||
)
|
||||
|
||||
|
|
218
todo/views.py
218
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
|
||||
|
@ -25,7 +23,6 @@ current_site = Site.objects.get_current()
|
|||
|
||||
|
||||
def check_user_allowed(user):
|
||||
|
||||
"""
|
||||
test for user_passes_test decorator
|
||||
"""
|
||||
|
@ -35,32 +32,28 @@ 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.")
|
||||
|
||||
|
||||
# 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 :
|
||||
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()
|
||||
|
@ -69,12 +62,10 @@ def list_lists(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
|
||||
|
||||
|
@ -94,39 +85,35 @@ def del_list(request,list_id,list_slug):
|
|||
|
||||
# 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_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
|
||||
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
|
||||
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
|
||||
# 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')
|
||||
|
@ -138,8 +125,7 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0):
|
|||
p.save()
|
||||
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,7 +134,6 @@ 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')
|
||||
|
@ -157,19 +142,17 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0):
|
|||
p.delete()
|
||||
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)
|
||||
|
@ -178,24 +161,24 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0):
|
|||
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():
|
||||
|
@ -204,28 +187,28 @@ def view_list(request,list_id=0,list_slug=None,view_completed=0):
|
|||
|
||||
# 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)
|
||||
|
||||
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,12 +217,10 @@ 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)
|
||||
|
||||
|
@ -251,58 +232,55 @@ def view_task(request,task_id):
|
|||
|
||||
auth_ok = 1
|
||||
if request.POST:
|
||||
form = EditItemForm(request.POST,instance=task)
|
||||
form = EditItemForm(request.POST, instance=task)
|
||||
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
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()
|
||||
# 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 })
|
||||
# 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)
|
||||
# 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 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:
|
||||
except:
|
||||
messages.error(request, "Comment saved but mail not sent. Contact your administrator.")
|
||||
|
||||
messages.success(request, "The task has been edited.")
|
||||
|
||||
messages.success(request, "The task has been edited.")
|
||||
|
||||
return HttpResponseRedirect(reverse('todo-incomplete_tasks', args=[task.list.id, task.list.slug]))
|
||||
|
||||
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,7 +290,6 @@ 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]
|
||||
|
@ -340,67 +317,61 @@ 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()
|
||||
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." )
|
||||
|
||||
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)
|
||||
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:
|
||||
|
||||
query_string = ''
|
||||
|
@ -418,14 +389,13 @@ def search(request):
|
|||
# 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" :
|
||||
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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue