diff --git a/todo/models.py b/todo/models.py index 2151fc8..a9582dc 100644 --- a/todo/models.py +++ b/todo/models.py @@ -1,10 +1,12 @@ from __future__ import unicode_literals + import datetime +import os import textwrap from django.conf import settings from django.contrib.auth.models import Group -from django.db import models, DEFAULT_DB_ALIAS +from django.db import DEFAULT_DB_ALIAS, models from django.db.transaction import Atomic, get_connection from django.urls import reverse from django.utils import timezone @@ -177,5 +179,12 @@ class Attachment(models.Model): timestamp = models.DateTimeField(default=datetime.datetime.now) file = models.FileField(upload_to=get_attachment_upload_dir, max_length=255) + def filename(self): + return os.path.basename(self.file.name) + + def extension(self): + name, extension = os.path.splitext(self.file.name) + return extension + def __str__(self): return f"{self.task.id} - {self.file.name}" diff --git a/todo/templates/todo/task_detail.html b/todo/templates/todo/task_detail.html index a1f8d29..eea9c91 100644 --- a/todo/templates/todo/task_detail.html +++ b/todo/templates/todo/task_detail.html @@ -115,6 +115,55 @@ {% endif %} +{% if attachments_enabled %} +
+
+ Attachments +
+ +
+ {% if task.attachment_set.count %} +
+ + + + + + + + + + + {% for attachment in task.attachment_set.all %} + + + + + + + {% endfor %} + +
FileUploadedByType
{{ attachment.filename }}{{ attachment.timestamp }}{{ attachment.added_by.get_full_name }}{{ attachment.extension.lower }}
+
+ {% endif %} + +
+ {% csrf_token %} +
+
+ + +
+
+ +
+
+
+ +
+
+{% endif %} +
Add comment
@@ -152,3 +201,16 @@ {% endif %}
{% endblock %} + +{% block extra_js %} + {# Support file attachment uploader #} + +{% endblock extra_js %} + diff --git a/todo/views/task_detail.py b/todo/views/task_detail.py index 8ded8d8..4351130 100644 --- a/todo/views/task_detail.py +++ b/todo/views/task_detail.py @@ -1,25 +1,27 @@ -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, redirect +from django.shortcuts import get_object_or_404, redirect, render 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, user_can_read_task from todo.features import HAS_TASK_MERGE - +from todo.forms import AddEditTaskForm +from todo.models import Attachment, Comment, Task +from todo.utils import ( + send_email_to_thread_participants, + staff_check, + toggle_task_completed, + user_can_read_task, +) if HAS_TASK_MERGE: from dal import autocomplete - from todo.views.task_autocomplete import TaskAutocomplete def handle_add_comment(request, task): @@ -27,9 +29,7 @@ def handle_add_comment(request, task): return Comment.objects.create( - author=request.user, - task=task, - body=bleach.clean(request.POST["comment-body"], strip=True), + author=request.user, task=task, body=bleach.clean(request.POST["comment-body"], strip=True) ) send_email_to_thread_participants( @@ -39,9 +39,7 @@ def handle_add_comment(request, task): subject='New comment posted on task "{}"'.format(task.title), ) - messages.success( - request, "Comment posted. Notification email sent to thread participants." - ) + messages.success(request, "Comment posted. Notification email sent to thread participants.") @login_required @@ -51,7 +49,7 @@ 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).order_by('-date') + 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. @@ -62,6 +60,7 @@ def task_detail(request, task_id: int) -> HttpResponse: if not HAS_TASK_MERGE: merge_form = None else: + class MergeForm(forms.Form): merge_target = forms.ModelChoiceField( queryset=Task.objects.all(), @@ -81,25 +80,17 @@ def task_detail(request, task_id: int) -> HttpResponse: raise PermissionDenied task.merge_into(merge_target) - return redirect(reverse( - "todo:task_detail", - kwargs={"task_id": merge_target.pk} - )) + return redirect(reverse("todo:task_detail", kwargs={"task_id": merge_target.pk})) # Save submitted comments handle_add_comment(request, task) # Save task edits if not request.POST.get("add_edit_task"): - form = AddEditTaskForm( - request.user, instance=task, initial={"task_list": task.task_list} - ) + form = AddEditTaskForm(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}, + request.user, request.POST, instance=task, initial={"task_list": task.task_list} ) if form.is_valid(): @@ -108,9 +99,7 @@ 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 ) # Mark complete @@ -126,13 +115,33 @@ def task_detail(request, task_id: int) -> HttpResponse: else: thedate = datetime.datetime.now() + # Handle uploaded files + if request.FILES.get("attachment_file_input"): + Attachment.objects.create( + task=task, + added_by=request.user, + timestamp=datetime.datetime.now(), + file=request.FILES.get("attachment_file_input"), + ) + return redirect("todo:task_detail", task_id=task.id) + + # For the context: Settings for file attachments defaults to True + # FIXME: Move settings defaults to a central location? + attachments_enabled = True + if ( + hasattr(settings, "TODO_ALLOW_FILE_ATTACHMENTS") + and not settings.TODO_ALLOW_FILE_ATTACHMENTS + ): + attachments_enabled = False + context = { "task": task, "comment_list": comment_list, "form": form, "merge_form": merge_form, "thedate": thedate, - "comment_classes": getattr(settings, 'TODO_COMMENT_CLASSES', []), + "comment_classes": getattr(settings, "TODO_COMMENT_CLASSES", []), + "attachments_enabled": attachments_enabled, } return render(request, "todo/task_detail.html", context)