Skip to content

Commit

Permalink
Merge pull request #701 from plone/add-image-alt-tag
Browse files Browse the repository at this point in the history
Add alt_text field to image content type
  • Loading branch information
mauritsvanrees authored Oct 21, 2024
2 parents bc5aed4 + eec97df commit 6230bd4
Show file tree
Hide file tree
Showing 19 changed files with 232 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ local.cfg
/venv/
.installed.txt

/test_*
robot_*

##
# Add extra configuration options in .meta.toml:
Expand Down
6 changes: 6 additions & 0 deletions .meta.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ dependencies_ignores = "['ZServer', 'plone.app.event', 'Products.CMFPlone',]"

[tox]
constraints_file = "https://dist.plone.org/release/6.1-dev/constraints.txt"

[gitignore]
extra_lines = """
/test_*
robot_*
"""
6 changes: 6 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ global-exclude *.pyc

recursive-exclude news *
exclude news

include requirements.txt
include constraints.txt
include *.yaml
include Makefile
exclude *-mxdev.txt
123 changes: 123 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
### Defensive settings for make:
# https://tech.davis-hansson.com/p/make/
SHELL:=bash
.ONESHELL:
.SHELLFLAGS:=-xeu -o pipefail -O inherit_errexit -c
.SILENT:
.DELETE_ON_ERROR:
MAKEFLAGS+=--warn-undefined-variables
MAKEFLAGS+=--no-builtin-rules

# We like colors
# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects
RED=`tput setaf 1`
GREEN=`tput setaf 2`
RESET=`tput sgr0`
YELLOW=`tput setaf 3`

BACKEND_FOLDER=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
DOCS_DIR=${BACKEND_FOLDER}/docs

# Python checks
PYTHON?=python3

# installed?
ifeq (, $(shell which $(PYTHON) ))
$(error "PYTHON=$(PYTHON) not found in $(PATH)")
endif

# version ok?
PYTHON_VERSION_MIN=3.8
PYTHON_VERSION_OK=$(shell $(PYTHON) -c "import sys; print((int(sys.version_info[0]), int(sys.version_info[1])) >= tuple(map(int, '$(PYTHON_VERSION_MIN)'.split('.'))))")
ifeq ($(PYTHON_VERSION_OK),0)
$(error "Need python $(PYTHON_VERSION) >= $(PYTHON_VERSION_MIN)")
endif

all: install

# Add the following 'help' target to your Makefile
# And add help text after each target name starting with '\#\#'
.PHONY: help
help: ## This help message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: clean
clean: clean-build clean-pyc clean-test clean-venv clean-instance ## remove all build, test, coverage and Python artifacts

.PHONY: clean-instance
clean-instance: ## remove existing instance
rm -fr instance etc inituser var

.PHONY: clean-venv
clean-venv: ## remove virtual environment
rm -fr bin include lib lib64 env pyvenv.cfg .tox .pytest_cache constraints-mxdev.txt requirements-mxdev.txt sources/

.PHONY: clean-build
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
rm -fr parts/
rm -fr coverage.xml
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -rf {} +

.PHONY: clean-pyc
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +

.PHONY: clean-test
clean-test: ## remove test and coverage artifacts
rm -f .coverage
rm -fr htmlcov/
rm -fr test_*
rm -fr robot_*

bin/pip bin/tox bin/mxdev:
@echo "$(GREEN)==> Setup Virtual Env$(RESET)"
$(PYTHON) -m venv .
bin/pip install -U "pip" "wheel" "cookiecutter" "mxdev" "tox" "pre-commit"
bin/pre-commit install

.PHONY: config
config: bin/pip ## Create instance configuration
@echo "$(GREEN)==> Create instance configuration$(RESET)"
bin/cookiecutter -f --no-input --config-file instance.yaml gh:plone/cookiecutter-zope-instance

.PHONY: install-plone-6.0
install-plone-6.0: config ## pip install Plone packages
@echo "$(GREEN)==> Setup Build$(RESET)"
bin/mxdev -c mx.ini
bin/pip install -r requirements-mxdev.txt

.PHONY: install
install: install-plone-6.0 ## Install Plone 6.0

.PHONY: start
start: ## Start a Plone instance on localhost:8080
PYTHONWARNINGS=ignore ./bin/runwsgi instance/etc/zope.ini

.PHONY: console
console: ## Start a console on a Plone instance
PYTHONWARNINGS=ignore ./bin/zconsole debug instance/etc/zope.conf

.PHONY: format
format: bin/tox ## Format the codebase according to our standards
@echo "$(GREEN)==> Format codebase$(RESET)"
bin/tox -e format

.PHONY: lint
lint: ## check code style
bin/tox -e lint

# Tests
.PHONY: test
test: bin/tox ## run tests
bin/tox -e test

.PHONY: test-coverage
test-coverage: bin/tox ## run tests with coverage
bin/tox -e coverage
1 change: 1 addition & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-c https://dist.plone.org/release/6.0.13/constraints.txt
7 changes: 7 additions & 0 deletions instance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
default_context:
initial_user_name: 'admin'
initial_user_password: 'admin'

zcml_package_includes: ['plone.app.contenttypes']

db_storage: direct
9 changes: 9 additions & 0 deletions mx.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
; This is a mxdev configuration file
; it can be used to override versions of packages already defined in the
; constraints files and to add new packages from VCS like git.
; to learn more about mxdev visit https://pypi.org/project/mxdev/

[settings]
main-package = -e .[test]
version-overrides =
plone.app.contenttypes >= 4.0.1.dev0
2 changes: 2 additions & 0 deletions news/+setup.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Setup local installation
[@ericof]
2 changes: 2 additions & 0 deletions news/700.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add alt_text field to image content type. This allows users to manually set the value of alt tag, for usage in automated listings.
[@cekk, @jackahl]
7 changes: 5 additions & 2 deletions plone/app/contenttypes/browser/templates/image.pt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
<figure class="figure">
<a tal:define="
scale context/@@images;
img_tag python:scale.tag('image', scale='large', css_class='figure-img img-fluid');
alt_text context/alt_text|nothing;
img_tag python:scale.tag('image', scale='large', css_class='figure-img img-fluid', alt=alt_text);
"
tal:attributes="
href string:${context/@@plone_context_state/object_url}/image_view_fullscreen;
Expand Down Expand Up @@ -85,7 +86,9 @@
>Download</a>
<a class="btn btn-primary fullscreen"
href="${context/@@plone_context_state/object_url}/image_view_fullscreen"
><span i18n:translate="label_click_to_view_full_image">View full-size image</span></a>
>
<span i18n:translate="label_click_to_view_full_image">View full-size image</span>
</a>
</section>


Expand Down
3 changes: 2 additions & 1 deletion plone/app/contenttypes/browser/templates/listing.pt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
item_link python:item_type in view.use_view_action and item_url+'/view' or item_url;
item_is_event python:view.is_event(obj);
item_has_image python:item.getIcon;
item_alt_text python:getattr(item, 'alt_text', '') if item_has_image else '';
item_type_class python:('contenttype-' + view.normalizeString(item_type)) if showicons else '';
">
<metal:block define-slot="entry">
Expand Down Expand Up @@ -161,7 +162,7 @@
<a tal:attributes="
href python:item_link;
">
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_list, css_class=img_class, loading='lazy')" />
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_list, css_class=img_class, loading='lazy', alt=item_alt_text)" />
</a>
</div>
</article>
Expand Down
8 changes: 6 additions & 2 deletions plone/app/contenttypes/browser/templates/listing_album.pt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@
<div class="card-image d-flex justify-content-center align-items-center"
style="height: 14rem;"
tal:define="
scale python:image_scale.tag(image, 'image', scale='mini', loading='lazy');
item_has_image python:image.getIcon;
item_alt_text python:getattr(image, 'alt_text', '') if item_has_image else '';
scale python:image_scale.tag(image, 'image', scale='mini', loading='lazy', alt=item_alt_text);
"
tal:condition="scale"
>
Expand Down Expand Up @@ -79,7 +81,9 @@
<div class="card-image d-flex justify-content-center align-items-center"
style="height: 14rem;"
tal:define="
scale python:image_scale.tag(album, 'image', scale='mini', loading='lazy') if getattr(album, 'image', None) else None;
item_has_image python:album.getIcon;
item_alt_text python:getattr(album, 'alt_text', '') if item_has_image else '';
scale python:image_scale.tag(album, 'image', scale='mini', loading='lazy', alt=item_alt_text) if getattr(album, 'image', None) else None;
"
tal:condition="scale"
>
Expand Down
4 changes: 3 additions & 1 deletion plone/app/contenttypes/browser/templates/listing_summary.pt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
tal:attributes="
href item_link;
title item_type;
item_has_image python:item.getIcon;
item_alt_text python:getattr(item, 'alt_text', '') if item_has_image else '';
"
>
Item Title
Expand Down Expand Up @@ -72,7 +74,7 @@
<a tal:attributes="
href item_link;
">
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_summary, css_class='image-responsive thumb-' + thumb_scale_summary, loading='lazy')" />
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_summary, css_class='image-responsive thumb-' + thumb_scale_summary, loading='lazy', alt=item_alt_text)" />
</a>
</div>

Expand Down
3 changes: 2 additions & 1 deletion plone/app/contenttypes/browser/templates/listing_tabular.pt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
item_wf_state_class python:'state-' + view.normalizeString(item_wf_state);
item_creator python:item.Creator();
item_has_image python:item.getIcon;
item_alt_text python:getattr(item, 'alt_text', '') if item_has_image else '';
item_link python:item_type in view.use_view_action and item_url+'/view' or item_url;
item_mime_type python:item.mime_type;
item_mime_type_icon python: 'mimetype-' + item_mime_type;
Expand Down Expand Up @@ -138,7 +139,7 @@

<td>
<a tal:condition="python:item_has_image and thumb_scale_table">
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_table, css_class=img_class, loading='lazy')"
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_table, css_class=img_class, loading='lazy', alt=item_alt_text)"
tal:attributes="
href python: item_link;
"
Expand Down
5 changes: 5 additions & 0 deletions plone/app/contenttypes/indexers.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,8 @@ def getIcon(obj):
if obj.aq_base.image:
return True
return False


@indexer(IDexterityContent)
def getAltTag(obj):
return obj.alt_text
4 changes: 4 additions & 0 deletions plone/app/contenttypes/indexers.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@
factory=".indexers.mime_type"
name="mime_type"
/>
<adapter
factory=".indexers.getAltTag"
name="alt_text"
/>

</configure>
14 changes: 14 additions & 0 deletions plone/app/contenttypes/schema/image.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,19 @@
<description />
<title i18n:translate="label_image">Image</title>
</field>
<field name="alt_text"
type="zope.schema.TextLine"
>
<description i18n:translate="label_alt_text_help">
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 or in automated contexts like listings.
Do not duplicate the Title or Description fields, since those might 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.
In case your description already sufficiently describes your image, leave this field blank.
</description>
<required>False</required>
<title i18n:translate="label_alt_text">Alt Text</title>
</field>
</schema>
</model>
32 changes: 32 additions & 0 deletions plone/app/contenttypes/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,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_alt.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)
Expand All @@ -85,6 +94,29 @@ def test_image_view(self):
self.assertTrue("My Image" in view())
self.assertTrue("This is my image." in view())

def test_image_view_alt(self):
view = self.image_alt.restrictedTraverse("@@image_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("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("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("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.
# def test_image_view_fullscreen(self):
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-c constraints.txt

0 comments on commit 6230bd4

Please sign in to comment.