175 lines
7.1 KiB
Python
175 lines
7.1 KiB
Python
import io
|
|
import shutil
|
|
import tempfile
|
|
from datetime import timedelta
|
|
|
|
from PIL import Image
|
|
from django.core.management import call_command
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.test import RequestFactory
|
|
from django.test import TestCase, override_settings
|
|
from django.utils import timezone
|
|
|
|
from apps.blog.api.schemas import AuthorSchema, PostListSchema
|
|
from apps.blog.models import Post
|
|
from apps.events.api.schemas import EventGallerySchema, EventListSchema
|
|
from apps.events.models import Event
|
|
from apps.gallery.api.schemas import GallerySchema
|
|
from apps.gallery.models import Gallery
|
|
from apps.users.api.schemas import UserProfileSchema
|
|
from apps.users.models import User
|
|
from core.media import (
|
|
BLUR_VARIANT,
|
|
PREVIEW_VARIANT,
|
|
THUMBNAIL_VARIANT,
|
|
delete_image_derivatives,
|
|
derivative_name,
|
|
process_public_image,
|
|
)
|
|
|
|
|
|
class MediaProcessingTests(TestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.media_root = tempfile.mkdtemp()
|
|
self.addCleanup(lambda: shutil.rmtree(self.media_root, ignore_errors=True))
|
|
self.override = override_settings(MEDIA_ROOT=self.media_root, MEDIA_URL="/media/")
|
|
self.override.enable()
|
|
self.addCleanup(self.override.disable)
|
|
self.user = User.objects.create_user(
|
|
username="media-user",
|
|
email="media@example.com",
|
|
password="TestPass123!",
|
|
)
|
|
self.factory = RequestFactory()
|
|
|
|
def _upload(self, name: str, *, mode: str = "RGB", fmt: str = "JPEG") -> SimpleUploadedFile:
|
|
buffer = io.BytesIO()
|
|
Image.new(mode, (2400, 1800), color=(20, 80, 200)).save(buffer, format=fmt)
|
|
buffer.seek(0)
|
|
return SimpleUploadedFile(name, buffer.read(), content_type=f"image/{fmt.lower()}")
|
|
|
|
def test_process_public_image_generates_derivatives_for_jpeg(self):
|
|
gallery = Gallery.objects.create(
|
|
title="JPEG image",
|
|
uploaded_by=self.user,
|
|
image=self._upload("photo.jpg", fmt="JPEG"),
|
|
)
|
|
|
|
result = process_public_image(gallery.image, "gallery", force=True)
|
|
|
|
self.assertTrue(result.processed)
|
|
self.assertTrue(gallery.image.storage.exists(derivative_name(gallery.image.name, THUMBNAIL_VARIANT)))
|
|
self.assertTrue(gallery.image.storage.exists(derivative_name(gallery.image.name, PREVIEW_VARIANT)))
|
|
self.assertTrue(gallery.image.storage.exists(derivative_name(gallery.image.name, BLUR_VARIANT)))
|
|
|
|
def test_process_public_image_generates_derivatives_for_png(self):
|
|
gallery = Gallery.objects.create(
|
|
title="PNG image",
|
|
uploaded_by=self.user,
|
|
image=self._upload("photo.png", mode="RGBA", fmt="PNG"),
|
|
)
|
|
|
|
result = process_public_image(gallery.image, "gallery", force=True)
|
|
|
|
self.assertEqual(result.width, 2400)
|
|
self.assertEqual(result.height, 1800)
|
|
self.assertTrue(gallery.image.storage.exists(derivative_name(gallery.image.name, PREVIEW_VARIANT)))
|
|
|
|
def test_delete_image_derivatives_removes_sidecars(self):
|
|
gallery = Gallery.objects.create(
|
|
title="Delete derivatives",
|
|
uploaded_by=self.user,
|
|
image=self._upload("cleanup.jpg", fmt="JPEG"),
|
|
)
|
|
process_public_image(gallery.image, "gallery", force=True)
|
|
|
|
delete_image_derivatives(gallery.image, "gallery")
|
|
|
|
self.assertFalse(gallery.image.storage.exists(derivative_name(gallery.image.name, THUMBNAIL_VARIANT)))
|
|
self.assertFalse(gallery.image.storage.exists(derivative_name(gallery.image.name, PREVIEW_VARIANT)))
|
|
self.assertFalse(gallery.image.storage.exists(derivative_name(gallery.image.name, BLUR_VARIANT)))
|
|
|
|
def test_replacing_image_removes_previous_sidecars(self):
|
|
gallery = Gallery.objects.create(
|
|
title="Replace derivatives",
|
|
uploaded_by=self.user,
|
|
image=self._upload("replace-first.jpg", fmt="JPEG"),
|
|
)
|
|
first_name = gallery.image.name
|
|
|
|
gallery.image = self._upload("replace-second.jpg", fmt="JPEG")
|
|
gallery.save()
|
|
|
|
self.assertFalse(gallery.image.storage.exists(derivative_name(first_name, THUMBNAIL_VARIANT)))
|
|
self.assertFalse(gallery.image.storage.exists(derivative_name(first_name, PREVIEW_VARIANT)))
|
|
self.assertFalse(gallery.image.storage.exists(derivative_name(first_name, BLUR_VARIANT)))
|
|
|
|
def test_backfill_command_is_idempotent(self):
|
|
gallery = Gallery(title="Backfill image", uploaded_by=self.user)
|
|
gallery._defer_image_processing = True
|
|
gallery.image = self._upload("backfill.jpg", fmt="JPEG")
|
|
gallery.save()
|
|
|
|
first = io.StringIO()
|
|
second = io.StringIO()
|
|
call_command("backfill_media_derivatives", stdout=first)
|
|
call_command("backfill_media_derivatives", stdout=second)
|
|
|
|
self.assertIn("processed=1", first.getvalue())
|
|
self.assertIn("skipped=1", second.getvalue())
|
|
|
|
def test_schema_resolvers_expose_derivative_urls(self):
|
|
self.user.profile_picture.save("profile.jpg", self._upload("profile.jpg", fmt="JPEG"))
|
|
|
|
gallery = Gallery.objects.create(
|
|
title="Schema image",
|
|
uploaded_by=self.user,
|
|
image=self._upload("schema-gallery.jpg", fmt="JPEG"),
|
|
)
|
|
event = Event.objects.create(
|
|
title="Schema event",
|
|
description="Event description",
|
|
start_time=timezone.now(),
|
|
end_time=timezone.now() + timedelta(hours=2),
|
|
featured_image=self._upload("schema-event.jpg", fmt="JPEG"),
|
|
)
|
|
post = Post.objects.create(
|
|
title="Schema post",
|
|
content="Post body",
|
|
author=self.user,
|
|
featured_image=self._upload("schema-post.jpg", fmt="JPEG"),
|
|
status=Post.StatusChoices.PUBLISHED,
|
|
)
|
|
|
|
context = {"request": self.factory.get("/")}
|
|
|
|
self.assertIn(
|
|
derivative_name(event.featured_image.name, THUMBNAIL_VARIANT),
|
|
EventListSchema.resolve_absolute_featured_image_thumbnail_url(event, context),
|
|
)
|
|
self.assertIn(
|
|
derivative_name(event.featured_image.name, PREVIEW_VARIANT),
|
|
EventListSchema.resolve_absolute_featured_image_preview_url(event, context),
|
|
)
|
|
self.assertIn(
|
|
derivative_name(gallery.image.name, PREVIEW_VARIANT),
|
|
EventGallerySchema.resolve_absolute_image_preview_url(gallery, context),
|
|
)
|
|
self.assertIn(
|
|
derivative_name(gallery.image.name, BLUR_VARIANT),
|
|
GallerySchema.resolve_absolute_image_blur_url(gallery, context),
|
|
)
|
|
self.assertIn(
|
|
derivative_name(post.featured_image.name, THUMBNAIL_VARIANT),
|
|
PostListSchema.resolve_absolute_featured_image_thumbnail_url(post, context),
|
|
)
|
|
self.assertIn(
|
|
derivative_name(self.user.profile_picture.name, THUMBNAIL_VARIANT),
|
|
AuthorSchema.resolve_profile_picture_thumbnail_url(self.user, context),
|
|
)
|
|
self.assertIn(
|
|
derivative_name(self.user.profile_picture.name, PREVIEW_VARIANT),
|
|
UserProfileSchema.resolve_profile_picture_preview_url(self.user, context),
|
|
)
|