Implement mail tracker system

* Implement mail tracking

Signed-off-by: Victor "multun" Collod <victor.collod@prologin.org>

* Implement task merging

* Add a mail tracker title format pattern

* Autocomplete task names

* Fix comment display

* Track notification answers

* Add a socket timeout for the mail worker

A mail worker is a long running application. And sometimes, the IMAP server
just hangs for hours for no apparent reason. imaplib doesn't enable setting
a timeout, and setting it globally seems fine.

* Only validate the merge form when submitted

* Redirect to the new form when merging

* Prettier task edit UI

* Make task merging optional

* Test mail tracking

* Update documentation for mail tracking

* Update dependencies

* Add the TODO_COMMENT_CLASSES setting

* Fix dependencies install order

* Remove debug leftovers, improve documentation

* Fail on missing from_address
This commit is contained in:
multun 2019-03-11 08:04:19 +01:00 committed by Scot Hacker
parent d0212b8a55
commit c7ad961ef3
28 changed files with 1069 additions and 136 deletions

View file

@ -1,15 +1,47 @@
import bleach
import datetime
import bleach
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.shortcuts import get_object_or_404, redirect, render, redirect
from django.urls import reverse
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, staff_check, user_can_read_task
from todo.features import HAS_TASK_MERGE
if HAS_TASK_MERGE:
from dal import autocomplete
from todo.views.task_autocomplete import TaskAutocomplete
def handle_add_comment(request, task):
if not request.POST.get("add_comment"):
return
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."
)
@login_required
@ -19,33 +51,55 @@ 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 task.task_list.group not in request.user.groups.all() and not request.user.is_staff:
if not user_can_read_task(task, request.user):
raise PermissionDenied
# 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),
)
# Handle task merging
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})
),
)
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 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
task.merge_into(merge_target)
return redirect(reverse(
"todo:task_detail",
kwargs={"task_id": merge_target.pk}
))
# Save submitted comments
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, request.POST, instance=task, initial={"task_list": task.task_list}
request.user, instance=task, initial={"task_list": task.task_list}
)
else:
form = AddEditTaskForm(
request.user,
request.POST,
instance=task,
initial={"task_list": task.task_list},
)
if form.is_valid():
@ -54,10 +108,10 @@ def task_detail(request, task_id: int) -> HttpResponse:
item.save()
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
"todo:list_detail",
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"):
@ -72,6 +126,13 @@ def task_detail(request, task_id: int) -> HttpResponse:
else:
thedate = datetime.datetime.now()
context = {"task": task, "comment_list": comment_list, "form": form, "thedate": thedate}
context = {
"task": task,
"comment_list": comment_list,
"form": form,
"merge_form": merge_form,
"thedate": thedate,
"comment_classes": getattr(settings, 'TODO_COMMENT_CLASSES', []),
}
return render(request, "todo/task_detail.html", context)