diff --git a/todo/templates/todo/task_detail.html b/todo/templates/todo/task_detail.html index 1a3fd84..8eca361 100644 --- a/todo/templates/todo/task_detail.html +++ b/todo/templates/todo/task_detail.html @@ -131,6 +131,7 @@ Uploaded By Type + Remove @@ -140,6 +141,12 @@ {{ attachment.timestamp }} {{ attachment.added_by.get_full_name }} {{ attachment.extension.lower }} + +
+ {% csrf_token %} + +
+ {% endfor %} diff --git a/todo/urls.py b/todo/urls.py index 0573def..a539616 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -56,6 +56,11 @@ urlpatterns = [ 'task//', views.task_detail, name='task_detail'), + + path( + 'attachment/remove//', + views.remove_attachment, + name='remove_attachment'), ] if HAS_TASK_MERGE: diff --git a/todo/utils.py b/todo/utils.py index 0016a53..23c0e01 100644 --- a/todo/utils.py +++ b/todo/utils.py @@ -1,13 +1,14 @@ import email.utils -import time import logging +import os +import time from django.conf import settings from django.contrib.sites.models import Site from django.core import mail from django.template.loader import render_to_string -from todo.models import Comment, Task +from todo.models import Attachment, Comment, Task log = logging.getLogger(__name__) @@ -153,3 +154,19 @@ def toggle_task_completed(task_id: int) -> bool: except Task.DoesNotExist: log.info(f"Task {task_id} not found.") return False + + +def remove_attachment_file(attachment_id: int) -> bool: + """Delete an Attachment object and its corresponding file from the filesystem.""" + try: + attachment = Attachment.objects.get(id=attachment_id) + if attachment.file: + if os.path.isfile(attachment.file.path): + os.remove(attachment.file.path) + + attachment.delete() + return True + + except Attachment.DoesNotExist: + log.info(f"Attachment {attachment_id} not found.") + return False diff --git a/todo/views/__init__.py b/todo/views/__init__.py index 4c5777d..fb6c891 100644 --- a/todo/views/__init__.py +++ b/todo/views/__init__.py @@ -2,10 +2,11 @@ from todo.views.add_list import add_list # noqa: F401 from todo.views.del_list import del_list # noqa: F401 from todo.views.delete_task import delete_task # noqa: F401 from todo.views.external_add import external_add # noqa: F401 +from todo.views.import_csv import import_csv # noqa: F401 from todo.views.list_detail import list_detail # noqa: F401 from todo.views.list_lists import list_lists # noqa: F401 +from todo.views.remove_attachment import remove_attachment # 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 # noqa: F401 from todo.views.toggle_done import toggle_done # noqa: F401 -from todo.views.import_csv import import_csv # noqa: F401 diff --git a/todo/views/remove_attachment.py b/todo/views/remove_attachment.py new file mode 100644 index 0000000..75b235f --- /dev/null +++ b/todo/views/remove_attachment.py @@ -0,0 +1,41 @@ +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.http import HttpResponse +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse + +from todo.models import Attachment +from todo.utils import remove_attachment_file + + +@login_required +def remove_attachment(request, attachment_id: int) -> HttpResponse: + """Delete a previously posted attachment object and its corresponding file + from the filesystem, permissions allowing. + """ + + if request.method == "POST": + attachment = get_object_or_404(Attachment, pk=attachment_id) + + redir_url = reverse( + "todo:task_detail", + kwargs={"task_id": attachment.task.id}, + ) + + # Permissions + if not ( + attachment.task.task_list.group in request.user.groups.all() + or request.user.is_superuser + ): + raise PermissionDenied + + if remove_attachment_file(attachment.id): + messages.success(request, f"Attachment {attachment.id} removed.") + else: + messages.error(request, f"Sorry, there was a problem deleting attachment {attachment.id}.") + + return redirect(redir_url) + + else: + raise PermissionDenied