kopia lustrzana https://github.com/wagtail/wagtail
Add "description" field to AbstractImage
- Baseline support for upcoming alt text capabilitiespull/12317/head
rodzic
897b0415cd
commit
5cc22f3f75
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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",
|
||||
|
|
|
@ -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." %}
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
),
|
||||
]
|
Ładowanie…
Reference in New Issue