diff --git a/todo/urls.py b/todo/urls.py
index 5db2f3b..eccef35 100644
--- a/todo/urls.py
+++ b/todo/urls.py
@@ -1,11 +1,12 @@
from django.urls import path
from todo import views
-
+from todo.features import HAS_TASK_MERGE
app_name = 'todo'
-urlpatterns = [
+from django.conf import settings
+urlpatterns = [
path(
'',
views.list_lists,
@@ -55,12 +56,19 @@ urlpatterns = [
'task//',
views.task_detail,
name='task_detail'),
+]
- path(
- 'task//autocomplete/',
- views.TaskAutocomplete.as_view(),
- name='task_autocomplete'),
+if HAS_TASK_MERGE:
+ # ensure autocomplete is optional
+ from todo.views.task_autocomplete import TaskAutocomplete
+ urlpatterns.append(
+ path(
+ 'task//autocomplete/',
+ TaskAutocomplete.as_view(),
+ name='task_autocomplete')
+ )
+urlpatterns.extend([
path(
'toggle_done//',
views.toggle_done,
@@ -75,4 +83,4 @@ urlpatterns = [
'search/',
views.search,
name="search"),
-]
+])
diff --git a/todo/utils.py b/todo/utils.py
index ae1766c..8e682ea 100644
--- a/todo/utils.py
+++ b/todo/utils.py
@@ -23,6 +23,10 @@ def staff_check(user):
return True
+def user_can_read_task(task, user):
+ return task.task_list.group in user.groups.all() or user.is_staff
+
+
def todo_get_backend(task):
'''returns a mail backend for some task'''
mail_backends = getattr(settings, "TODO_MAIL_BACKENDS", None)
@@ -148,8 +152,9 @@ def send_email_to_thread_participants(task, msg_body, user, subject=None):
for ca in commenters
if ca.author is not None
)
- for user_email in (task.created_by.email, task.assigned_to.email):
- recip_list.add(user_email)
+ for related_user in (task.created_by, task.assigned_to):
+ if related_user is not None:
+ recip_list.add(related_user.email)
recip_list = list(m for m in recip_list if m)
todo_send_mail(user, task, email_subject, email_body, recip_list)
diff --git a/todo/views/__init__.py b/todo/views/__init__.py
index 63cdb5b..c36bb65 100644
--- a/todo/views/__init__.py
+++ b/todo/views/__init__.py
@@ -6,5 +6,5 @@ from todo.views.list_detail import list_detail # noqa: F401
from todo.views.list_lists import list_lists # noqa: F401
from todo.views.reorder_tasks import reorder_tasks # noqa: F401
from todo.views.search import search # noqa: F401
-from todo.views.task_detail import task_detail, TaskAutocomplete # noqa: F401
+from todo.views.task_detail import task_detail # noqa: F401
from todo.views.toggle_done import toggle_done # noqa: F401
diff --git a/todo/views/task_autocomplete.py b/todo/views/task_autocomplete.py
new file mode 100644
index 0000000..0a5667e
--- /dev/null
+++ b/todo/views/task_autocomplete.py
@@ -0,0 +1,29 @@
+from dal import autocomplete
+from django.contrib.auth.decorators import login_required
+from django.core.exceptions import PermissionDenied
+from django.shortcuts import get_object_or_404
+from django.utils.decorators import method_decorator
+from todo.models import Task
+from todo.utils import user_can_read_task
+
+
+class TaskAutocomplete(autocomplete.Select2QuerySetView):
+ @method_decorator(login_required)
+ def dispatch(self, request, task_id, *args, **kwargs):
+ self.task = get_object_or_404(Task, pk=task_id)
+ if not user_can_read_task(self.task, request.user):
+ raise PermissionDenied
+
+ return super().dispatch(request, task_id, *args, **kwargs)
+
+ def get_queryset(self):
+ # Don't forget to filter out results depending on the visitor !
+ if not self.request.user.is_authenticated:
+ return Task.objects.none()
+
+ qs = Task.objects.filter(task_list=self.task.task_list).exclude(pk=self.task.pk)
+
+ if self.q:
+ qs = qs.filter(title__istartswith=self.q)
+
+ return qs
diff --git a/todo/views/task_detail.py b/todo/views/task_detail.py
index 436847a..64a201e 100644
--- a/todo/views/task_detail.py
+++ b/todo/views/task_detail.py
@@ -12,35 +12,35 @@ from django.utils.decorators import method_decorator
from todo.forms import AddEditTaskForm
from todo.models import Comment, Task
-from todo.utils import send_email_to_thread_participants, toggle_task_completed, staff_check
-from todo.utils import send_email_to_thread_participants, toggle_task_completed
-from dal import autocomplete
+from todo.utils import send_email_to_thread_participants, toggle_task_completed, staff_check, user_can_read_task
+from todo.features import HAS_TASK_MERGE
-def user_can_read_task(task, user):
- return task.task_list.group in user.groups.all() or user.is_staff
+if HAS_TASK_MERGE:
+ from dal import autocomplete
+ from todo.views.task_autocomplete import TaskAutocomplete
-class TaskAutocomplete(autocomplete.Select2QuerySetView):
- @method_decorator(login_required)
- def dispatch(self, request, task_id, *args, **kwargs):
- self.task = get_object_or_404(Task, pk=task_id)
- if not user_can_read_task(self.task, request.user):
- raise PermissionDenied
+def handle_add_comment(request, task):
+ if not request.POST.get("add_comment"):
+ return
- return super().dispatch(request, task_id, *args, **kwargs)
+ Comment.objects.create(
+ author=request.user,
+ task=task,
+ body=bleach.clean(request.POST["comment-body"], strip=True),
+ )
- def get_queryset(self):
- # Don't forget to filter out results depending on the visitor !
- if not self.request.user.is_authenticated:
- return Task.objects.none()
+ send_email_to_thread_participants(
+ task,
+ request.POST["comment-body"],
+ request.user,
+ subject='New comment posted on task "{}"'.format(task.title),
+ )
- qs = Task.objects.filter(task_list=self.task.task_list).exclude(pk=self.task.pk)
-
- if self.q:
- qs = qs.filter(title__istartswith=self.q)
-
- return qs
+ messages.success(
+ request, "Comment posted. Notification email sent to thread participants."
+ )
@login_required
@@ -50,26 +50,32 @@ def task_detail(request, task_id: int) -> HttpResponse:
"""
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).order_by('-date')
# 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.
if not user_can_read_task(task, request.user):
raise PermissionDenied
- class MergeForm(forms.Form):
- merge_target = forms.ModelChoiceField(
- queryset=Task.objects.all(),
- widget=autocomplete.ModelSelect2(
- url=reverse("todo:task_autocomplete", kwargs={"task_id": task_id})
- ),
- )
-
# Handle task merging
- if request.POST.get("merge_task_into"):
- merge_form = MergeForm(request.POST)
- if merge_form.is_valid():
- merge_target = merge_form.cleaned_data["merge_target"]
+ if not HAS_TASK_MERGE:
+ merge_form = None
+ else:
+ class MergeForm(forms.Form):
+ merge_target = forms.ModelChoiceField(
+ queryset=Task.objects.all(),
+ widget=autocomplete.ModelSelect2(
+ url=reverse("todo:task_autocomplete", kwargs={"task_id": task_id})
+ ),
+ )
+
+ # Handle task merging
+ if not request.POST.get("merge_task_into"):
+ merge_form = MergeForm()
+ else:
+ merge_form = MergeForm(request.POST)
+ if merge_form.is_valid():
+ merge_target = merge_form.cleaned_data["merge_target"]
if not user_can_read_task(merge_target, request.user):
raise PermissionDenied
@@ -78,29 +84,16 @@ def task_detail(request, task_id: int) -> HttpResponse:
"todo:task_detail",
kwargs={"task_id": merge_target.pk}
))
- else:
- merge_form = MergeForm()
# Save submitted comments
- if request.POST.get("add_comment"):
- Comment.objects.create(
- author=request.user,
- task=task,
- body=bleach.clean(request.POST["comment-body"], strip=True),
- )
-
- send_email_to_thread_participants(
- task,
- request.POST["comment-body"],
- request.user,
- subject='New comment posted on task "{}"'.format(task.title),
- )
- messages.success(
- request, "Comment posted. Notification email sent to thread participants."
- )
+ handle_add_comment(request, task)
# Save task edits
- if request.POST.get("add_edit_task"):
+ if not request.POST.get("add_edit_task"):
+ form = AddEditTaskForm(
+ request.user, instance=task, initial={"task_list": task.task_list}
+ )
+ else:
form = AddEditTaskForm(
request.user,
request.POST,
@@ -118,10 +111,6 @@ def task_detail(request, task_id: int) -> HttpResponse:
list_id=task.task_list.id,
list_slug=task.task_list.slug,
)
- else:
- form = AddEditTaskForm(
- request.user, instance=task, initial={"task_list": task.task_list}
- )
# Mark complete
if request.POST.get("toggle_done"):