Rename Item model to Task
- With matching model, view, template changes
This commit is contained in:
parent
a2d02b0a8c
commit
d3d8d5e46c
15 changed files with 133 additions and 119 deletions
1
setup.py
1
setup.py
|
@ -21,7 +21,6 @@ setup(
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2',
|
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Topic :: Office/Business :: Groupware',
|
'Topic :: Office/Business :: Groupware',
|
||||||
'Topic :: Software Development :: Bug Tracking',
|
'Topic :: Software Development :: Bug Tracking',
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from todo.models import Item, TaskList, Comment
|
from todo.models import Task, TaskList, Comment
|
||||||
|
|
||||||
|
|
||||||
class ItemAdmin(admin.ModelAdmin):
|
class TaskAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'task_list', 'completed', 'priority', 'due_date')
|
list_display = ('title', 'task_list', 'completed', 'priority', 'due_date')
|
||||||
list_filter = ('task_list',)
|
list_filter = ('task_list',)
|
||||||
ordering = ('priority',)
|
ordering = ('priority',)
|
||||||
|
@ -15,4 +15,4 @@ class CommentAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
admin.site.register(TaskList)
|
admin.site.register(TaskList)
|
||||||
admin.site.register(Comment, CommentAdmin)
|
admin.site.register(Comment, CommentAdmin)
|
||||||
admin.site.register(Item, ItemAdmin)
|
admin.site.register(Task, TaskAdmin)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from todo.models import Item, TaskList
|
from todo.models import Task, TaskList
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class AddTaskListForm(ModelForm):
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|
||||||
class AddEditItemForm(ModelForm):
|
class AddEditTaskForm(ModelForm):
|
||||||
"""The picklist showing the users to which a new task can be assigned
|
"""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."""
|
must find other members of the groups the current list belongs to."""
|
||||||
|
|
||||||
|
@ -43,11 +43,11 @@ class AddEditItemForm(ModelForm):
|
||||||
widget=forms.Textarea(), required=False)
|
widget=forms.Textarea(), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Item
|
model = Task
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|
||||||
class AddExternalItemForm(ModelForm):
|
class AddExternalTaskForm(ModelForm):
|
||||||
"""Form to allow users who are not part of the GTD system to file a ticket."""
|
"""Form to allow users who are not part of the GTD system to file a ticket."""
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
|
@ -63,7 +63,7 @@ class AddExternalItemForm(ModelForm):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Item
|
model = Task
|
||||||
exclude = (
|
exclude = (
|
||||||
'task_list', 'created_date', 'due_date', 'created_by', 'assigned_to', 'completed', 'completed_date', )
|
'task_list', 'created_date', 'due_date', 'created_by', 'assigned_to', 'completed', 'completed_date', )
|
||||||
|
|
||||||
|
|
19
todo/migrations/0006_rename_item_model.py
Normal file
19
todo/migrations/0006_rename_item_model.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.0.3 on 2018-03-28 22:40
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('todo', '0005_auto_20180212_2325'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Item',
|
||||||
|
new_name='Task',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,14 +1,12 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class TaskList(models.Model):
|
class TaskList(models.Model):
|
||||||
name = models.CharField(max_length=60)
|
name = models.CharField(max_length=60)
|
||||||
slug = models.SlugField(default='',)
|
slug = models.SlugField(default='',)
|
||||||
|
@ -25,8 +23,7 @@ class TaskList(models.Model):
|
||||||
unique_together = ("group", "slug")
|
unique_together = ("group", "slug")
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
class Task(models.Model):
|
||||||
class Item(models.Model):
|
|
||||||
title = models.CharField(max_length=140)
|
title = models.CharField(max_length=140)
|
||||||
task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, null=True)
|
task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, null=True)
|
||||||
created_date = models.DateField(auto_now=True)
|
created_date = models.DateField(auto_now=True)
|
||||||
|
@ -41,7 +38,7 @@ class Item(models.Model):
|
||||||
|
|
||||||
# Has due date for an instance of this object passed?
|
# Has due date for an instance of this object passed?
|
||||||
def overdue_status(self):
|
def overdue_status(self):
|
||||||
"Returns whether the item's due date has passed or not."
|
"Returns whether the Tasks's due date has passed or not."
|
||||||
if self.due_date and datetime.date.today() > self.due_date:
|
if self.due_date and datetime.date.today() > self.due_date:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -51,25 +48,24 @@ class Item(models.Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('todo:task_detail', kwargs={'task_id': self.id, })
|
return reverse('todo:task_detail', kwargs={'task_id': self.id, })
|
||||||
|
|
||||||
# Auto-set the item creation / completed date
|
# Auto-set the Task creation / completed date
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
# If Item is being marked complete, set the completed_date
|
# If Task is being marked complete, set the completed_date
|
||||||
if self.completed:
|
if self.completed:
|
||||||
self.completed_date = datetime.datetime.now()
|
self.completed_date = datetime.datetime.now()
|
||||||
super(Item, self).save()
|
super(Task, self).save()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["priority"]
|
ordering = ["priority"]
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
"""
|
"""
|
||||||
Not using Django's built-in comments because 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.
|
a comment and change task details at the same time. Rolling our own since it's easy.
|
||||||
"""
|
"""
|
||||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
task = models.ForeignKey(Item, on_delete=models.CASCADE)
|
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||||
date = models.DateTimeField(default=datetime.datetime.now)
|
date = models.DateTimeField(default=datetime.datetime.now)
|
||||||
body = models.TextField(blank=True)
|
body = models.TextField(blank=True)
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
<p>Category tally:</p>
|
<p>Category tally:</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Incomplete: {{ item_count_undone }} </li>
|
<li>Incomplete: {{ task_count_undone }} </li>
|
||||||
<li>Complete: {{ item_count_done }} </li>
|
<li>Complete: {{ task_count_done }} </li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Total: {{ item_count_total }}</strong>
|
<strong>Total: {{ task_count_total }}</strong>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="id_assigned_to">Assigned To</label>
|
<label for="id_assigned_to">Assigned To</label>
|
||||||
{# See todo.forms.AddEditItemForm #}
|
{# See todo.forms.AddEditTaskForm #}
|
||||||
{{form.assigned_to}}
|
{{form.assigned_to}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<hr />
|
<hr />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if items %}
|
{% if tasks %}
|
||||||
{% if list_slug == "mine" %}
|
{% if list_slug == "mine" %}
|
||||||
<h1>Tasks assigned to me (in all groups)</h1>
|
<h1>Tasks assigned to me (in all groups)</h1>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<th>Del</th>
|
<th>Del</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for task in items %}
|
{% for task in tasks %}
|
||||||
<tr id="{{ task.id }}">
|
<tr id="{{ task.id }}">
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="toggle_done_tasks"
|
<input type="checkbox" name="toggle_done_tasks"
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h4>No items on this list yet (add one!)</h4>
|
<h4>No tasks on this list yet (add one!)</h4>
|
||||||
{% include 'todo/include/toggle_delete.html' %}
|
{% include 'todo/include/toggle_delete.html' %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -5,16 +5,16 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Todo Lists</h1>
|
<h1>Todo Lists</h1>
|
||||||
|
|
||||||
<p>{{ item_count }} items in {{ list_count }} list{{ list_count|pluralize }}</p>
|
<p>{{ task_count }} tasks in {{ list_count }} list{{ list_count|pluralize }}</p>
|
||||||
|
|
||||||
{% regroup lists by group as section_list %}
|
{% regroup lists by group as section_list %}
|
||||||
{% for group in section_list %}
|
{% for group in section_list %}
|
||||||
<h3>Group: {{ group.grouper }}</h3>
|
<h3>Group: {{ group.grouper }}</h3>
|
||||||
<ul class="list-group mb-4">
|
<ul class="list-group mb-4">
|
||||||
{% for item in group.list %}
|
{% for task in group.list %}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
<a href="{% url 'todo:list_detail' item.id item.slug %}">{{ item.name }}</a>
|
<a href="{% url 'todo:list_detail' task.id task.slug %}">{{ task.name }}</a>
|
||||||
<span class="badge badge-primary badge-pill">{{ item.item_set.count }}</span>
|
<span class="badge badge-primary badge-pill">{{ task.task_set.count }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
{% block content_title %}<h2 class="page_title">Search</h2>{% endblock %}
|
{% block content_title %}<h2 class="page_title">Search</h2>{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if found_items %}
|
{% if found_tasks %}
|
||||||
<h2>{{found_items.count}} search results for term: "{{ query_string }}"</h2>
|
<h2>{{found_tasks.count}} search results for term: "{{ query_string }}"</h2>
|
||||||
<div class="post_list">
|
<div class="post_list">
|
||||||
{% for f in found_items %}
|
{% for f in found_tasks %}
|
||||||
<p>
|
<p>
|
||||||
<strong>
|
<strong>
|
||||||
<a href="{% url 'todo:task_detail' f.id %}">{{ f.title }}</a>
|
<a href="{% url 'todo:task_detail' f.id %}">{{ f.title }}</a>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
||||||
|
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
from todo.models import Item, TaskList
|
from todo.models import Task, TaskList
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -13,14 +13,14 @@ def todo_setup(django_user_model):
|
||||||
u1 = django_user_model.objects.create_user(username="u1", password="password", email="u1@example.com")
|
u1 = django_user_model.objects.create_user(username="u1", password="password", email="u1@example.com")
|
||||||
u1.groups.add(g1)
|
u1.groups.add(g1)
|
||||||
tlist1 = TaskList.objects.create(group=g1, name="Zip", slug="zip")
|
tlist1 = TaskList.objects.create(group=g1, name="Zip", slug="zip")
|
||||||
Item.objects.create(created_by=u1, title="Task 1", task_list=tlist1, priority=1)
|
Task.objects.create(created_by=u1, title="Task 1", task_list=tlist1, priority=1)
|
||||||
Item.objects.create(created_by=u1, title="Task 2", task_list=tlist1, priority=2, completed=True)
|
Task.objects.create(created_by=u1, title="Task 2", task_list=tlist1, priority=2, completed=True)
|
||||||
Item.objects.create(created_by=u1, title="Task 3", task_list=tlist1, priority=3)
|
Task.objects.create(created_by=u1, title="Task 3", task_list=tlist1, priority=3)
|
||||||
|
|
||||||
g2 = Group.objects.create(name="Workgroup Two")
|
g2 = Group.objects.create(name="Workgroup Two")
|
||||||
u2 = django_user_model.objects.create_user(username="u2", password="password", email="u2@example.com")
|
u2 = django_user_model.objects.create_user(username="u2", password="password", email="u2@example.com")
|
||||||
u2.groups.add(g2)
|
u2.groups.add(g2)
|
||||||
tlist2 = TaskList.objects.create(group=g2, name="Zap", slug="zap")
|
tlist2 = TaskList.objects.create(group=g2, name="Zap", slug="zap")
|
||||||
Item.objects.create(created_by=u2, title="Task 1", task_list=tlist2, priority=1)
|
Task.objects.create(created_by=u2, title="Task 1", task_list=tlist2, priority=1)
|
||||||
Item.objects.create(created_by=u2, title="Task 2", task_list=tlist2, priority=2, completed=True)
|
Task.objects.create(created_by=u2, title="Task 2", task_list=tlist2, priority=2, completed=True)
|
||||||
Item.objects.create(created_by=u2, title="Task 3", task_list=tlist2, priority=3)
|
Task.objects.create(created_by=u2, title="Task 3", task_list=tlist2, priority=3)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
||||||
|
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
|
|
||||||
from todo.models import Item, Comment
|
from todo.models import Task, Comment
|
||||||
from todo.utils import toggle_done, toggle_deleted, send_notify_mail, send_email_to_thread_participants
|
from todo.utils import toggle_done, toggle_deleted, send_notify_mail, send_email_to_thread_participants
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ def email_backend_setup(settings):
|
||||||
def test_toggle_done(todo_setup):
|
def test_toggle_done(todo_setup):
|
||||||
"""Utility function takes an array of POSTed IDs and changes their `completed` status.
|
"""Utility function takes an array of POSTed IDs and changes their `completed` status.
|
||||||
"""
|
"""
|
||||||
u1_tasks = Item.objects.filter(created_by__username="u1")
|
u1_tasks = Task.objects.filter(created_by__username="u1")
|
||||||
completed = u1_tasks.filter(completed=True)
|
completed = u1_tasks.filter(completed=True)
|
||||||
incomplete = u1_tasks.filter(completed=False)
|
incomplete = u1_tasks.filter(completed=False)
|
||||||
|
|
||||||
|
@ -38,13 +38,13 @@ def test_toggle_done(todo_setup):
|
||||||
def test_toggle_deleted(todo_setup):
|
def test_toggle_deleted(todo_setup):
|
||||||
"""Unlike toggle_done, delete means delete, so it's not really a toggle.
|
"""Unlike toggle_done, delete means delete, so it's not really a toggle.
|
||||||
"""
|
"""
|
||||||
u1_tasks = Item.objects.filter(created_by__username="u1")
|
u1_tasks = Task.objects.filter(created_by__username="u1")
|
||||||
assert u1_tasks.count() == 3
|
assert u1_tasks.count() == 3
|
||||||
t1 = u1_tasks.first()
|
t1 = u1_tasks.first()
|
||||||
t2 = u1_tasks.last()
|
t2 = u1_tasks.last()
|
||||||
|
|
||||||
toggle_deleted([t1.id, t2.id, ])
|
toggle_deleted([t1.id, t2.id, ])
|
||||||
u1_tasks = Item.objects.filter(created_by__username="u1")
|
u1_tasks = Task.objects.filter(created_by__username="u1")
|
||||||
assert u1_tasks.count() == 1
|
assert u1_tasks.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ def test_send_notify_mail_not_me(todo_setup, django_user_model, email_backend_se
|
||||||
u1 = django_user_model.objects.get(username="u1")
|
u1 = django_user_model.objects.get(username="u1")
|
||||||
u2 = django_user_model.objects.get(username="u2")
|
u2 = django_user_model.objects.get(username="u2")
|
||||||
|
|
||||||
task = Item.objects.filter(created_by=u1).first()
|
task = Task.objects.filter(created_by=u1).first()
|
||||||
task.assigned_to = u2
|
task.assigned_to = u2
|
||||||
task.save()
|
task.save()
|
||||||
send_notify_mail(task)
|
send_notify_mail(task)
|
||||||
|
@ -68,7 +68,7 @@ def test_send_notify_mail_myself(todo_setup, django_user_model, email_backend_se
|
||||||
"""
|
"""
|
||||||
|
|
||||||
u1 = django_user_model.objects.get(username="u1")
|
u1 = django_user_model.objects.get(username="u1")
|
||||||
task = Item.objects.filter(created_by=u1).first()
|
task = Task.objects.filter(created_by=u1).first()
|
||||||
task.assigned_to = u1
|
task.assigned_to = u1
|
||||||
task.save()
|
task.save()
|
||||||
send_notify_mail(task)
|
send_notify_mail(task)
|
||||||
|
@ -80,7 +80,7 @@ def test_send_email_to_thread_participants(todo_setup, django_user_model, email_
|
||||||
Notification email should be sent to all three users."""
|
Notification email should be sent to all three users."""
|
||||||
|
|
||||||
u1 = django_user_model.objects.get(username="u1")
|
u1 = django_user_model.objects.get(username="u1")
|
||||||
task = Item.objects.filter(created_by=u1).first()
|
task = Task.objects.filter(created_by=u1).first()
|
||||||
|
|
||||||
u3 = django_user_model.objects.create_user(username="u3", password="zzz", email="u3@example.com")
|
u3 = django_user_model.objects.create_user(username="u3", password="zzz", email="u3@example.com")
|
||||||
u4 = django_user_model.objects.create_user(username="u4", password="zzz", email="u4@example.com")
|
u4 = django_user_model.objects.create_user(username="u4", password="zzz", email="u4@example.com")
|
||||||
|
|
|
@ -3,7 +3,7 @@ import pytest
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from todo.models import Item, TaskList
|
from todo.models import Task, TaskList
|
||||||
|
|
||||||
"""
|
"""
|
||||||
First the "smoketests" - do they respond at all for a logged in admin user?
|
First the "smoketests" - do they respond at all for a logged in admin user?
|
||||||
|
@ -15,7 +15,7 @@ After that, view contents and behaviors.
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_todo_setup(todo_setup):
|
def test_todo_setup(todo_setup):
|
||||||
assert Item.objects.all().count() == 6
|
assert Task.objects.all().count() == 6
|
||||||
|
|
||||||
|
|
||||||
def test_view_list_lists(todo_setup, admin_client):
|
def test_view_list_lists(todo_setup, admin_client):
|
||||||
|
@ -73,7 +73,7 @@ def test_view_add_list(todo_setup, admin_client):
|
||||||
|
|
||||||
|
|
||||||
def test_view_task_detail(todo_setup, admin_client):
|
def test_view_task_detail(todo_setup, admin_client):
|
||||||
task = Item.objects.first()
|
task = Task.objects.first()
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -131,7 +131,7 @@ def test_view_list_not_mine(todo_setup, client):
|
||||||
|
|
||||||
def test_view_task_mine(todo_setup, client):
|
def test_view_task_mine(todo_setup, client):
|
||||||
# Users can always view their own tasks
|
# Users can always view their own tasks
|
||||||
task = Item.objects.filter(created_by__username="u1").first()
|
task = Task.objects.filter(created_by__username="u1").first()
|
||||||
client.login(username="u1", password="password")
|
client.login(username="u1", password="password")
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
|
@ -147,7 +147,7 @@ def test_view_task_my_group(todo_setup, client, django_user_model):
|
||||||
u2.groups.add(g1)
|
u2.groups.add(g1)
|
||||||
|
|
||||||
# Now u2 should be able to view one of u1's tasks.
|
# Now u2 should be able to view one of u1's tasks.
|
||||||
task = Item.objects.filter(created_by__username="u1").first()
|
task = Task.objects.filter(created_by__username="u1").first()
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
||||||
client.login(username="u2", password="password")
|
client.login(username="u2", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
|
@ -157,7 +157,7 @@ def test_view_task_my_group(todo_setup, client, django_user_model):
|
||||||
def test_view_task_not_in_my_group(todo_setup, client):
|
def test_view_task_not_in_my_group(todo_setup, client):
|
||||||
# User canNOT view a task that isn't theirs if the two users are not in a shared group.
|
# User canNOT view a task that isn't theirs if the two users are not in a shared group.
|
||||||
# For this we can use the fixture data as-is.
|
# For this we can use the fixture data as-is.
|
||||||
task = Item.objects.filter(created_by__username="u1").first()
|
task = Task.objects.filter(created_by__username="u1").first()
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
||||||
client.login(username="u2", password="password")
|
client.login(username="u2", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
|
|
|
@ -4,35 +4,35 @@ from django.contrib.sites.models import Site
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from todo.models import Item, Comment
|
from todo.models import Task, Comment
|
||||||
|
|
||||||
|
|
||||||
def toggle_done(item_ids):
|
def toggle_done(task_ids):
|
||||||
"""Check for items in the mark_done POST array. If present, change status to complete.
|
"""Check for tasks in the mark_done POST array. If present, change status to complete.
|
||||||
Takes a list of task IDs. Returns list of status change strings.
|
Takes a list of task IDs. Returns list of status change strings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ret = []
|
_ret = []
|
||||||
for item_id in item_ids:
|
for task_id in task_ids:
|
||||||
i = Item.objects.get(id=item_id)
|
i = Task.objects.get(id=task_id)
|
||||||
old_state = "completed" if i.completed else "incomplete"
|
old_state = "completed" if i.completed else "incomplete"
|
||||||
i.completed = not i.completed # Invert the done state, either way
|
i.completed = not i.completed # Invert the done state, either way
|
||||||
new_state = "completed" if i.completed else "incomplete"
|
new_state = "completed" if i.completed else "incomplete"
|
||||||
i.completed_date = datetime.datetime.now()
|
i.completed_date = datetime.datetime.now()
|
||||||
i.save()
|
i.save()
|
||||||
_ret.append("Item \"{i}\" changed from {o} to {n}.".format(i=i.title, o=old_state, n=new_state))
|
_ret.append("Task \"{i}\" changed from {o} to {n}.".format(i=i.title, o=old_state, n=new_state))
|
||||||
|
|
||||||
return _ret
|
return _ret
|
||||||
|
|
||||||
|
|
||||||
def toggle_deleted(deleted_item_ids):
|
def toggle_deleted(deleted_task_ids):
|
||||||
"""Delete selected items. Returns list of status change strings.
|
"""Delete selected tasks. Returns list of status change strings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ret = []
|
_ret = []
|
||||||
for item_id in deleted_item_ids:
|
for task_id in deleted_task_ids:
|
||||||
i = Item.objects.get(id=item_id)
|
i = Task.objects.get(id=task_id)
|
||||||
_ret.append("Item \"{i}\" deleted.".format(i=i.title))
|
_ret.append("Task \"{i}\" deleted.".format(i=i.title))
|
||||||
i.delete()
|
i.delete()
|
||||||
|
|
||||||
return _ret
|
return _ret
|
||||||
|
|
104
todo/views.py
104
todo/views.py
|
@ -14,8 +14,8 @@ from django.shortcuts import get_object_or_404, render, redirect
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from todo.forms import AddTaskListForm, AddEditItemForm, AddExternalItemForm, SearchForm
|
from todo.forms import AddTaskListForm, AddEditTaskForm, AddExternalTaskForm, SearchForm
|
||||||
from todo.models import Item, TaskList, Comment
|
from todo.models import Task, TaskList, Comment
|
||||||
from todo.utils import (
|
from todo.utils import (
|
||||||
toggle_done,
|
toggle_done,
|
||||||
toggle_deleted,
|
toggle_deleted,
|
||||||
|
@ -62,16 +62,16 @@ def list_lists(request) -> HttpResponse:
|
||||||
|
|
||||||
# superusers see all lists, so count shouldn't filter by just lists the admin belongs to
|
# superusers 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()
|
task_count = Task.objects.filter(completed=0).count()
|
||||||
else:
|
else:
|
||||||
item_count = Item.objects.filter(completed=0).filter(task_list__group__in=request.user.groups.all()).count()
|
task_count = Task.objects.filter(completed=0).filter(task_list__group__in=request.user.groups.all()).count()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"lists": lists,
|
"lists": lists,
|
||||||
"thedate": thedate,
|
"thedate": thedate,
|
||||||
"searchform": searchform,
|
"searchform": searchform,
|
||||||
"list_count": list_count,
|
"list_count": list_count,
|
||||||
"item_count": item_count,
|
"task_count": task_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'todo/list_lists.html', context)
|
return render(request, 'todo/list_lists.html', context)
|
||||||
|
@ -95,15 +95,15 @@ def del_list(request, list_id: int, list_slug: str) -> HttpResponse:
|
||||||
messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
|
messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
|
||||||
return redirect('todo:lists')
|
return redirect('todo:lists')
|
||||||
else:
|
else:
|
||||||
item_count_done = Item.objects.filter(task_list=task_list.id, completed=True).count()
|
task_count_done = Task.objects.filter(task_list=task_list.id, completed=True).count()
|
||||||
item_count_undone = Item.objects.filter(task_list=task_list.id, completed=False).count()
|
task_count_undone = Task.objects.filter(task_list=task_list.id, completed=False).count()
|
||||||
item_count_total = Item.objects.filter(task_list=task_list.id).count()
|
task_count_total = Task.objects.filter(task_list=task_list.id).count()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"task_list": task_list,
|
"task_list": task_list,
|
||||||
"item_count_done": item_count_done,
|
"task_count_done": task_count_done,
|
||||||
"item_count_undone": item_count_undone,
|
"task_count_undone": task_count_undone,
|
||||||
"item_count_total": item_count_total,
|
"task_count_total": task_count_total,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'todo/del_list.html', context)
|
return render(request, 'todo/del_list.html', context)
|
||||||
|
@ -111,37 +111,37 @@ def del_list(request, list_id: int, list_slug: str) -> HttpResponse:
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
||||||
"""Display and manage items in a todo list.
|
"""Display and manage tasks in a todo list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Defaults
|
# Defaults
|
||||||
task_list = None
|
task_list = None
|
||||||
form = None
|
form = None
|
||||||
|
|
||||||
# Which items to show on this list view?
|
# Which tasks to show on this list view?
|
||||||
if list_slug == "mine":
|
if list_slug == "mine":
|
||||||
items = Item.objects.filter(assigned_to=request.user)
|
tasks =Task.objects.filter(assigned_to=request.user)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Show a specific list, ensuring permissions.
|
# Show a specific list, ensuring permissions.
|
||||||
task_list = get_object_or_404(TaskList, id=list_id)
|
task_list = get_object_or_404(TaskList, id=list_id)
|
||||||
if task_list.group not in request.user.groups.all() and not request.user.is_staff:
|
if task_list.group not in request.user.groups.all() and not request.user.is_staff:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
items = Item.objects.filter(task_list=task_list.id)
|
tasks = Task.objects.filter(task_list=task_list.id)
|
||||||
|
|
||||||
# Additional filtering
|
# Additional filtering
|
||||||
if view_completed:
|
if view_completed:
|
||||||
items = items.filter(completed=True)
|
tasks = tasks.filter(completed=True)
|
||||||
else:
|
else:
|
||||||
items = items.filter(completed=False)
|
tasks = tasks.filter(completed=False)
|
||||||
|
|
||||||
if request.POST:
|
if request.POST:
|
||||||
# Process completed and deleted items on each POST
|
# Process completed and deleted tasks on each POST
|
||||||
results_changed = toggle_done(request.POST.getlist('toggle_done_tasks'))
|
results_changed = toggle_done(request.POST.getlist('toggle_done_tasks'))
|
||||||
for res in results_changed:
|
for res in results_changed:
|
||||||
messages.success(request, res)
|
messages.success(request, res)
|
||||||
|
|
||||||
results_changed = toggle_deleted(request, request.POST.getlist('toggle_deleted_tasks'))
|
results_changed = toggle_deleted(request.POST.getlist('toggle_deleted_tasks'))
|
||||||
for res in results_changed:
|
for res in results_changed:
|
||||||
messages.success(request, res)
|
messages.success(request, res)
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
||||||
# ######################
|
# ######################
|
||||||
|
|
||||||
if request.POST.getlist('add_edit_task'):
|
if request.POST.getlist('add_edit_task'):
|
||||||
form = AddEditItemForm(request.user, request.POST, initial={
|
form = AddEditTaskForm(request.user, request.POST, initial={
|
||||||
'assigned_to': request.user.id,
|
'assigned_to': request.user.id,
|
||||||
'priority': 999,
|
'priority': 999,
|
||||||
'task_list': task_list
|
'task_list': task_list
|
||||||
|
@ -168,7 +168,7 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
||||||
else:
|
else:
|
||||||
# Don't allow adding new tasks on some views
|
# Don't allow adding new tasks on some views
|
||||||
if list_slug not in ["mine", "recent-add", "recent-complete", ]:
|
if list_slug not in ["mine", "recent-add", "recent-complete", ]:
|
||||||
form = AddEditItemForm(request.user, initial={
|
form = AddEditTaskForm(request.user, initial={
|
||||||
'assigned_to': request.user.id,
|
'assigned_to': request.user.id,
|
||||||
'priority': 999,
|
'priority': 999,
|
||||||
'task_list': task_list
|
'task_list': task_list
|
||||||
|
@ -179,7 +179,7 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
||||||
"list_slug": list_slug,
|
"list_slug": list_slug,
|
||||||
"task_list": task_list,
|
"task_list": task_list,
|
||||||
"form": form,
|
"form": form,
|
||||||
"items": items,
|
"tasks": tasks,
|
||||||
"view_completed": view_completed,
|
"view_completed": view_completed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,10 +191,10 @@ def task_detail(request, task_id: int) -> HttpResponse:
|
||||||
"""View task details. Allow task details to be edited. Process new comments on task.
|
"""View task details. Allow task details to be edited. Process new comments on task.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
task = get_object_or_404(Item, pk=task_id)
|
task = get_object_or_404(Task, pk=task_id)
|
||||||
comment_list = Comment.objects.filter(task=task_id)
|
comment_list = Comment.objects.filter(task=task_id)
|
||||||
|
|
||||||
# Ensure user has permission to view item. Admins can view all tasks.
|
# Ensure user has permission to view task. Admins can view all tasks.
|
||||||
# Get the group this task belongs to, and check whether current user is a member of that group.
|
# Get the group this task belongs to, and check whether current user is a member of that group.
|
||||||
if task.task_list.group not in request.user.groups.all() and not request.user.is_staff:
|
if task.task_list.group not in request.user.groups.all() and not request.user.is_staff:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
@ -212,14 +212,14 @@ def task_detail(request, task_id: int) -> HttpResponse:
|
||||||
|
|
||||||
# Save task edits
|
# Save task edits
|
||||||
if request.POST.get('add_edit_task'):
|
if request.POST.get('add_edit_task'):
|
||||||
form = AddEditItemForm(request.user, request.POST, instance=task, initial={'task_list': task.task_list})
|
form = AddEditTaskForm(request.user, request.POST, instance=task, initial={'task_list': task.task_list})
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
messages.success(request, "The task has been edited.")
|
messages.success(request, "The task has been edited.")
|
||||||
return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug)
|
return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug)
|
||||||
else:
|
else:
|
||||||
form = AddEditItemForm(request.user, instance=task, initial={'task_list': task.task_list})
|
form = AddEditTaskForm(request.user, instance=task, initial={'task_list': task.task_list})
|
||||||
|
|
||||||
# Mark complete
|
# Mark complete
|
||||||
if request.POST.get('toggle_done'):
|
if request.POST.get('toggle_done'):
|
||||||
|
@ -251,15 +251,15 @@ def reorder_tasks(request) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
newtasklist = request.POST.getlist('tasktable[]')
|
newtasklist = request.POST.getlist('tasktable[]')
|
||||||
if newtasklist:
|
if newtasklist:
|
||||||
# First item in received list is always empty - remove it
|
# First task in received list is always empty - remove it
|
||||||
del newtasklist[0]
|
del newtasklist[0]
|
||||||
|
|
||||||
# Re-prioritize each item in list
|
# Re-prioritize each task in list
|
||||||
i = 1
|
i = 1
|
||||||
for t in newtasklist:
|
for t in newtasklist:
|
||||||
newitem = Item.objects.get(pk=t)
|
newtask = Task.objects.get(pk=t)
|
||||||
newitem.priority = i
|
newtask.priority = i
|
||||||
newitem.save()
|
newtask.save()
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# All views must return an httpresponse of some kind ... without this we get
|
# All views must return an httpresponse of some kind ... without this we get
|
||||||
|
@ -306,33 +306,33 @@ def search(request) -> HttpResponse:
|
||||||
if request.GET:
|
if request.GET:
|
||||||
|
|
||||||
query_string = ''
|
query_string = ''
|
||||||
found_items = None
|
found_tasks = None
|
||||||
if ('q' in request.GET) and request.GET['q'].strip():
|
if ('q' in request.GET) and request.GET['q'].strip():
|
||||||
query_string = request.GET['q']
|
query_string = request.GET['q']
|
||||||
|
|
||||||
found_items = Item.objects.filter(
|
found_tasks = Task.objects.filter(
|
||||||
Q(title__icontains=query_string) |
|
Q(title__icontains=query_string) |
|
||||||
Q(note__icontains=query_string)
|
Q(note__icontains=query_string)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# What if they selected the "completed" toggle but didn't enter a query string?
|
# What if they selected the "completed" toggle but didn't enter a query string?
|
||||||
# We still need found_items in a queryset so it can be "excluded" below.
|
# We still need found_tasks in a queryset so it can be "excluded" below.
|
||||||
found_items = Item.objects.all()
|
found_tasks = Task.objects.all()
|
||||||
|
|
||||||
if 'inc_complete' in request.GET:
|
if 'inc_complete' in request.GET:
|
||||||
found_items = found_items.exclude(completed=True)
|
found_tasks = found_tasks.exclude(completed=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
query_string = None
|
query_string = None
|
||||||
found_items = None
|
found_tasks =None
|
||||||
|
|
||||||
# Only include items that are in groups of which this user is a member:
|
# Only include tasks that are in groups of which this user is a member:
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
found_items = found_items.filter(task_list__group__in=request.user.groups.all())
|
found_tasks = found_tasks.filter(task_list__group__in=request.user.groups.all())
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'query_string': query_string,
|
'query_string': query_string,
|
||||||
'found_items': found_items
|
'found_tasks': found_tasks
|
||||||
}
|
}
|
||||||
return render(request, 'todo/search_results.html', context)
|
return render(request, 'todo/search_results.html', context)
|
||||||
|
|
||||||
|
@ -353,25 +353,25 @@ def external_add(request) -> HttpResponse:
|
||||||
raise RuntimeError("There is no TaskList with ID specified for DEFAULT_LIST_ID in settings.")
|
raise RuntimeError("There is no TaskList with ID specified for DEFAULT_LIST_ID in settings.")
|
||||||
|
|
||||||
if request.POST:
|
if request.POST:
|
||||||
form = AddExternalItemForm(request.POST)
|
form = AddExternalTaskForm(request.POST)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
current_site = Site.objects.get_current()
|
current_site = Site.objects.get_current()
|
||||||
item = form.save(commit=False)
|
task = form.save(commit=False)
|
||||||
item.task_list = TaskList.objects.get(id=settings.TODO_DEFAULT_LIST_ID)
|
task.task_list = TaskList.objects.get(id=settings.TODO_DEFAULT_LIST_ID)
|
||||||
item.created_by = request.user
|
task.created_by = request.user
|
||||||
if settings.TODO_DEFAULT_ASSIGNEE:
|
if settings.TODO_DEFAULT_ASSIGNEE:
|
||||||
item.assigned_to = User.objects.get(username=settings.TODO_DEFAULT_ASSIGNEE)
|
task.assigned_to = User.objects.get(username=settings.TODO_DEFAULT_ASSIGNEE)
|
||||||
item.save()
|
task.save()
|
||||||
|
|
||||||
# Send email to assignee if we have one
|
# Send email to assignee if we have one
|
||||||
if item.assigned_to:
|
if task.assigned_to:
|
||||||
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': item.title})
|
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task.title})
|
||||||
email_body = render_to_string("todo/email/assigned_body.txt", {'task': item, 'site': current_site, })
|
email_body = render_to_string("todo/email/assigned_body.txt", {'task': task, 'site': current_site, })
|
||||||
try:
|
try:
|
||||||
send_mail(
|
send_mail(
|
||||||
email_subject, email_body, item.created_by.email,
|
email_subject, email_body, task.created_by.email,
|
||||||
[item.assigned_to.email, ], fail_silently=False)
|
[task.assigned_to.email, ], fail_silently=False)
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
messages.error(request, "Task saved but mail not sent. Contact your administrator.")
|
messages.error(request, "Task saved but mail not sent. Contact your administrator.")
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ def external_add(request) -> HttpResponse:
|
||||||
return redirect(settings.TODO_PUBLIC_SUBMIT_REDIRECT)
|
return redirect(settings.TODO_PUBLIC_SUBMIT_REDIRECT)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = AddExternalItemForm(initial={'priority': 999})
|
form = AddExternalTaskForm(initial={'priority': 999})
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"form": form,
|
"form": form,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue