test thumbnail management of AlbumEditForm

This commit is contained in:
imperosol
2026-04-26 17:10:39 +02:00
parent d621911849
commit 43ecc509a3
2 changed files with 219 additions and 136 deletions
+218
View File
@@ -0,0 +1,218 @@
import random
import string
from pathlib import Path
from typing import Callable
from unittest.mock import patch
import pytest
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client
from django.urls import reverse
from django.utils.datastructures import MultiValueDict
from django.utils.timezone import localdate
from model_bakery import baker
from PIL import Image
from pytest_django.asserts import assertInHTML, assertRedirects
from core.baker_recipes import subscriber_user
from core.models import Group, User
from core.utils import RED_PIXEL_PNG
from sas.baker_recipes import picture_recipe
from sas.forms import AlbumEditForm
from sas.models import Album
@pytest.fixture
def sas_root(db) -> Album:
return Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID)
@pytest.fixture
def album(db) -> Album:
name = "".join(
random.choice(string.ascii_letters) for _ in range(Album.NAME_MAX_LENGTH)
)
return baker.make(
Album, name=name, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True
)
@pytest.mark.parametrize("user", [None, lambda: baker.make(User), subscriber_user.make])
@pytest.mark.django_db
def test_permission_denied(
client: Client, album: Album, user: Callable[[], User] | None
):
if user:
client.force_login(user())
url = reverse("sas:album_edit", kwargs={"album_id": album.pk})
for method in client.get, client.post:
assert method(url).status_code == 403
@pytest.mark.django_db
def test_sas_root_read_only(client: Client, sas_root: Album):
moderator = baker.make(
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
)
client.force_login(moderator)
url = reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
for method in client.get, client.post:
assert method(url).status_code == 404
@pytest.mark.parametrize(
("excluded", "is_valid"),
[
("name", False),
("date", False),
("file", True),
("parent", False),
("edit_groups", True),
("recursive", True),
],
)
@pytest.mark.django_db
def test_form_required(album: Album, excluded: str, is_valid: bool): # noqa: FBT001
data = {
"name": album.name,
"parent": baker.make(Album, parent=album.parent, is_moderated=True).pk,
"date": localdate(),
"file": "/random/path",
"edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID],
"recursive": False,
}
del data[excluded]
assert AlbumEditForm(data=data).is_valid() == is_valid
@pytest.mark.django_db
def test_form_album_name(album: Album):
data = {
"name": "a" * Album.NAME_MAX_LENGTH,
"parent": album.pk,
"date": localdate(),
}
assert AlbumEditForm(data=data).is_valid()
data["name"] = "a" * (Album.NAME_MAX_LENGTH + 1)
assert not AlbumEditForm(data=data).is_valid()
@pytest.mark.django_db
def test_update_recursive_parent(client: Client, album: Album):
client.force_login(baker.make(User, is_superuser=True))
payload = {"name": album.name, "parent": album.pk, "date": localdate()}
response = client.post(
reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
)
assertInHTML("<li>Boucle dans l'arborescence des dossiers</li>", response.text)
assert response.status_code == 200
@pytest.mark.parametrize(
"user",
[
lambda: baker.make(User, is_superuser=True),
lambda: baker.make(
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
),
],
)
@pytest.mark.parametrize(
"parent",
[
lambda: baker.make(
Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True
),
lambda: Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID),
],
)
@pytest.mark.django_db
def test_update(
client: Client,
album: Album,
sas_root: Album,
user: Callable[[], User],
parent: Callable[[], Album],
):
client.force_login(user())
expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk})
payload = {
"name": "foo",
"parent": parent().id,
"date": localdate(),
"recursive": False,
}
response = client.post(
reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
)
assertRedirects(response, expected_redirect)
album.refresh_from_db()
assert album.name == "foo"
assert album.parent.id == payload["parent"]
assert localdate(album.date) == localdate()
class TestAlbumThumbnail:
@pytest.fixture
def files(self):
return MultiValueDict(
{"file": [SimpleUploadedFile(name="foo.png", content=RED_PIXEL_PNG)]}
)
def test_thumbnail_resized(self, album, files):
"""Test that album thumbnails are resized to the correct dimensions."""
form = AlbumEditForm(
data={"name": album.name, "date": localdate(), "parent": album.parent.id},
files=files,
instance=album,
)
assert form.is_valid()
form.save()
album.refresh_from_db()
assert album.file.name == f"SAS/{album.name}/thumb.webp"
assert Image.open(album.file).size == (200, 200)
def test_thumbnail_removed(self, album):
"""Test the case where the user checks the box to remove the thumbnail"""
album.file = ContentFile(name="foo.png", content=RED_PIXEL_PNG)
album.save()
previous_filename = album.file.name
form = AlbumEditForm(
data={
"name": "foo",
"date": localdate(),
"parent": album.parent.id,
"file-clear": True,
},
instance=album,
)
# as there is now no picture, a thumbnail should be generated
with patch.object(Album, "generate_thumbnail") as mock:
assert form.is_valid()
form.save()
album.refresh_from_db()
assert album.file.storage.exists(album.file.name)
assert not album.file.storage.exists(previous_filename)
mock.assert_called_once()
def test_generate_thumbnail(self, album):
"""Test that if no image is given and the album has pictures,
the thumbnail is automatically generated.
"""
picture = picture_recipe.make(
parent=album, thumbnail=ContentFile(name="foo.png", content=RED_PIXEL_PNG)
)
form = AlbumEditForm(
data={"name": "foo", "date": localdate(), "parent": album.parent.id},
instance=album,
)
assert form.is_valid()
form.save()
album.refresh_from_db()
assert Path(album.file.name) == Path("SAS/foo/thumb.webp")
assert album.file.storage.exists(album.file.name)
assert Image.open(album.file) == Image.open(picture.thumbnail)
+1 -136
View File
@@ -20,14 +20,12 @@ from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import localdate
from model_bakery import baker from model_bakery import baker
from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects
from core.baker_recipes import old_subscriber_user, subscriber_user from core.baker_recipes import old_subscriber_user, subscriber_user
from core.models import Group, User from core.models import Group, User
from sas.baker_recipes import picture_recipe from sas.baker_recipes import picture_recipe
from sas.forms import AlbumEditForm
from sas.models import Album, Picture from sas.models import Album, Picture
# Create your tests here. # Create your tests here.
@@ -97,6 +95,7 @@ def test_main_page_content_anonymous(client: Client):
@pytest.mark.django_db @pytest.mark.django_db
def test_album_access_non_subscriber(client: Client): def test_album_access_non_subscriber(client: Client):
"""Test that non-subscribers can only access albums where they are identified.""" """Test that non-subscribers can only access albums where they are identified."""
cache.clear()
album = baker.make(Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID) album = baker.make(Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID)
user = baker.make(User) user = baker.make(User)
client.force_login(user) client.force_login(user)
@@ -163,140 +162,6 @@ class TestAlbumUpload:
assert not album.children.exists() assert not album.children.exists()
@pytest.mark.django_db
class TestAlbumEdit:
@pytest.fixture
def sas_root(self) -> Album:
return Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID)
@pytest.fixture
def album(self) -> Album:
return baker.make(
Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True
)
@pytest.mark.parametrize(
"user",
[None, lambda: baker.make(User), subscriber_user.make],
)
def test_permission_denied(
self,
client: Client,
album: Album,
user: Callable[[], User] | None,
):
if user:
client.force_login(user())
url = reverse("sas:album_edit", kwargs={"album_id": album.pk})
response = client.get(url)
assert response.status_code == 403
response = client.post(url)
assert response.status_code == 403
def test_sas_root_read_only(self, client: Client, sas_root: Album):
moderator = baker.make(
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
)
client.force_login(moderator)
url = reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
response = client.get(url)
assert response.status_code == 404
response = client.post(url)
assert response.status_code == 404
@pytest.mark.parametrize(
("excluded", "is_valid"),
[
("name", False),
("date", False),
("file", True),
("parent", False),
("edit_groups", True),
("recursive", True),
],
)
def test_form_required(self, album: Album, excluded: str, is_valid: bool): # noqa: FBT001
data = {
"name": album.name[: Album.NAME_MAX_LENGTH],
"parent": baker.make(Album, parent=album.parent, is_moderated=True).pk,
"date": localdate().strftime("%Y-%m-%d"),
"file": "/random/path",
"edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID],
"recursive": False,
}
del data[excluded]
assert AlbumEditForm(data=data).is_valid() == is_valid
def test_form_album_name(self, album: Album):
data = {
"name": album.name[: Album.NAME_MAX_LENGTH],
"parent": album.pk,
"date": localdate().strftime("%Y-%m-%d"),
}
assert AlbumEditForm(data=data).is_valid()
data["name"] = album.name[: Album.NAME_MAX_LENGTH + 1]
assert not AlbumEditForm(data=data).is_valid()
def test_update_recursive_parent(self, client: Client, album: Album):
client.force_login(baker.make(User, is_superuser=True))
payload = {
"name": album.name[: Album.NAME_MAX_LENGTH],
"parent": album.pk,
"date": localdate().strftime("%Y-%m-%d"),
}
response = client.post(
reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
)
assertInHTML("<li>Boucle dans l'arborescence des dossiers</li>", response.text)
assert response.status_code == 200
@pytest.mark.parametrize(
"user",
[
lambda: baker.make(User, is_superuser=True),
lambda: baker.make(
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
),
],
)
@pytest.mark.parametrize(
"parent",
[
lambda: baker.make(
Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True
),
lambda: Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID),
],
)
def test_update(
self,
client: Client,
album: Album,
sas_root: Album,
user: Callable[[], User],
parent: Callable[[], Album],
):
client.force_login(user())
expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk})
payload = {
"name": album.name[: Album.NAME_MAX_LENGTH],
"parent": parent().id,
"date": localdate().strftime("%Y-%m-%d"),
"recursive": False,
}
response = client.post(
reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
)
assertRedirects(response, expected_redirect)
album.refresh_from_db()
assert album.name == payload["name"]
assert album.parent.id == payload["parent"]
assert localdate(album.date) == localdate()
class TestSasModeration(TestCase): class TestSasModeration(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):