add deletion + tests and processing status
Run Tests / Run Tests (push) Successful in 36s
Details
Run Tests / Run Tests (push) Successful in 36s
Details
This commit is contained in:
parent
7176e8ce09
commit
db7794dece
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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,15 +43,22 @@
|
|||
>
|
||||
<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?');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition duration-300">
|
||||
<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"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
||||
# ...
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue