add deletion + tests and processing status
Run Tests / Run Tests (push) Successful in 36s Details

This commit is contained in:
James Ravenscroft 2024-12-09 14:21:51 +00:00
parent 7176e8ce09
commit db7794dece
8 changed files with 171 additions and 22 deletions

View File

@ -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),
),
]

View File

@ -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"]

View File

@ -13,11 +13,27 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for document in documents %}
<div class="bg-white p-6 rounded-lg shadow border-2 border-dotted border-gray-300">
<img src="{% url 'document_thumbnail' pk=document.id %}" alt="{{ document.title }} thumbnail" class="w-full h-48 object-cover mb-4 rounded">
<h3 class="text-xl font-semibold mb-2">{{ document.title }}</h3>
<div
class="bg-white p-6 rounded-lg shadow border-2 border-dotted border-gray-300"
>
<img
src="{% url 'document_thumbnail' pk=document.id %}"
alt="{{ document.title }} thumbnail"
class="w-full h-48 object-cover mb-4 rounded"
/>
<div class="flex justify-between items-center mb-2">
<h3 class="text-xl font-semibold">{{ document.title }}</h3>
<span
class="px-2 py-1 text-xs font-semibold rounded-full {% if document.status == 'pending' %} bg-gray-200 text-gray-800 {% elif document.status == 'processing' %} bg-blue-200 text-blue-800 {% elif document.status == 'done' %} bg-green-200 text-green-800 {% elif document.status == 'error' %} bg-red-200 text-red-800 {% endif %}"
>
{{ document.status|title }}
</span>
</div>
<p class="text-gray-600 mb-4">
Analyzed on: {{ document.analysis_date }}
Created: {{ document.created_at }}
</p>
<p class="text-gray-600 mb-4">
Last Updated: {{ document.updated_at }}
</p>
<div class="flex justify-between items-center">
<a
@ -27,12 +43,19 @@
>
<a
href="{% url 'download_document' document.id %}"
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 transition duration-300"
>Download</a
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-green-600 transition duration-300"
>Export</a
>
<form
action="{% url 'delete_document' document.id %}"
method="post"
onsubmit="return confirm('Are you sure you want to delete this document?');"
>
<form action="{% url 'delete_document' document.id %}" method="post" onsubmit="return confirm('Are you sure you want to delete this document?');">
{% csrf_token %}
<button type="submit" class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition duration-300">
<button
type="submit"
class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition duration-300"
>
Delete
</button>
</form>
@ -47,15 +70,31 @@
</section>
<section class="text-center">
<h2 class="text-3xl font-bold text-gray-800 mb-6">Upload a New Document</h2>
<form action="{% url 'upload_document' %}" method="post" enctype="multipart/form-data">
<form
action="{% url 'upload_document' %}"
method="post"
enctype="multipart/form-data"
>
{% csrf_token %}
<div class="mb-4">
<input type="file" name="document" id="document" class="hidden" accept=".png,.jpg,.jpeg">
<label for="document" class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition duration-300 cursor-pointer inline-block">
<input
type="file"
name="document"
id="document"
class="hidden"
accept=".png,.jpg,.jpeg"
/>
<label
for="document"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition duration-300 cursor-pointer inline-block"
>
Choose File
</label>
</div>
<button type="submit" class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition duration-300">
<button
type="submit"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition duration-300"
>
Upload Document
</button>
</form>

View File

@ -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()
# ...

View File

@ -9,10 +9,13 @@ urlpatterns = [
path("documents/upload", views.upload_document, name="upload_document"),
path("documents/<str:pk>", views.view_document, name="view_document"),
path(
"documents/<str:pk>/thumbnail", views.document_thumbnail, name="document_thumbnail"
"documents/<str:pk>/thumbnail",
views.document_thumbnail,
name="document_thumbnail",
),
path(
"documents/<str:pk>/download", views.download_document, name="download_document"
),
path("documents/<str:pk>/delete", views.delete_document, name="delete_document"),
path("auth/register", views.register, name="register"),
]

View File

@ -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",
]

View File

@ -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
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")