From db7794deceef10df4eb0cd4da2f5d2bdd272bc5d Mon Sep 17 00:00:00 2001 From: James Ravenscroft Date: Mon, 9 Dec 2024 14:21:51 +0000 Subject: [PATCH] add deletion + tests and processing status --- .../webui/migrations/0004_imagememo_status.py | 18 +++++ penparse/webui/models.py | 9 +++ penparse/webui/templates/dashboard.html | 69 +++++++++++++---- penparse/webui/test/test_delete_doc_view.py | 0 penparse/webui/test/test_delete_document.py | 74 +++++++++++++++++++ penparse/webui/urls.py | 5 +- penparse/webui/views/__init__.py | 2 + penparse/webui/views/delete.py | 16 ++-- 8 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 penparse/webui/migrations/0004_imagememo_status.py create mode 100644 penparse/webui/test/test_delete_doc_view.py create mode 100644 penparse/webui/test/test_delete_document.py diff --git a/penparse/webui/migrations/0004_imagememo_status.py b/penparse/webui/migrations/0004_imagememo_status.py new file mode 100644 index 0000000..b101864 --- /dev/null +++ b/penparse/webui/migrations/0004_imagememo_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-12-09 14:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('webui', '0003_imagememo_image_mimetype_alter_imagememo_image'), + ] + + operations = [ + migrations.AddField( + model_name='imagememo', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('done', 'Done'), ('error', 'Error')], default='pending', max_length=10), + ), + ] diff --git a/penparse/webui/models.py b/penparse/webui/models.py index 0b5f4b2..750e42c 100644 --- a/penparse/webui/models.py +++ b/penparse/webui/models.py @@ -38,6 +38,12 @@ class UserManager(BaseUserManager): return self._create_user(email, password, **extra_fields) +class MemoStatus(models.TextChoices): + Pending = "pending" + Processing = "processing" + Done = "done" + Error = "error" + class ImageMemo(models.Model): """Model definition for ImageMemo.""" @@ -54,6 +60,9 @@ class ImageMemo(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + status = models.CharField(max_length=10, choices=MemoStatus.choices, default=MemoStatus.Pending) + + class Meta: ordering = ["-created_at"] diff --git a/penparse/webui/templates/dashboard.html b/penparse/webui/templates/dashboard.html index eb0aeef..e1f2df7 100644 --- a/penparse/webui/templates/dashboard.html +++ b/penparse/webui/templates/dashboard.html @@ -13,11 +13,27 @@
{% for document in documents %} -
- {{ document.title }} thumbnail -

{{ document.title }}

+
+ {{ document.title }} thumbnail +
+

{{ document.title }}

+ + {{ document.status|title }} + +

- Analyzed on: {{ document.analysis_date }} + Created: {{ document.created_at }} +

+

+ Last Updated: {{ document.updated_at }}

DownloadExport -
- {% csrf_token %} - -
+ +
{% empty %} @@ -47,15 +70,31 @@

Upload a New Document

-
+ {% csrf_token %}
- -
-
diff --git a/penparse/webui/test/test_delete_doc_view.py b/penparse/webui/test/test_delete_doc_view.py new file mode 100644 index 0000000..e69de29 diff --git a/penparse/webui/test/test_delete_document.py b/penparse/webui/test/test_delete_document.py new file mode 100644 index 0000000..850238c --- /dev/null +++ b/penparse/webui/test/test_delete_document.py @@ -0,0 +1,74 @@ +import pytest +import uuid +from django.core.files.uploadedfile import SimpleUploadedFile +from django.urls import reverse +from django.core.files.storage import default_storage + +from ..models import ImageMemo, User + + +@pytest.mark.django_db +class TestDeleteDocument: + + @pytest.fixture + def setup_users(self): + self.user1 = User.objects.create_user( + email="user1@test.com", password="password1" + ) + self.user2 = User.objects.create_user( + email="user2@test.com", password="password2" + ) + + @pytest.fixture + def setup_document(self, setup_users): + img_content = b"fake image content" + test_image = SimpleUploadedFile( + name="test_image.jpg", content=img_content, content_type="image/jpeg" + ) + self.image_memo = ImageMemo.objects.create( + author=self.user1, + image=test_image, + ) + + def test_delete_document_success(self, client, setup_document): + client.force_login(self.user1) + url = reverse("delete_document", kwargs={"pk": str(self.image_memo.id)}) + response = client.post(url) + + assert response.status_code == 302 + assert response.url == reverse("dashboard") + assert not ImageMemo.objects.filter(id=self.image_memo.id).exists() + assert not default_storage.exists(self.image_memo.image.name) + + def test_delete_nonexistent_document(self, client, setup_users): + client.force_login(self.user1) + url = reverse( + "delete_document", kwargs={"pk": "facade00-0000-4000-a000-000000000000"} + ) + response = client.post(url) + + assert response.status_code == 404 + assert response.content == b"Document not found" + + def test_delete_other_users_document(self, client, setup_document): + client.force_login(self.user2) + url = reverse("delete_document", kwargs={"pk": str(self.image_memo.id)}) + response = client.post(url) + + assert response.status_code == 404 + assert response.content == b"Document not found" + assert ImageMemo.objects.filter(id=self.image_memo.id).exists() + + def test_delete_document_missing_file(self, client, setup_document): + # Remove the file manually + default_storage.delete(self.image_memo.image.name) + + client.force_login(self.user1) + url = reverse("delete_document", kwargs={"pk": str(self.image_memo.id)}) + response = client.post(url) + + assert response.status_code == 302 + assert response.url == reverse("dashboard") + assert not ImageMemo.objects.filter(id=self.image_memo.id).exists() + + # ... diff --git a/penparse/webui/urls.py b/penparse/webui/urls.py index 29d02a7..cbe6e83 100644 --- a/penparse/webui/urls.py +++ b/penparse/webui/urls.py @@ -9,10 +9,13 @@ urlpatterns = [ path("documents/upload", views.upload_document, name="upload_document"), path("documents/", views.view_document, name="view_document"), path( - "documents//thumbnail", views.document_thumbnail, name="document_thumbnail" + "documents//thumbnail", + views.document_thumbnail, + name="document_thumbnail", ), path( "documents//download", views.download_document, name="download_document" ), + path("documents//delete", views.delete_document, name="delete_document"), path("auth/register", views.register, name="register"), ] diff --git a/penparse/webui/views/__init__.py b/penparse/webui/views/__init__.py index 2facf45..3c6d87f 100644 --- a/penparse/webui/views/__init__.py +++ b/penparse/webui/views/__init__.py @@ -16,6 +16,7 @@ logger = logging.getLogger(__name__) from .thumbnail import document_thumbnail from .register import register from .upload import upload_document +from .delete import delete_document def index(request): @@ -32,6 +33,7 @@ __all__ = [ "view_document", "download_document", "upload_document", + "delete_document", ] diff --git a/penparse/webui/views/delete.py b/penparse/webui/views/delete.py index f405024..ef98d8e 100644 --- a/penparse/webui/views/delete.py +++ b/penparse/webui/views/delete.py @@ -1,5 +1,4 @@ -import logging -import os +from loguru import logger from django.contrib import messages from django.shortcuts import redirect @@ -12,9 +11,6 @@ from django.http import HttpRequest, HttpResponse from django.contrib.auth.decorators import login_required -logger = logging.getLogger(__name__) - - @login_required def delete_document(request: HttpRequest, pk: str): @@ -26,6 +22,14 @@ def delete_document(request: HttpRequest, pk: str): return HttpResponse(content="Document not found", status=404) # delete file from storage - default_storage.delete(document.image.name) + if default_storage.exists(document.image.name): + default_storage.delete(document.image.name) + else: + logger.warning( + "File {document.image.name} associated with doc {document.id} did not exist when we tried to delete it" + ) + + # delete document from database + document.delete() return redirect("dashboard")