diff --git a/src/articles/migrations/0034_replace_attachment_urls.py b/src/articles/migrations/0034_replace_attachment_urls.py
new file mode 100644
index 0000000..7328375
--- /dev/null
+++ b/src/articles/migrations/0034_replace_attachment_urls.py
@@ -0,0 +1,59 @@
+# Generated by Django 4.1.1 on 2022-10-01 06:55
+from django.apps.registry import Apps
+from django.db import migrations
+from django.db.backends.base.schema import BaseDatabaseSchemaEditor
+from django.urls import reverse
+
+
+def replace_with_wrapper_url(
+ apps: Apps, schema_editor: BaseDatabaseSchemaEditor
+) -> None:
+ Attachment = apps.get_model("attachments", "Attachment")
+ Article = apps.get_model("articles", "Article")
+ db_alias = schema_editor.connection.alias
+ attachments = Attachment.objects.using(db_alias).all()
+ modified = []
+ for article in Article.objects.using(db_alias).all():
+ for attachment in attachments:
+ article.content = article.content.replace(
+ attachment.original_file.url,
+ reverse("attachments:original", kwargs={"pk": attachment.pk}),
+ )
+ if attachment.processed_file:
+ article.content = article.content.replace(
+ attachment.processed_file.url,
+ reverse("attachments:processed", kwargs={"pk": attachment.pk}),
+ )
+ modified.append(article)
+ Article.objects.using(db_alias).bulk_update(modified, ["content"])
+
+
+def replace_with_file_url(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
+ Attachment = apps.get_model("attachments", "Attachment")
+ Article = apps.get_model("articles", "Article")
+ db_alias = schema_editor.connection.alias
+ attachments = Attachment.objects.using(db_alias).all()
+ modified = []
+ for article in Article.objects.using(db_alias).all():
+ for attachment in attachments:
+ article.content = article.content.replace(
+ reverse("attachments:original", kwargs={"pk": attachment.pk}),
+ attachment.original_file.url,
+ )
+ if attachment.processed_file:
+ article.content = article.content.replace(
+ reverse("attachments:processed", kwargs={"pk": attachment.pk}),
+ attachment.processed_file.url,
+ )
+ modified.append(article)
+ Article.objects.using(db_alias).bulk_update(modified, ["content"])
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("articles", "0033_alter_article_options"),
+ ("attachments", "0008_attachment_updated_at"),
+ ]
+
+ operations = [migrations.RunPython(replace_with_wrapper_url, replace_with_file_url)]
diff --git a/src/attachments/admin.py b/src/attachments/admin.py
index 14672a7..04d2f2a 100644
--- a/src/attachments/admin.py
+++ b/src/attachments/admin.py
@@ -43,18 +43,16 @@ class AttachmentAdmin(admin.ModelAdmin):
def processed_file_url(self, instance: Attachment) -> str:
if instance.processed_file:
return format_html(
- '{0} 📋',
- instance.processed_file.url,
- instance.processed_file.url,
+ '{0} 📋',
+ instance.processed_file_url,
)
return ""
def original_file_url(self, instance: Attachment) -> str:
if instance.original_file:
return format_html(
- '{0} 📋',
- instance.original_file.url,
- instance.original_file.url,
+ '{0} 📋',
+ instance.original_file_url,
)
return ""
diff --git a/src/attachments/migrations/0008_attachment_updated_at.py b/src/attachments/migrations/0008_attachment_updated_at.py
new file mode 100644
index 0000000..d5590a8
--- /dev/null
+++ b/src/attachments/migrations/0008_attachment_updated_at.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.1 on 2022-10-01 06:13
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("attachments", "0007_auto_20201201_1917"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="attachment",
+ name="updated_at",
+ field=models.DateTimeField(auto_now=True),
+ ),
+ ]
diff --git a/src/attachments/models.py b/src/attachments/models.py
index 24a8ff5..5505af0 100644
--- a/src/attachments/models.py
+++ b/src/attachments/models.py
@@ -11,6 +11,7 @@ from django.core.files import File
from django.core.handlers.wsgi import WSGIRequest
from django.db import models
from django.db.models.fields.files import FieldFile
+from django.urls import reverse
from PIL import Image
from articles.utils import build_full_absolute_url
@@ -35,6 +36,7 @@ class Attachment(models.Model):
original_file = AbsoluteUrlFileField()
processed_file = AbsoluteUrlFileField(blank=True, null=True)
open_graph_image = models.BooleanField(blank=True, default=False)
+ updated_at = models.DateTimeField(auto_now=True)
objects = AttachmentManager()
@@ -48,6 +50,16 @@ class Attachment(models.Model):
self.processed_file = None # type: ignore
self.save()
+ @property
+ def original_file_url(self) -> str:
+ return reverse("attachments:original", kwargs={"pk": self.pk})
+
+ @property
+ def processed_file_url(self) -> str | None:
+ if self.processed_file:
+ return reverse("attachments:processed", kwargs={"pk": self.pk})
+ return None
+
def save(self, *args: Any, **kwargs: Any) -> None:
super().save(*args, **kwargs)
diff --git a/src/attachments/tests/resources/docker-logo.png b/src/attachments/tests/resources/docker-logo.png
new file mode 100644
index 0000000..6eecb6b
Binary files /dev/null and b/src/attachments/tests/resources/docker-logo.png differ
diff --git a/src/attachments/tests/test_views.py b/src/attachments/tests/test_views.py
new file mode 100644
index 0000000..91fdde6
--- /dev/null
+++ b/src/attachments/tests/test_views.py
@@ -0,0 +1,45 @@
+from pathlib import Path
+
+import pytest
+from django.core.files import File
+from django.test.client import Client
+from django.urls import reverse
+
+from attachments.models import Attachment
+
+
+@pytest.mark.block_network()
+@pytest.fixture()
+def attachment(db: None) -> Attachment:
+ # This path manipulation is required to make the test run from this directory
+ # or from upper in the hierarchy (e.g.: settings.BASE_DIR)
+ original_path = Path(__file__).parent / "resources" / "image.png"
+ original_path = original_path.relative_to(Path.cwd())
+ processed_path = Path(__file__).parent / "resources" / "docker-logo.png"
+ processed_path = processed_path.relative_to(Path.cwd())
+ with original_path.open("rb") as of, processed_path.open("rb") as pf:
+ original_file = File(of)
+ processed_file = File(pf)
+ attachment = Attachment.objects.create(
+ description="Docker logo",
+ original_file=original_file,
+ processed_file=processed_file,
+ )
+ attachment.save()
+ return attachment
+
+
+def test_view_original(attachment: Attachment, client: Client) -> None:
+ url = reverse("attachments:original", kwargs={"pk": attachment.pk})
+ res = client.get(url)
+ assert res.status_code == 302
+ assert res.url == attachment.original_file.url
+ assert "Last-Modified" in res.headers
+
+
+def test_view_processed(attachment: Attachment, client: Client) -> None:
+ url = reverse("attachments:processed", kwargs={"pk": attachment.pk})
+ res = client.get(url)
+ assert res.status_code == 302
+ assert res.url == attachment.processed_file.url
+ assert "Last-Modified" in res.headers
diff --git a/src/attachments/urls.py b/src/attachments/urls.py
new file mode 100644
index 0000000..160b979
--- /dev/null
+++ b/src/attachments/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+
+from attachments import views
+
+app_name = "attachments"
+
+urlpatterns = [
+ path("/original/", views.get_original, name="original"),
+ path("/processed/", views.get_processed, name="processed"),
+]
diff --git a/src/attachments/views.py b/src/attachments/views.py
new file mode 100644
index 0000000..5d761f9
--- /dev/null
+++ b/src/attachments/views.py
@@ -0,0 +1,25 @@
+import datetime
+
+from django.core.handlers.wsgi import WSGIRequest
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+from django.views.decorators.http import last_modified
+
+from attachments.models import Attachment
+
+
+def get_updated_at(request: WSGIRequest, pk: int) -> datetime.datetime:
+ attachment = get_object_or_404(Attachment, pk=pk)
+ return attachment.updated_at
+
+
+@last_modified(get_updated_at)
+def get_original(request: WSGIRequest, pk: int) -> HttpResponse:
+ attachment = get_object_or_404(Attachment, pk=pk)
+ return HttpResponseRedirect(attachment.original_file.url)
+
+
+@last_modified(get_updated_at)
+def get_processed(request: WSGIRequest, pk: int) -> HttpResponse:
+ attachment = get_object_or_404(Attachment, pk=pk)
+ return HttpResponseRedirect(attachment.processed_file.url)
diff --git a/src/blog/urls.py b/src/blog/urls.py
index 72c2f90..3d6a2bc 100644
--- a/src/blog/urls.py
+++ b/src/blog/urls.py
@@ -32,6 +32,7 @@ urlpatterns = [
),
path("admin/", admin.site.urls),
path("", include("articles.urls")),
+ path("attachments/", include("attachments.urls")),
]
if settings.DEBUG: