diff --git a/.gitignore b/.gitignore index 486392f6..8d63c885 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,8 @@ local.cfg /venv/ .installed.txt +/test_* +robot_* ## # Add extra configuration options in .meta.toml: diff --git a/.meta.toml b/.meta.toml index 3bf9f054..74ca55db 100644 --- a/.meta.toml +++ b/.meta.toml @@ -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_* +""" diff --git a/MANIFEST.in b/MANIFEST.in index 74225c91..5993732e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..053d9d8b --- /dev/null +++ b/Makefile @@ -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 diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 00000000..3bb2e326 --- /dev/null +++ b/constraints.txt @@ -0,0 +1 @@ +-c https://dist.plone.org/release/6.0.13/constraints.txt diff --git a/instance.yaml b/instance.yaml new file mode 100644 index 00000000..fe9d3149 --- /dev/null +++ b/instance.yaml @@ -0,0 +1,7 @@ +default_context: + initial_user_name: 'admin' + initial_user_password: 'admin' + + zcml_package_includes: ['plone.app.contenttypes'] + + db_storage: direct diff --git a/mx.ini b/mx.ini new file mode 100644 index 00000000..db2906f9 --- /dev/null +++ b/mx.ini @@ -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 diff --git a/news/+setup.internal b/news/+setup.internal new file mode 100644 index 00000000..3ca94f9d --- /dev/null +++ b/news/+setup.internal @@ -0,0 +1,2 @@ +Setup local installation +[@ericof] diff --git a/news/700.feature b/news/700.feature new file mode 100644 index 00000000..b73a4c8e --- /dev/null +++ b/news/700.feature @@ -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] diff --git a/plone/app/contenttypes/browser/templates/image.pt b/plone/app/contenttypes/browser/templates/image.pt index cb77820f..5c0592ff 100644 --- a/plone/app/contenttypes/browser/templates/image.pt +++ b/plone/app/contenttypes/browser/templates/image.pt @@ -27,7 +27,8 @@
View full-size image + > + View full-size image + diff --git a/plone/app/contenttypes/browser/templates/listing.pt b/plone/app/contenttypes/browser/templates/listing.pt index 9ad3df22..5be2631e 100644 --- a/plone/app/contenttypes/browser/templates/listing.pt +++ b/plone/app/contenttypes/browser/templates/listing.pt @@ -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 ''; "> @@ -161,7 +162,7 @@ - + diff --git a/plone/app/contenttypes/browser/templates/listing_album.pt b/plone/app/contenttypes/browser/templates/listing_album.pt index 70561c0e..d3f5f3b3 100644 --- a/plone/app/contenttypes/browser/templates/listing_album.pt +++ b/plone/app/contenttypes/browser/templates/listing_album.pt @@ -48,7 +48,9 @@
@@ -79,7 +81,9 @@
diff --git a/plone/app/contenttypes/browser/templates/listing_summary.pt b/plone/app/contenttypes/browser/templates/listing_summary.pt index fec35e61..91f60dc4 100644 --- a/plone/app/contenttypes/browser/templates/listing_summary.pt +++ b/plone/app/contenttypes/browser/templates/listing_summary.pt @@ -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 @@ -72,7 +74,7 @@ - +
diff --git a/plone/app/contenttypes/browser/templates/listing_tabular.pt b/plone/app/contenttypes/browser/templates/listing_tabular.pt index 29b8cdc3..56205c2a 100644 --- a/plone/app/contenttypes/browser/templates/listing_tabular.pt +++ b/plone/app/contenttypes/browser/templates/listing_tabular.pt @@ -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; @@ -138,7 +139,7 @@ - + diff --git a/plone/app/contenttypes/schema/image.xml b/plone/app/contenttypes/schema/image.xml index 05c45972..af70b3b4 100644 --- a/plone/app/contenttypes/schema/image.xml +++ b/plone/app/contenttypes/schema/image.xml @@ -26,5 +26,19 @@ 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 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. + + False + Alt Text + diff --git a/plone/app/contenttypes/tests/test_image.py b/plone/app/contenttypes/tests/test_image.py index b31a514a..27f6d786 100644 --- a/plone/app/contenttypes/tests/test_image.py +++ b/plone/app/contenttypes/tests/test_image.py @@ -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) @@ -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): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..da443200 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +-c constraints.txt