Add cache to attachment urls
This commit is contained in:
parent
871d28f864
commit
d7a7e65805
9 changed files with 174 additions and 6 deletions
59
src/articles/migrations/0034_replace_attachment_urls.py
Normal file
59
src/articles/migrations/0034_replace_attachment_urls.py
Normal file
|
@ -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)]
|
|
@ -43,18 +43,16 @@ class AttachmentAdmin(admin.ModelAdmin):
|
|||
def processed_file_url(self, instance: Attachment) -> str:
|
||||
if instance.processed_file:
|
||||
return format_html(
|
||||
'{0} <a class="copy-button" data-to-copy="{1}" href="#">📋</a>',
|
||||
instance.processed_file.url,
|
||||
instance.processed_file.url,
|
||||
'{0} <a class="copy-button" data-to-copy="{0}" href="#">📋</a>',
|
||||
instance.processed_file_url,
|
||||
)
|
||||
return ""
|
||||
|
||||
def original_file_url(self, instance: Attachment) -> str:
|
||||
if instance.original_file:
|
||||
return format_html(
|
||||
'{0} <a class="copy-button" data-to-copy="{1}" href="#">📋</a>',
|
||||
instance.original_file.url,
|
||||
instance.original_file.url,
|
||||
'{0} <a class="copy-button" data-to-copy="{0}" href="#">📋</a>',
|
||||
instance.original_file_url,
|
||||
)
|
||||
return ""
|
||||
|
||||
|
|
18
src/attachments/migrations/0008_attachment_updated_at.py
Normal file
18
src/attachments/migrations/0008_attachment_updated_at.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
||||
|
|
BIN
src/attachments/tests/resources/docker-logo.png
Normal file
BIN
src/attachments/tests/resources/docker-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
45
src/attachments/tests/test_views.py
Normal file
45
src/attachments/tests/test_views.py
Normal file
|
@ -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
|
10
src/attachments/urls.py
Normal file
10
src/attachments/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from attachments import views
|
||||
|
||||
app_name = "attachments"
|
||||
|
||||
urlpatterns = [
|
||||
path("<int:pk>/original/", views.get_original, name="original"),
|
||||
path("<int:pk>/processed/", views.get_processed, name="processed"),
|
||||
]
|
25
src/attachments/views.py
Normal file
25
src/attachments/views.py
Normal file
|
@ -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)
|
|
@ -32,6 +32,7 @@ urlpatterns = [
|
|||
),
|
||||
path("admin/", admin.site.urls),
|
||||
path("", include("articles.urls")),
|
||||
path("attachments/", include("attachments.urls")),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
Reference in a new issue