Add "description" field to AbstractImage

- Baseline support for upcoming alt text capabilities
pull/12317/head
Chiemezuo 2024-03-24 13:13:36 +01:00 zatwierdzone przez LB (Ben Johnston)
rodzic 897b0415cd
commit 5cc22f3f75
9 zmienionych plików z 194 dodań i 98 usunięć

Wyświetl plik

@ -18,6 +18,7 @@ Changelog
* Consistently use `capfirst` for title-casing model verbose names (Sébastien Corbin)
* Add support for Python 3.13 (Matt Westcott)
* Fire `copy_for_translation_done` signal when copying translatable models as well as pages (Coen van der Kamp)
* Add support for an image `description` field across all images, to better support accessible image descriptions (Chiemezuo Akujobi)
* Fix: Prevent page type business rules from blocking reordering of pages (Andy Babic, Sage Abdullah)
* Fix: Improve layout of object permissions table (Sage Abdullah)
* Fix: Fix typo in aria-label attribute of page explorer navigation link (Sébastien Corbin)

Wyświetl plik

@ -33,6 +33,7 @@ This release adds formal support for Django 5.1.
* Deprecate the `WAGTAIL_AUTO_UPDATE_PREVIEW` setting, use `WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL = 0` instead (Sage Abdullah)
* Consistently use `capfirst` for title-casing model verbose names (Sébastien Corbin)
* Fire `copy_for_translation_done` signal when copying translatable models as well as pages (Coen van der Kamp)
* Add support for an image `description` field across all images, to better support accessible image descriptions (Chiemezuo Akujobi)
### Bug fixes

Wyświetl plik

@ -0,0 +1,20 @@
# Generated by Django 4.2.13 on 2024-08-05 22:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("wagtailimages", "0026_delete_uploadedimage"),
]
operations = [
migrations.AddField(
model_name="image",
name="description",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="description"
),
),
]

Wyświetl plik

@ -262,6 +262,12 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
width_field="width",
height_field="height",
)
description = models.CharField(
blank=True,
max_length=255,
verbose_name=_("description"),
default="",
)
width = models.IntegerField(verbose_name=_("width"), editable=False)
height = models.IntegerField(verbose_name=_("height"), editable=False)
created_at = models.DateTimeField(
@ -823,9 +829,9 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
@property
def default_alt_text(self):
# by default the alt text field (used in rich text insertion) is populated
# from the title. Subclasses might provide a separate alt field, and
# override this
return self.title
# from the description. In the absence of that, it is populated from the title.
# Subclasses might provide a separate alt field, and override this
return getattr(self, "description", None) or self.title
def is_editable_by_user(self, user):
from wagtail.images.permissions import permission_policy
@ -840,6 +846,7 @@ class Image(AbstractImage):
admin_form_fields = (
"title",
"file",
"description",
"collection",
"tags",
"focal_point_x",

Wyświetl plik

@ -64,6 +64,7 @@
</div>
</div>
<div class="right col9">
<p class="error-message">{% trans "Please provide an image description to comply with best practices for accessibility." %}</p>
<p class="status-msg success">{% trans "Upload successful. Please update this image with a more appropriate title, if necessary. You may also delete the image completely if the upload wasn't required." %}</p>
<p class="status-msg warning">
{% trans "Upload successful. However, your new image seems to be a duplicate of this existing image. You may delete it if it wasn't required." %}

Wyświetl plik

@ -145,6 +145,23 @@ class TestImage(TestCase):
)
self.assertIsNone(image.get_suggested_focal_point())
def test_default_with_description(self):
# Primary default should be description
image = Image.objects.create(
title="Test Image",
description="This is a test description",
file=get_test_image_file(),
)
self.assertEqual(image.default_alt_text, image.description)
def test_default_without_description(self):
# Secondary default should be title
image = Image.objects.create(
title="Test Image",
file=get_test_image_file(),
)
self.assertEqual(image.default_alt_text, image.title)
class TestImageQuerySet(TransactionTestCase):
fixtures = ["test_empty.json"]

Wyświetl plik

@ -1,95 +1,99 @@
from django.db import transaction
from django.test import TestCase, TransactionTestCase, override_settings
from wagtail.images import get_image_model, signal_handlers
from wagtail.images.tests.utils import get_test_image_file
from wagtail.models import Collection
from .utils import Image
class TestFilesDeletedForDefaultModels(TransactionTestCase):
"""
Because we expect file deletion to only happen once a transaction is
successfully committed, we must run these tests using TransactionTestCase
per the following documentation:
Django's TestCase class wraps each test in a transaction and rolls back that
transaction after each test, in order to provide test isolation. This means
that no transaction is ever actually committed, thus your on_commit()
callbacks will never be run. If you need to test the results of an
on_commit() callback, use a TransactionTestCase instead.
https://docs.djangoproject.com/en/1.10/topics/db/transactions/#use-in-tests
"""
def setUp(self):
# Required to create root collection because the TransactionTestCase
# does not make initial data loaded in migrations available and
# serialized_rollback=True causes other problems in the test suite.
# ref: https://docs.djangoproject.com/en/1.10/topics/testing/overview/#rollback-emulation
Collection.objects.get_or_create(
name="Root",
path="0001",
depth=1,
numchild=0,
)
def test_image_file_deleted_oncommit(self):
with transaction.atomic():
image = get_image_model().objects.create(
title="Test Image", file=get_test_image_file()
)
filename = image.file.name
self.assertTrue(image.file.storage.exists(filename))
image.delete()
self.assertTrue(image.file.storage.exists(filename))
self.assertFalse(image.file.storage.exists(filename))
def test_rendition_file_deleted_oncommit(self):
with transaction.atomic():
image = get_image_model().objects.create(
title="Test Image", file=get_test_image_file()
)
rendition = image.get_rendition("original")
filename = rendition.file.name
self.assertTrue(rendition.file.storage.exists(filename))
rendition.delete()
self.assertTrue(rendition.file.storage.exists(filename))
self.assertFalse(rendition.file.storage.exists(filename))
@override_settings(WAGTAILIMAGES_IMAGE_MODEL="tests.CustomImage")
class TestFilesDeletedForCustomModels(TestFilesDeletedForDefaultModels):
def setUp(self):
# Required to create root collection because the TransactionTestCase
# does not make initial data loaded in migrations available and
# serialized_rollback=True causes other problems in the test suite.
# ref: https://docs.djangoproject.com/en/1.10/topics/testing/overview/#rollback-emulation
Collection.objects.get_or_create(
name="Root",
path="0001",
depth=1,
numchild=0,
)
#: Sadly signal receivers only get connected when starting django.
#: We will re-attach them here to mimic the django startup behaviour
#: and get the signals connected to our custom model..
signal_handlers.register_signal_handlers()
def test_image_model(self):
cls = get_image_model()
self.assertEqual(f"{cls._meta.app_label}.{cls.__name__}", "tests.CustomImage")
@override_settings(WAGTAILIMAGES_FEATURE_DETECTION_ENABLED=True)
class TestRawForPreSaveImageFeatureDetection(TestCase):
fixtures = ["test.json"]
# just to test the file is from a fixture doesn't actually exists.
# raw check in pre_save_image_feature_detection skips on the provided condition of this test
# hence avoiding an error
def test_image_does_not_exist(self):
bad_image = Image.objects.get(pk=1)
self.assertFalse(bad_image.file.storage.exists(bad_image.file.name))
from django.db import transaction
from django.test import TestCase, TransactionTestCase, override_settings
from wagtail.images import get_image_model, signal_handlers
from wagtail.images.tests.utils import get_test_image_file
from wagtail.models import Collection
from .utils import Image
class TestFilesDeletedForDefaultModels(TransactionTestCase):
"""
Because we expect file deletion to only happen once a transaction is
successfully committed, we must run these tests using TransactionTestCase
per the following documentation:
Django's TestCase class wraps each test in a transaction and rolls back that
transaction after each test, in order to provide test isolation. This means
that no transaction is ever actually committed, thus your on_commit()
callbacks will never be run. If you need to test the results of an
on_commit() callback, use a TransactionTestCase instead.
https://docs.djangoproject.com/en/1.10/topics/db/transactions/#use-in-tests
"""
def setUp(self):
# Required to create root collection because the TransactionTestCase
# does not make initial data loaded in migrations available and
# serialized_rollback=True causes other problems in the test suite.
# ref: https://docs.djangoproject.com/en/1.10/topics/testing/overview/#rollback-emulation
Collection.objects.get_or_create(
name="Root",
path="0001",
depth=1,
numchild=0,
)
def test_image_file_deleted_oncommit(self):
with transaction.atomic():
image = get_image_model().objects.create(
title="Test Image",
description="A test description",
file=get_test_image_file(),
)
filename = image.file.name
self.assertTrue(image.file.storage.exists(filename))
image.delete()
self.assertTrue(image.file.storage.exists(filename))
self.assertFalse(image.file.storage.exists(filename))
def test_rendition_file_deleted_oncommit(self):
with transaction.atomic():
image = get_image_model().objects.create(
title="Test Image",
description="A test description",
file=get_test_image_file(),
)
rendition = image.get_rendition("original")
filename = rendition.file.name
self.assertTrue(rendition.file.storage.exists(filename))
rendition.delete()
self.assertTrue(rendition.file.storage.exists(filename))
self.assertFalse(rendition.file.storage.exists(filename))
@override_settings(WAGTAILIMAGES_IMAGE_MODEL="tests.CustomImage")
class TestFilesDeletedForCustomModels(TestFilesDeletedForDefaultModels):
def setUp(self):
# Required to create root collection because the TransactionTestCase
# does not make initial data loaded in migrations available and
# serialized_rollback=True causes other problems in the test suite.
# ref: https://docs.djangoproject.com/en/1.10/topics/testing/overview/#rollback-emulation
Collection.objects.get_or_create(
name="Root",
path="0001",
depth=1,
numchild=0,
)
#: Sadly signal receivers only get connected when starting django.
#: We will re-attach them here to mimic the django startup behaviour
#: and get the signals connected to our custom model..
signal_handlers.register_signal_handlers()
def test_image_model(self):
cls = get_image_model()
self.assertEqual(f"{cls._meta.app_label}.{cls.__name__}", "tests.CustomImage")
@override_settings(WAGTAILIMAGES_FEATURE_DETECTION_ENABLED=True)
class TestRawForPreSaveImageFeatureDetection(TestCase):
fixtures = ["test.json"]
# just to test the file is from a fixture doesn't actually exists.
# raw check in pre_save_image_feature_detection skips on the provided condition of this test
# hence avoiding an error
def test_image_does_not_exist(self):
bad_image = Image.objects.get(pk=1)
self.assertFalse(bad_image.file.storage.exists(bad_image.file.name))

Wyświetl plik

@ -722,6 +722,7 @@ class TestGetImageForm(WagtailTestUtils, TestCase):
[
"title",
"file",
"description",
"collection",
"tags",
"focal_point_x",
@ -739,6 +740,7 @@ class TestGetImageForm(WagtailTestUtils, TestCase):
[
"title",
"file",
"description",
"collection",
"tags",
"focal_point_x",
@ -844,11 +846,13 @@ class TestDifferentUpload(TestCase):
def test_upload_path(self):
image = CustomImageFilePath.objects.create(
title="Test image",
description="A test description",
file=get_test_image_file(),
)
second_image = CustomImageFilePath.objects.create(
title="Test Image",
description="A test description",
file=get_test_image_file(colour="black"),
)

Wyświetl plik

@ -0,0 +1,41 @@
# Generated by Django 5.0.7 on 2024-08-06 07:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("tests", "0042_alter_customdocument_file_size_and_more"),
]
operations = [
migrations.AddField(
model_name="customimage",
name="description",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="description"
),
),
migrations.AddField(
model_name="customimagefilepath",
name="description",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="description"
),
),
migrations.AddField(
model_name="customimagewithauthor",
name="description",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="description"
),
),
migrations.AddField(
model_name="customrestaurantimage",
name="description",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="description"
),
),
]