diff --git a/news/518.feature b/news/518.feature new file mode 100644 index 000000000..739e8fbbf --- /dev/null +++ b/news/518.feature @@ -0,0 +1,2 @@ +Add alt_text field to images and lead image behavior. This allows users to manually set the value of alt tag. +[cekk] diff --git a/plone/app/contenttypes/behaviors/leadimage.py b/plone/app/contenttypes/behaviors/leadimage.py index f7bebbddf..3cacde92d 100644 --- a/plone/app/contenttypes/behaviors/leadimage.py +++ b/plone/app/contenttypes/behaviors/leadimage.py @@ -30,6 +30,23 @@ class ILeadImageBehavior(model.Schema): required=False, ) + alt_text = schema.TextLine( + title=_(u'label_alt_text', default=u'Alt Text'), + description=_( + u'label_alt_text_description', + default=u'Briefly describe the meaning of the image for people ' + u'using assistive technology like screen readers. This will be ' + u'used when the image is viewed by itself. Do not duplicate the ' + u'Title or Description fields, since they will also be read by ' + u'screen readers. Alt text should describe what a sighted user ' + u'sees when looking at the image. This might include text the ' + u'image contains, or even a description of an abstract pattern. ' + u'This field should never be left blank on sites that want to be ' + u'compliant with accessibility standards.' + ), + required=False, + ) + @implementer(ILeadImageBehavior) @adapter(IDexterityContent) @@ -53,3 +70,11 @@ def image_caption(self): @image_caption.setter def image_caption(self, value): self.context.image_caption = value + + @property + def alt_text(self): + return self.context.alt_text + + @alt_text.setter + def alt_text(self, value): + self.context.alt_text = value diff --git a/plone/app/contenttypes/browser/templates/image.pt b/plone/app/contenttypes/browser/templates/image.pt index 8f1541873..86babcdf7 100644 --- a/plone/app/contenttypes/browser/templates/image.pt +++ b/plone/app/contenttypes/browser/templates/image.pt @@ -13,7 +13,8 @@
diff --git a/plone/app/contenttypes/schema/image.xml b/plone/app/contenttypes/schema/image.xml index 63c24e427..72bbd8c18 100644 --- a/plone/app/contenttypes/schema/image.xml +++ b/plone/app/contenttypes/schema/image.xml @@ -18,5 +18,18 @@ Image + + + Briefly describe the meaning of the image for people using assistive technology like screen readers. + This will be used when the image is viewed by itself. + Do not duplicate the Title or Description fields, since they will also be read by screen readers. + Alt text should describe what a sighted user sees when looking at the image. + This might include text the image contains, or even a description of an abstract pattern. + This field should never be left blank on sites that want to be compliant with accessibility standards. + + False + Alt Text + diff --git a/plone/app/contenttypes/tests/test_behaviors_leadimage.py b/plone/app/contenttypes/tests/test_behaviors_leadimage.py index a42331db8..0390d7e83 100644 --- a/plone/app/contenttypes/tests/test_behaviors_leadimage.py +++ b/plone/app/contenttypes/tests/test_behaviors_leadimage.py @@ -79,3 +79,21 @@ def test_lead_image_viewlet_shows_up(self): # But doesn't show up on folder_contents, which is not a default view self.browser.open(self.portal_url + '/leadimagefolder/folder_contents') self.assertTrue('
' not in self.browser.contents) + + def test_lead_image_viewlet_shows_alt_text(self): + self.browser.open(self.portal_url + '/leadimagefolder/edit') + # Image upload + file_path = os.path.join(os.path.dirname(__file__), 'image.jpg') + file_ctl = self.browser.getControl( + name='form.widgets.ILeadImageBehavior.image' + ) + with io.FileIO(file_path, 'rb') as f: + file_ctl.add_file(f, 'image/png', 'image.jpg') + # Image caption + self.browser.getControl( + name='form.widgets.ILeadImageBehavior.alt_text' + ).value = 'alternative text' + # Submit form + self.browser.getControl('Save').click() + + self.assertTrue('alternative text' in self.browser.contents) diff --git a/plone/app/contenttypes/tests/test_image.py b/plone/app/contenttypes/tests/test_image.py index 9974c82e7..09a7e8d7a 100644 --- a/plone/app/contenttypes/tests/test_image.py +++ b/plone/app/contenttypes/tests/test_image.py @@ -94,7 +94,16 @@ def setUp(self): image.title = 'My Image' image.description = 'This is my image.' image.image = dummy_image() + + self.portal.invokeFactory('Image', 'image-with-alt') + image_alt = self.portal['image-with-alt'] + image_alt.title = 'My Image 2' + image_alt.description = 'This is my second image.' + image_alt.alt_text = 'An alt text' + image.image = dummy_image() + self.image = image + self.image_alt = image_alt self.request.set('URL', image.absolute_url()) self.request.set('ACTUAL_URL', image.absolute_url()) alsoProvides(self.request, IPloneFormLayer) @@ -106,6 +115,31 @@ def test_image_view(self): self.assertEqual(view.request.response.status, 200) self.assertTrue('My Image' in view()) self.assertTrue('This is my image.' in view()) + self.assertFalse('An alt text' in view()) + + def test_image_view_alt(self): + view = self.image_alt.restrictedTraverse('@@view') + + self.assertTrue(view()) + self.assertEqual(view.request.response.status, 200) + self.assertTrue('My Image 2' in view()) + self.assertTrue('This is my second image.' in view()) + self.assertTrue('An alt text' in view()) + + def test_image_alt_in_listing_view(self): + self.image_alt.image = dummy_image(u'image.svg') + view = self.portal.restrictedTraverse('@@listing_view') + self.assertTrue('An alt text' in view()) + + def test_image_alt_in_summary_view(self): + self.image_alt.image = dummy_image(u'image.svg') + view = self.portal.restrictedTraverse('@@summary_view') + self.assertTrue('An alt text' in view()) + + def test_image_alt_in_album_view(self): + self.image_alt.image = dummy_image(u'image.svg') + view = self.portal.restrictedTraverse('@@album_view') + self.assertTrue('An alt text' in view()) # XXX: Not working. See ImageFunctionalTest test_image_view_fullscreen # Problem seems to be that the image is not properly uploaded. @@ -124,7 +158,15 @@ def test_svg_image(self): scale = self.image.restrictedTraverse('@@images') self.assertRegex( scale.scale('image', scale='large').tag(), - r'My Image' # noqa: E501 + r'' # noqa: E501 + ) + + def test_svg_image_alt(self): + self.image_alt.image = dummy_image(u'image.svg') + scale = self.image_alt.restrictedTraverse('@@images') + self.assertRegex( + scale.scale('image', scale='large').tag(), + r'An alt text' # noqa: E501 ) diff --git a/plone/app/contenttypes/upgrades.py b/plone/app/contenttypes/upgrades.py index 19c838195..872e9e901 100644 --- a/plone/app/contenttypes/upgrades.py +++ b/plone/app/contenttypes/upgrades.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from plone import api from plone.app.contenttypes.utils import DEFAULT_TYPES from plone.dexterity.interfaces import IDexterityFTI from Products.CMFCore.utils import getToolByName diff --git a/plone/app/contenttypes/upgrades.zcml b/plone/app/contenttypes/upgrades.zcml index 0ad9f2ac7..49944ab13 100644 --- a/plone/app/contenttypes/upgrades.zcml +++ b/plone/app/contenttypes/upgrades.zcml @@ -86,5 +86,5 @@ profile="plone.app.contenttypes:default" handler=".upgrades.searchabletext_richtext" /> - +