From a4e0f98ed1c744008fb476f886befb6dfecdba55 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 17 Jul 2025 14:40:25 +0200 Subject: [PATCH 01/24] initial test setup --- CONTRIBUTORS.md | 2 +- LICENSE.md | 2 +- Makefile | 2 +- README.md | 10 +- pyproject.toml | 47 ++++---- scripts/create_site.py | 3 +- src/cs_dynamicpages/browser/configure.zcml | 10 -- .../content/dynamic_page_row.py | 1 - .../controlpanel.py | 17 ++- src/cs_dynamicpages/dependencies.zcml | 2 +- .../locales/cs_dynamicpages.pot | 18 +++ .../profiles/default/metadata.xml | 5 +- .../default/registry/dynamic_bundle.xml | 2 - .../profiles/uninstall/controlpanel.xml | 21 ++++ .../uninstall/registry/dynamic_bundle.xml | 18 +++ .../registry/dynamica_pages_control_panel.xml | 14 +++ .../profiles/uninstall/types.xml | 22 ++++ .../test_view_dynamic_page_folder_view.py | 1 - ...est_view_dynamic_page_row_featured_view.py | 1 - .../.gitkeep => upgrades/__init__.py} | 0 src/cs_dynamicpages/upgrades/configure.zcml | 21 ++++ .../views/dynamic_page_folder_view.py | 1 - .../views/dynamic_page_row_featured_view.py | 1 - src/cs_dynamicpages/views/dynamic_view.py | 2 +- tests/__init__.py | 0 tests/base.py | 19 +++ tests/conftest.py | 2 +- tests/content/__init__.py | 0 tests/content/conftest.py | 111 ++++++++++++++++++ tests/content/test_content_operations.py | 46 ++++++++ tests/setup/__init__.py | 0 tests/setup/test_setup_install.py | 6 +- 32 files changed, 350 insertions(+), 57 deletions(-) create mode 100644 src/cs_dynamicpages/locales/cs_dynamicpages.pot create mode 100644 src/cs_dynamicpages/profiles/uninstall/controlpanel.xml create mode 100644 src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml create mode 100644 src/cs_dynamicpages/profiles/uninstall/registry/dynamica_pages_control_panel.xml create mode 100644 src/cs_dynamicpages/profiles/uninstall/types.xml rename src/cs_dynamicpages/{browser/overrides/.gitkeep => upgrades/__init__.py} (100%) create mode 100644 src/cs_dynamicpages/upgrades/configure.zcml create mode 100644 tests/__init__.py create mode 100644 tests/base.py create mode 100644 tests/content/__init__.py create mode 100644 tests/content/conftest.py create mode 100644 tests/content/test_content_operations.py create mode 100644 tests/setup/__init__.py diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0e9306f..8ab3256 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,3 +1,3 @@ # Contributors -- Lur Ibargutxi [libargutxi@codesyntax.com] +- Plone Community [collective@plone.org] diff --git a/LICENSE.md b/LICENSE.md index a3a620a..d7e00b9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -cs_dynamicpages Copyright 2025, Lur Ibargutxi +cs_dynamicpages Copyright 2025, Plone Community This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 diff --git a/Makefile b/Makefile index fef1930..c80356e 100644 --- a/Makefile +++ b/Makefile @@ -131,4 +131,4 @@ test-coverage: $(VENV_FOLDER) ## run tests with coverage ## Add bobtemplates features (check bobtemplates.plone's documentation to get the list of available features) add: $(VENV_FOLDER) - /home/lur/plonecli_azkena/bin/plonecli add $(filter-out $@,$(MAKECMDGOALS)) + @uvx plonecli add -b .mrbob.ini $(filter-out $@,$(MAKECMDGOALS)) diff --git a/README.md b/README.md index 99e93b6..ce2ecf7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # cs_dynamicpages -An addon to create dynamic pages for Plone +A new addon for Plone ## Features @@ -22,8 +22,8 @@ make create-site ## Contribute -- [Issue tracker](https://github.com/cs/cs_dynamicpages/issues) -- [Source code](https://github.com/cs/cs_dynamicpages/) +- [Issue tracker](https://github.com/collective/cs_dynamicpages/issues) +- [Source code](https://github.com/collective/cs_dynamicpages/) ### Prerequisites ✅ @@ -38,7 +38,7 @@ make create-site 1. Clone this repository, then change your working directory. ```shell - git clone git@github.com:cs/cs_dynamicpages.git + git clone git@github.com:collective/cs_dynamicpages.git cd cs_dynamicpages ``` @@ -83,4 +83,4 @@ The project is licensed under GPLv2. ## Credits and acknowledgements 🙏 -Generated using [Cookieplone (0.9.7)](https://github.com/plone/cookieplone) and [cookieplone-templates (6782781)](https://github.com/plone/cookieplone-templates/commit/6782781dae4bafb227467828066ab16b84c23750) on 2025-07-07 09:25:15.108762. A special thanks to all contributors and supporters! +Generated using [Cookieplone (0.9.7)](https://github.com/plone/cookieplone) and [cookieplone-templates (4d55553)](https://github.com/plone/cookieplone-templates/commit/4d55553d61416df56b3360914b398d675b3f72a6) on 2025-07-17 11:59:12.982862. A special thanks to all contributors and supporters! diff --git a/pyproject.toml b/pyproject.toml index 601e8cd..04c36e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,49 +1,51 @@ [project] name = "cs_dynamicpages" dynamic = ["version"] -description = "An addon to create dynamic pages for Plone" +description = "A new addon for Plone" readme = "README.md" license = "GPL-2.0-only" requires-python = ">=3.10" -authors = [ - { name = "Lur Ibargutxi", email = "libargutxi@codesyntax.com" }, -] -keywords = [ - "CMS", - "Plone", - "Python", -] +authors = [{ name = "Plone Community", email = "collective@plone.org" }] +keywords = ["CMS", "Plone", "Python"] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Web Environment", - "Framework :: Plone","Framework :: Plone :: 6.0","Framework :: Plone :: 6.1", + "Framework :: Plone", + "Framework :: Plone :: 6.0", + "Framework :: Plone :: 6.1", "Framework :: Plone :: Addon", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Operating System :: OS Independent", - "Programming Language :: Python","Programming Language :: Python :: 3.10","Programming Language :: Python :: 3.11","Programming Language :: Python :: 3.12","Programming Language :: Python :: 3.13", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "Products.CMFPlone", "plone.api", - "z3c.jbot", "collective.z3cform.datagridfield", + "z3c.jbot", ] [project.optional-dependencies] test = [ "horse-with-no-namespace", "plone.app.testing", - "plone.classicui","plone.restapi[test]", + "plone.app.contenttypes[test]", + "plone.classicui", + "plone.restapi[test]", "pytest", "pytest-cov", "pytest-plone>=0.5.0", ] [project.urls] -Homepage = "https://github.com/cs/cs_dynamicpages" +Homepage = "https://github.com/collective/cs_dynamicpages" PyPI = "https://pypi.org/project/cs_dynamicpages" -Source = "https://github.com/cs/cs_dynamicpages" -Tracker = "https://github.com/cs/cs_dynamicpages/issues" +Source = "https://github.com/collective/cs_dynamicpages" +Tracker = "https://github.com/collective/cs_dynamicpages/issues" [project.entry-points."plone.autoinclude.plugin"] @@ -63,9 +65,7 @@ build-backend = "hatchling.build" strict-naming = true [tool.hatch.build.targets.sdist] -exclude = [ - "/.github", -] +exclude = ["/.github"] [tool.hatch.build.targets.wheel] packages = ["src/cs_dynamicpages"] @@ -76,7 +76,7 @@ filename = "CHANGELOG.md" start_string = "\n" title_format = "## {version} ({project_date})" template = "news/.changelog_template.jinja" -issue_format = "[#{issue}](https://github.com/cs/cs_dynamicpages/issues/{issue})" +issue_format = "[#{issue}](https://github.com/collective/cs_dynamicpages/issues/{issue})" underlines = ["", "", ""] [[tool.towncrier.type]] @@ -131,7 +131,8 @@ lint.select = [ # mccabe "C90", # pycodestyle - "E", "W", + "E", + "W", # pyflakes "F", # pygrep-hooks @@ -168,6 +169,4 @@ testpaths = ["tests"] source_pkgs = ["cs_dynamicpages", "tests"] branch = true parallel = true -omit = [ - "src/cs_dynamicpages/locales/*.py", -] \ No newline at end of file +omit = ["src/cs_dynamicpages/locales/*.py"] diff --git a/scripts/create_site.py b/scripts/create_site.py index d650191..b659efd 100644 --- a/scripts/create_site.py +++ b/scripts/create_site.py @@ -46,7 +46,8 @@ def asbool(s): payload = { "title": "cs_dynamicpages", "profile_id": _DEFAULT_PROFILE, - "distribution_name": "classic", +"distribution_name": "classic", + "setup_content": False, "default_language": "en", "portal_timezone": "UTC", diff --git a/src/cs_dynamicpages/browser/configure.zcml b/src/cs_dynamicpages/browser/configure.zcml index 624ce2f..140a7db 100644 --- a/src/cs_dynamicpages/browser/configure.zcml +++ b/src/cs_dynamicpages/browser/configure.zcml @@ -7,16 +7,6 @@ - - - - >>>>>> 0eadc1c (initial test setup) }, ], ) diff --git a/src/cs_dynamicpages/dependencies.zcml b/src/cs_dynamicpages/dependencies.zcml index 75ca76a..3a4d184 100644 --- a/src/cs_dynamicpages/dependencies.zcml +++ b/src/cs_dynamicpages/dependencies.zcml @@ -1,5 +1,5 @@ - + diff --git a/src/cs_dynamicpages/locales/cs_dynamicpages.pot b/src/cs_dynamicpages/locales/cs_dynamicpages.pot new file mode 100644 index 0000000..ca28bf5 --- /dev/null +++ b/src/cs_dynamicpages/locales/cs_dynamicpages.pot @@ -0,0 +1,18 @@ +#--- PLEASE EDIT THE LINES BELOW CORRECTLY --- +#SOME DESCRIPTIVE TITLE. +#FIRST AUTHOR , YEAR. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2022-05-25 17:12+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: cs_dynamicpages\n" diff --git a/src/cs_dynamicpages/profiles/default/metadata.xml b/src/cs_dynamicpages/profiles/default/metadata.xml index ec58cba..999094f 100644 --- a/src/cs_dynamicpages/profiles/default/metadata.xml +++ b/src/cs_dynamicpages/profiles/default/metadata.xml @@ -2,5 +2,8 @@ 1000 - profile-plone.app.dexterity:default + profile-plone.app.dexterity:default + profile-collective.z3cform.datagridfield:default + + diff --git a/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml b/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml index 2419b1c..b31e8cc 100644 --- a/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml +++ b/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml @@ -1,7 +1,5 @@ - - + + + + + + + + diff --git a/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml b/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml new file mode 100644 index 0000000..3ba379b --- /dev/null +++ b/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cs_dynamicpages/profiles/uninstall/registry/dynamica_pages_control_panel.xml b/src/cs_dynamicpages/profiles/uninstall/registry/dynamica_pages_control_panel.xml new file mode 100644 index 0000000..24b3120 --- /dev/null +++ b/src/cs_dynamicpages/profiles/uninstall/registry/dynamica_pages_control_panel.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/src/cs_dynamicpages/profiles/uninstall/types.xml b/src/cs_dynamicpages/profiles/uninstall/types.xml new file mode 100644 index 0000000..a325b1b --- /dev/null +++ b/src/cs_dynamicpages/profiles/uninstall/types.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py b/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py index c00c836..6c58f1e 100644 --- a/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py +++ b/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from cs_dynamicpages.testing import CS_DYNAMICPAGES_FUNCTIONAL_TESTING from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING from cs_dynamicpages.views.dynamic_page_folder_view import IDynamicPageFolderView diff --git a/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py b/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py index 29e6a8f..1e6fd5c 100644 --- a/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py +++ b/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from cs_dynamicpages.testing import CS_DYNAMICPAGES_FUNCTIONAL_TESTING from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING from cs_dynamicpages.views.dynamic_page_row_featured_view import IDynamicPageRowFeaturedView diff --git a/src/cs_dynamicpages/browser/overrides/.gitkeep b/src/cs_dynamicpages/upgrades/__init__.py similarity index 100% rename from src/cs_dynamicpages/browser/overrides/.gitkeep rename to src/cs_dynamicpages/upgrades/__init__.py diff --git a/src/cs_dynamicpages/upgrades/configure.zcml b/src/cs_dynamicpages/upgrades/configure.zcml new file mode 100644 index 0000000..7a0a5a3 --- /dev/null +++ b/src/cs_dynamicpages/upgrades/configure.zcml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/cs_dynamicpages/views/dynamic_page_folder_view.py b/src/cs_dynamicpages/views/dynamic_page_folder_view.py index f8596ad..b68079d 100644 --- a/src/cs_dynamicpages/views/dynamic_page_folder_view.py +++ b/src/cs_dynamicpages/views/dynamic_page_folder_view.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # from cs_dynamicpages import _ from Products.Five.browser import BrowserView diff --git a/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py b/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py index 0dd9585..8366298 100644 --- a/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py +++ b/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # from cs_dynamicpages import _ from Products.Five.browser import BrowserView diff --git a/src/cs_dynamicpages/views/dynamic_view.py b/src/cs_dynamicpages/views/dynamic_view.py index ed03e33..ca6396a 100644 --- a/src/cs_dynamicpages/views/dynamic_view.py +++ b/src/cs_dynamicpages/views/dynamic_view.py @@ -36,4 +36,4 @@ def dynamic_page_folder_element_url(self): return "" def can_edit(self): - return api.user.has_permission("Modify portal content", obj=self.context) \ No newline at end of file + return api.user.has_permission("Modify portal content", obj=self.context) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 0000000..39b5447 --- /dev/null +++ b/tests/base.py @@ -0,0 +1,19 @@ +import pytest + +from cs_dynamicpages import logger +from cs_dynamicpages import PACKAGE_NAME + +class TestBase: + @pytest.fixture(autouse=True) + def installed(self, portal, installer): + """ + Workaround to test isolation problems when using + collective.z3cform.datagridfield + + See: https://community.plone.org/t/test-isolation-errors-with-collective-z3cform-datagridfield/7424 + """ + try: + installer.install_product(PACKAGE_NAME) + except: + logger.info('Package already installed') + diff --git a/tests/conftest.py b/tests/conftest.py index 2055be7..5067a0d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ +from pytest_plone import fixtures_factory from cs_dynamicpages.testing import ACCEPTANCE_TESTING from cs_dynamicpages.testing import FUNCTIONAL_TESTING from cs_dynamicpages.testing import INTEGRATION_TESTING -from pytest_plone import fixtures_factory pytest_plugins = ["pytest_plone"] diff --git a/tests/content/__init__.py b/tests/content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/content/conftest.py b/tests/content/conftest.py new file mode 100644 index 0000000..62e6d0f --- /dev/null +++ b/tests/content/conftest.py @@ -0,0 +1,111 @@ +from zope.interface import alsoProvides +from cs_dynamicpages.interfaces import IBrowserLayer +from plone.app.multilingual.interfaces import ITranslationManager + +from plone.namedfile import NamedBlobImage +from base64 import b64decode +from collections import defaultdict +from plone import api +import pytest + + +@pytest.fixture +def contents() -> list: + """Content to be created.""" + return [ + # EU CONTENT + { + "_container": "", + "_language": "eu", + "_transitions": ["publish"], + "type": "Folder", + "id": "folder", + "title": "Folder", + }, + { + "_container": "folder", + "_transitions": ["publish"], + "_language": "eu", + "type": "DynamicPageFolder", + "id": "dpf", + "title": "DPF", + }, + { + "_container": "folder/dpf", + "_transitions": ["publish"], + "_language": "eu", + "type": "DynamicPageRow", + "id": "row-1", + "title": "Row 1", + }, + { + "_container": "folder/dpf", + "_transitions": ["publish"], + "_language": "eu", + "type": "DynamicPageRow", + "id": "row-2", + "title": "Row 2", + }, + ] + +@pytest.fixture() +def portal(functional): + return functional["portal"] + +@pytest.fixture() +def my_request(functional): + req = functional['request'] + alsoProvides(req, IBrowserLayer) + return req + +@pytest.fixture +def create_contents(contents): + """Helper fixture to create initial content.""" + + def func(portal) -> dict: + ids = defaultdict(list) + for item in contents: + container_path = item["_container"] + container = portal.unrestrictedTraverse(container_path) + payload = {"container": container, "language": item["_language"]} + if "_image" in item: + payload["image"] = NamedBlobImage(b64decode(item["_image"])) + for key, value in item.items(): + if key.startswith("_"): + continue + payload[key] = value + + content = api.content.create(**payload) + content.language = payload["language"] + if "_view" in item: + content.setLayout(item["_view"]) + # Set translation + if "_translation_of" in item: + source = portal.unrestrictedTraverse(item["_translation_of"]) + ITranslationManager(source).register_translation( + content.language, content + ) + # Transition items + if "_transitions" in item: + transitions = item["_transitions"] + for transition in transitions: + api.content.transition(content, transition=transition) + ids[container_path].append(content.getId()) + return ids + + return func + + +@pytest.fixture() +def portal_with_content(app, portal, create_contents): + """Plone portal with initial content.""" + with api.env.adopt_roles(["Manager"]): + content_ids = create_contents(portal) + # transaction.commit() + yield portal + # with api.env.adopt_roles(["Manager"]): + # containers = sorted(content_ids.keys(), reverse=True) + # for container_path in containers: + # container = portal.unrestrictedTraverse(container_path) + # container.manage_delObjects(content_ids[container_path]) + # transaction.commit() diff --git a/tests/content/test_content_operations.py b/tests/content/test_content_operations.py new file mode 100644 index 0000000..b205691 --- /dev/null +++ b/tests/content/test_content_operations.py @@ -0,0 +1,46 @@ +from plone import api +import pytest +from ..base import TestBase + + +class TestContent(TestBase): + + @pytest.fixture(autouse=True) + def create_content(self, portal): + with api.env.adopt_roles(["Manager"]): + self.folder = api.content.create( + container=portal, + type="Folder", + id="folder", + ) + + assert self.folder is not None + assert self.folder.id == "folder" + + self.dpf = api.content.create( + container=self.folder, type="DynamicPageFolder", id="dpf", title="DPF" + ) + + assert self.dpf is not None + assert self.dpf.id == "dpf" + + self.row1 = api.content.create( + container=self.dpf, type="DynamicPageRow", id="row-1", title="Row 1" + ) + assert self.row1 is not None + assert self.row1.id == "row-1" + + self.row2 = api.content.create( + container=self.dpf, type="DynamicPageRow", id="row-2", title="Row 2" + ) + assert self.row2 is not None + assert self.row2.id == "row-2" + + def test_view(self, portal, my_request): + self.folder.setLayout('dynamic-view') + + view = api.content.get_view( + context=self.folder, name="dynamic-view", request=my_request + ) + + assert view().find('Add row') != -1 diff --git a/tests/setup/__init__.py b/tests/setup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/setup/test_setup_install.py b/tests/setup/test_setup_install.py index e2efb57..ebd4363 100644 --- a/tests/setup/test_setup_install.py +++ b/tests/setup/test_setup_install.py @@ -1,7 +1,11 @@ +from ..base import TestBase from cs_dynamicpages import PACKAGE_NAME -class TestSetupInstall: + +class TestSetupInstall(TestBase): + + def test_addon_installed(self, installer): """Test if cs_dynamicpages is installed.""" assert installer.is_product_installed(PACKAGE_NAME) is True From 6e99b5b94d9e0f706bd5ada1a63ed89d2f136c04 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 17 Jul 2025 14:43:33 +0200 Subject: [PATCH 02/24] fix --- .../dynamica_pages_control_panel/controlpanel.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py b/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py index 31f91ad..466438a 100644 --- a/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py +++ b/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py @@ -66,7 +66,6 @@ class IDynamicaPagesControlPanel(Interface): "row_type_has_featured_add_button": True, }, { -<<<<<<< HEAD "row_type": "cs_dynamicpages-features-view", "each_row_type_fields": ["IBasic.title", "IRowColumns.columns"], "row_type_has_featured_add_button": True, @@ -85,17 +84,6 @@ class IDynamicaPagesControlPanel(Interface): "row_type": "cs_dynamicpages-text-view", "each_row_type_fields": ["IBasic.title", "IRichTextBehavior-text"], "row_type_has_featured_add_button": False, -======= - "row_type": "cs_dynamicpages-query-three-columns-view", - "each_row_type_fields": [ - "IBasic.title", - "ICollection.query", - "ICollection.sort_on", - "ICollection.sort_order", - "ICollection.betweeen", - "ICollection.limit", - ], ->>>>>>> 0eadc1c (initial test setup) }, ], ) From e16a642e8013d8cc7d9935f2533096d98d6065b8 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 17 Jul 2025 15:17:25 +0200 Subject: [PATCH 03/24] tests --- src/cs_dynamicpages/views/dynamic_view.pt | 379 ++++++++++++------- src/cs_dynamicpages/vocabularies/row_type.py | 1 + tests/content/conftest.py | 13 + tests/content/test_content_operations.py | 37 +- 4 files changed, 284 insertions(+), 146 deletions(-) diff --git a/src/cs_dynamicpages/views/dynamic_view.pt b/src/cs_dynamicpages/views/dynamic_view.pt index 983a03e..6b15d3a 100644 --- a/src/cs_dynamicpages/views/dynamic_view.pt +++ b/src/cs_dynamicpages/views/dynamic_view.pt @@ -1,158 +1,257 @@ - + - - -
-
+ + +
-
- - + class="fixed-top d-flex justify-content-end py-2 me-2" + tal:condition="view/can_edit" + > +
+
+ + + +
-
-
- - -
-
- + + +
+
+ Template: ${row/row_template} - - Edit - Delete row + + Edit + Delete row - -
- -
- - -
-
-
-
-
- - +
  • + +
  • + + +
  • ${featured_transition/name} +
  • +
    +
    +
  • + +
  • +
  • Move up
  • +
  • Move down
  • +
  • + + +
  • Delete
  • + + +
    +
    +
    - - Add new featured - + + Add new featured + +
    +
    +
    + +
    +
    +
    +
    + + -
    - -
    -
    -
    -
    - - - - -
    -
    - +
    - \ No newline at end of file +
    diff --git a/src/cs_dynamicpages/vocabularies/row_type.py b/src/cs_dynamicpages/vocabularies/row_type.py index 2838efa..18640f7 100644 --- a/src/cs_dynamicpages/vocabularies/row_type.py +++ b/src/cs_dynamicpages/vocabularies/row_type.py @@ -29,6 +29,7 @@ def __call__(self, context): portal_type="DynamicPageRow", context=api.portal.get(), ) + query_context = None if elements: query_context = elements[0].getObject() else: diff --git a/tests/content/conftest.py b/tests/content/conftest.py index 62e6d0f..7609f85 100644 --- a/tests/content/conftest.py +++ b/tests/content/conftest.py @@ -1,3 +1,6 @@ +from plone.app.testing import SITE_OWNER_NAME +from plone.app.testing import SITE_OWNER_PASSWORD +from plone.testing.zope import Browser from zope.interface import alsoProvides from cs_dynamicpages.interfaces import IBrowserLayer from plone.app.multilingual.interfaces import ITranslationManager @@ -52,6 +55,16 @@ def contents() -> list: def portal(functional): return functional["portal"] + +@pytest.fixture() +def browser(functional): + browser = Browser(functional['app']) + browser.handleErrors = False + browser.addHeader( + "Authorization", f"Basic {SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}" + ) + return browser + @pytest.fixture() def my_request(functional): req = functional['request'] diff --git a/tests/content/test_content_operations.py b/tests/content/test_content_operations.py index b205691..dc90db9 100644 --- a/tests/content/test_content_operations.py +++ b/tests/content/test_content_operations.py @@ -1,3 +1,4 @@ +import transaction from plone import api import pytest from ..base import TestBase @@ -17,6 +18,8 @@ def create_content(self, portal): assert self.folder is not None assert self.folder.id == "folder" + self.folder.setLayout('dynamic-view') + self.dpf = api.content.create( container=self.folder, type="DynamicPageFolder", id="dpf", title="DPF" ) @@ -36,11 +39,33 @@ def create_content(self, portal): assert self.row2 is not None assert self.row2.id == "row-2" - def test_view(self, portal, my_request): - self.folder.setLayout('dynamic-view') + transaction.commit() + def test_view(self, browser): + """ check that the folder is rendered correctly with the basic instructions """ + + browser.open(self.folder.absolute_url()) + # We have 2 rows, so there must be an option to delete a row + assert 'Delete row' in browser.contents + + # assert "Row 1" in browser.contents + # assert "Row 2" in browser.contents + + # There must be an option to add a new row + assert 'Add new row' in browser.contents + + def test_add_row(self, browser): + """ click add row""" + browser.open(self.folder.absolute_url()) + link = browser.getLink("Add new row") + link.click() + assert "++add++DynamicPageRow" in browser.url + + control = browser.getControl(name="form.widgets.IBasic.title") + control.value = "Row 3" + + save = browser.getControl(name="form.buttons.save") + save.click() - view = api.content.get_view( - context=self.folder, name="dynamic-view", request=my_request - ) + browser.open(self.folder.absolute_url()) - assert view().find('Add row') != -1 + assert len(self.folder.dpf.keys()) == 3 From 0149e0a0376b666b42a02831d9f4cbce5387b5f3 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 17 Jul 2025 15:20:43 +0200 Subject: [PATCH 04/24] lint & format --- scripts/create_site.py | 3 +- src/cs_dynamicpages/behaviors/configure.zcml | 16 +- .../behaviors/related_image.py | 5 +- src/cs_dynamicpages/behaviors/row_columns.py | 12 +- src/cs_dynamicpages/configure.zcml | 4 +- .../content/dynamic_page_row.py | 9 +- .../content/dynamic_page_row_featured.py | 3 +- .../controlpanel.py | 33 +- src/cs_dynamicpages/locales/__main__.py | 6 +- src/cs_dynamicpages/permissions.zcml | 2 +- .../default/registry/dynamic_bundle.xml | 54 +-- .../profiles/default/types/DynamicPageRow.xml | 2 +- .../default/types/DynamicPageRowFeatured.xml | 2 +- .../profiles/uninstall/controlpanel.xml | 3 +- .../uninstall/registry/dynamic_bundle.xml | 13 +- .../tests/test_behavior_row_columns.py | 10 +- .../test_view_dynamic_page_folder_view.py | 22 +- ...est_view_dynamic_page_row_featured_view.py | 26 +- .../tests/test_vocab_image_position.py | 13 +- .../tests/test_vocab_row_columns.py | 13 +- src/cs_dynamicpages/views/accordion_view.pt | 40 +- src/cs_dynamicpages/views/configure.zcml | 28 +- .../views/dynamic_page_folder_view.pt | 60 +-- .../views/dynamic_page_folder_view.py | 5 +- .../views/dynamic_page_row_featured_view.pt | 40 +- .../views/dynamic_page_row_featured_view.py | 5 +- .../views/dynamic_page_row_view.pt | 41 ++- .../views/dynamic_page_row_view.py | 1 + src/cs_dynamicpages/views/dynamic_view.pt | 341 +++++++++--------- src/cs_dynamicpages/views/dynamic_view.py | 5 +- .../views/featured_overlay_view.pt | 20 +- src/cs_dynamicpages/views/featured_view.pt | 30 +- src/cs_dynamicpages/views/featured_view.py | 2 +- src/cs_dynamicpages/views/features_view.pt | 50 ++- .../views/horizontal_rule_view.pt | 2 +- .../views/query_columns_view.pt | 41 ++- .../views/query_columns_view.py | 3 +- src/cs_dynamicpages/views/slider_view.pt | 62 +++- src/cs_dynamicpages/views/slider_view.py | 2 +- src/cs_dynamicpages/views/spacer_view.pt | 2 +- src/cs_dynamicpages/views/text_view.pt | 8 +- .../vocabularies/configure.zcml | 16 +- .../vocabularies/image_position.py | 19 +- .../vocabularies/row_columns.py | 21 +- src/cs_dynamicpages/vocabularies/row_type.py | 10 +- tests/base.py | 8 +- tests/conftest.py | 2 +- tests/content/conftest.py | 23 +- tests/content/test_content_operations.py | 17 +- tests/setup/test_setup_install.py | 3 - 50 files changed, 641 insertions(+), 517 deletions(-) diff --git a/scripts/create_site.py b/scripts/create_site.py index b659efd..d650191 100644 --- a/scripts/create_site.py +++ b/scripts/create_site.py @@ -46,8 +46,7 @@ def asbool(s): payload = { "title": "cs_dynamicpages", "profile_id": _DEFAULT_PROFILE, -"distribution_name": "classic", - + "distribution_name": "classic", "setup_content": False, "default_language": "en", "portal_timezone": "UTC", diff --git a/src/cs_dynamicpages/behaviors/configure.zcml b/src/cs_dynamicpages/behaviors/configure.zcml index f1d4aeb..ea2852a 100644 --- a/src/cs_dynamicpages/behaviors/configure.zcml +++ b/src/cs_dynamicpages/behaviors/configure.zcml @@ -13,14 +13,14 @@ - + - + - + diff --git a/src/cs_dynamicpages/content/dynamic_page_row.py b/src/cs_dynamicpages/content/dynamic_page_row.py index 3477d52..e7f71da 100644 --- a/src/cs_dynamicpages/content/dynamic_page_row.py +++ b/src/cs_dynamicpages/content/dynamic_page_row.py @@ -12,6 +12,7 @@ from zope import schema from zope.interface import implementer + log = getLogger(__name__) # from cs_dynamicpages import _ @@ -51,10 +52,12 @@ def featured_list(self): def show_featured_add_button(self): row_type = self.row_type - row_type_fields = api.portal.get_registry_record('cs_dynamicpages.dynamica_pages_control_panel.row_type_fields') + row_type_fields = api.portal.get_registry_record( + "cs_dynamicpages.dynamica_pages_control_panel.row_type_fields" + ) for row_type_field in row_type_fields: - if row_type_field['row_type'] == row_type: - return row_type_field['row_type_has_featured_add_button'] + if row_type_field["row_type"] == row_type: + return row_type_field["row_type_has_featured_add_button"] return False def render(self, request): diff --git a/src/cs_dynamicpages/content/dynamic_page_row_featured.py b/src/cs_dynamicpages/content/dynamic_page_row_featured.py index 1a20140..27ad5f5 100644 --- a/src/cs_dynamicpages/content/dynamic_page_row_featured.py +++ b/src/cs_dynamicpages/content/dynamic_page_row_featured.py @@ -1,5 +1,6 @@ # from plone.app.textfield import RichText # from plone.autoform import directives +from plone import api from plone.dexterity.content import Item # from plone.namedfile import field as namedfile @@ -9,7 +10,7 @@ # from z3c.form.browser.radio import RadioFieldWidget # from zope import schema from zope.interface import implementer -from plone import api + # from cs_dynamicpages import _ diff --git a/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py b/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py index 466438a..3769ed6 100644 --- a/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py +++ b/src/cs_dynamicpages/controlpanels/dynamica_pages_control_panel/controlpanel.py @@ -1,15 +1,15 @@ +from collective.z3cform.datagridfield.datagridfield import DataGridFieldFactory +from collective.z3cform.datagridfield.registry import DictRow from cs_dynamicpages import _ from cs_dynamicpages.interfaces import IBrowserLayer from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper from plone.app.registry.browser.controlpanel import RegistryEditForm +from plone.autoform.directives import widget from plone.restapi.controlpanels import RegistryConfigletPanel from plone.z3cform import layout from zope import schema from zope.component import adapter from zope.interface import Interface -from collective.z3cform.datagridfield.datagridfield import DataGridFieldFactory -from collective.z3cform.datagridfield.registry import DictRow -from plone.autoform.directives import widget class IRowTypeFieldsSchema(Interface): @@ -42,12 +42,25 @@ class IDynamicaPagesControlPanel(Interface): default=[ { "row_type": "cs_dynamicpages-featured-view", - "each_row_type_fields": ["IBasic.title", "IBasic.description", "IRelatedImage.related_image", "IRelatedImage.image_position", "ILinkInfo.link_text", "ILinkInfo.link_url"], + "each_row_type_fields": [ + "IBasic.title", + "IBasic.description", + "IRelatedImage.related_image", + "IRelatedImage.image_position", + "ILinkInfo.link_text", + "ILinkInfo.link_url", + ], "row_type_has_featured_add_button": False, }, { "row_type": "cs_dynamicpages-featured-overlay-view", - "each_row_type_fields": ["IBasic.title", "IBasic.description", "IRelatedImage.related_image", "ILinkInfo.link_text", "ILinkInfo.link_url"], + "each_row_type_fields": [ + "IBasic.title", + "IBasic.description", + "IRelatedImage.related_image", + "ILinkInfo.link_text", + "ILinkInfo.link_url", + ], "row_type_has_featured_add_button": False, }, { @@ -77,7 +90,15 @@ class IDynamicaPagesControlPanel(Interface): }, { "row_type": "cs_dynamicpages-query-columns-view", - "each_row_type_fields": ["IBasic.title", "ICollection.query", "ICollection.sort_on", "ICollection.sort_order", "ICollection.betweeen", "ICollection.limit", "IRowColumns.columns"], + "each_row_type_fields": [ + "IBasic.title", + "ICollection.query", + "ICollection.sort_on", + "ICollection.sort_order", + "ICollection.betweeen", + "ICollection.limit", + "IRowColumns.columns", + ], "row_type_has_featured_add_button": False, }, { diff --git a/src/cs_dynamicpages/locales/__main__.py b/src/cs_dynamicpages/locales/__main__.py index a5677b6..cea189c 100644 --- a/src/cs_dynamicpages/locales/__main__.py +++ b/src/cs_dynamicpages/locales/__main__.py @@ -37,7 +37,7 @@ def locale_folder_setup(domain: str): f"--input={locale_path}/{domain}.pot " f"--output={locale_path}/{lang}/LC_MESSAGES/{domain}.po" ) - subprocess.call(cmd, shell=True) # noQA: S602 + subprocess.call(cmd, shell=True) # noqa: S602 def _rebuild(domain: str): @@ -46,7 +46,7 @@ def _rebuild(domain: str): f"--exclude {excludes} " f"--create {domain} {target_path}" ) - subprocess.call(cmd, shell=True) # noQA: S602 + subprocess.call(cmd, shell=True) # noqa: S602 def _sync(domain: str): @@ -54,7 +54,7 @@ def _sync(domain: str): f"{i18ndude} sync --pot {locale_path}/{domain}.pot " f"{locale_path}/*/LC_MESSAGES/{domain}.po" ) - subprocess.call(cmd, shell=True) # noQA: S602 + subprocess.call(cmd, shell=True) # noqa: S602 def main(): diff --git a/src/cs_dynamicpages/permissions.zcml b/src/cs_dynamicpages/permissions.zcml index 6773a76..cf82d48 100644 --- a/src/cs_dynamicpages/permissions.zcml +++ b/src/cs_dynamicpages/permissions.zcml @@ -17,7 +17,7 @@ title="cs_dynamicpages: Add DynamicPageRow" /> - diff --git a/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml b/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml index b31e8cc..908eb65 100644 --- a/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml +++ b/src/cs_dynamicpages/profiles/default/registry/dynamic_bundle.xml @@ -1,48 +1,48 @@ - + - - True - ++plone++cs_dynamicpages.edit/edit_dynamicpagerow.js - ++plone++cs_dynamicpages.edit/dynamicpageview.css - plone - True - False - + True + ++plone++cs_dynamicpages.edit/edit_dynamicpagerow.js + ++plone++cs_dynamicpages.edit/dynamicpageview.css + plone + True + False + - True - ++plone++cs_dynamicpages.edit/delete-row.js - plone - True - False - + True + ++plone++cs_dynamicpages.edit/delete-row.js + plone + True + False + - True - ++plone++cs_dynamicpages.edit/reorder-rows.js - plone - True - False - + True + ++plone++cs_dynamicpages.edit/reorder-rows.js + plone + True + False + - True - ++plone++cs_dynamicpages.edit/editmode-toggle.js - plone - True - False - + True + ++plone++cs_dynamicpages.edit/editmode-toggle.js + plone + True + False + diff --git a/src/cs_dynamicpages/profiles/default/types/DynamicPageRow.xml b/src/cs_dynamicpages/profiles/default/types/DynamicPageRow.xml index 787b2bd..cc8b6d4 100644 --- a/src/cs_dynamicpages/profiles/default/types/DynamicPageRow.xml +++ b/src/cs_dynamicpages/profiles/default/types/DynamicPageRow.xml @@ -54,7 +54,7 @@ - + diff --git a/src/cs_dynamicpages/profiles/default/types/DynamicPageRowFeatured.xml b/src/cs_dynamicpages/profiles/default/types/DynamicPageRowFeatured.xml index 06e8789..e8d85e9 100644 --- a/src/cs_dynamicpages/profiles/default/types/DynamicPageRowFeatured.xml +++ b/src/cs_dynamicpages/profiles/default/types/DynamicPageRowFeatured.xml @@ -47,7 +47,7 @@ - + diff --git a/src/cs_dynamicpages/profiles/uninstall/controlpanel.xml b/src/cs_dynamicpages/profiles/uninstall/controlpanel.xml index a1244da..cafe9d2 100644 --- a/src/cs_dynamicpages/profiles/uninstall/controlpanel.xml +++ b/src/cs_dynamicpages/profiles/uninstall/controlpanel.xml @@ -11,11 +11,12 @@ category="Products" condition_expr="" icon_expr="" + remove="true" title="Dynamica Pages Control Panel" url_expr="string:${portal_url}/@@dynamica_pages_control_panel-controlpanel" visible="True" i18n:attributes="title" - remove="true" /> + />
    diff --git a/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml b/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml index 3ba379b..dc49d67 100644 --- a/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml +++ b/src/cs_dynamicpages/profiles/uninstall/registry/dynamic_bundle.xml @@ -1,18 +1,21 @@ - + + remove="true" + /> + remove="true" + /> + remove="true" + /> - \ No newline at end of file + diff --git a/src/cs_dynamicpages/tests/test_behavior_row_columns.py b/src/cs_dynamicpages/tests/test_behavior_row_columns.py index cfc4c29..4e56339 100644 --- a/src/cs_dynamicpages/tests/test_behavior_row_columns.py +++ b/src/cs_dynamicpages/tests/test_behavior_row_columns.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- from cs_dynamicpages.behaviors.row_columns import IRowColumnsMarker -from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING # noqa +from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.behavior.interfaces import IBehavior @@ -10,16 +9,15 @@ class RowColumnsIntegrationTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_INTEGRATION_TESTING def setUp(self): """Custom shared utility setup for tests.""" - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) def test_behavior_row_columns(self): - behavior = getUtility(IBehavior, 'cs_dynamicpages.row_columns') + behavior = getUtility(IBehavior, "cs_dynamicpages.row_columns") self.assertEqual( behavior.marker, IRowColumnsMarker, diff --git a/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py b/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py index 6c58f1e..caf7d16 100644 --- a/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py +++ b/src/cs_dynamicpages/tests/test_view_dynamic_page_folder_view.py @@ -11,19 +11,18 @@ class ViewsIntegrationTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - api.content.create(self.portal, 'Folder', 'other-folder') - api.content.create(self.portal, 'Document', 'front-page') + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + api.content.create(self.portal, "Folder", "other-folder") + api.content.create(self.portal, "Document", "front-page") def test_dynamic_page_folder_view_is_registered(self): view = getMultiAdapter( - (self.portal['other-folder'], self.portal.REQUEST), - name='dynamic-page-folder-view' + (self.portal["other-folder"], self.portal.REQUEST), + name="dynamic-page-folder-view", ) self.assertTrue(IDynamicPageFolderView.providedBy(view)) @@ -31,8 +30,8 @@ def test_dynamic_page_folder_view_not_matching_interface(self): view_found = True try: view = getMultiAdapter( - (self.portal['front-page'], self.portal.REQUEST), - name='dynamic-page-folder-view' + (self.portal["front-page"], self.portal.REQUEST), + name="dynamic-page-folder-view", ) except ComponentLookupError: view_found = False @@ -42,9 +41,8 @@ def test_dynamic_page_folder_view_not_matching_interface(self): class ViewsFunctionalTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_FUNCTIONAL_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) diff --git a/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py b/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py index 1e6fd5c..e8d71fa 100644 --- a/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py +++ b/src/cs_dynamicpages/tests/test_view_dynamic_page_row_featured_view.py @@ -1,6 +1,8 @@ from cs_dynamicpages.testing import CS_DYNAMICPAGES_FUNCTIONAL_TESTING from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING -from cs_dynamicpages.views.dynamic_page_row_featured_view import IDynamicPageRowFeaturedView +from cs_dynamicpages.views.dynamic_page_row_featured_view import ( + IDynamicPageRowFeaturedView, +) from plone import api from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID @@ -11,19 +13,18 @@ class ViewsIntegrationTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - api.content.create(self.portal, 'Folder', 'other-folder') - api.content.create(self.portal, 'Document', 'front-page') + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + api.content.create(self.portal, "Folder", "other-folder") + api.content.create(self.portal, "Document", "front-page") def test_dynamic_page_row_featured_view_is_registered(self): view = getMultiAdapter( - (self.portal['other-folder'], self.portal.REQUEST), - name='dynamic-page-row-featured-view' + (self.portal["other-folder"], self.portal.REQUEST), + name="dynamic-page-row-featured-view", ) self.assertTrue(IDynamicPageRowFeaturedView.providedBy(view)) @@ -31,8 +32,8 @@ def test_dynamic_page_row_featured_view_not_matching_interface(self): view_found = True try: view = getMultiAdapter( - (self.portal['front-page'], self.portal.REQUEST), - name='dynamic-page-row-featured-view' + (self.portal["front-page"], self.portal.REQUEST), + name="dynamic-page-row-featured-view", ) except ComponentLookupError: view_found = False @@ -42,9 +43,8 @@ def test_dynamic_page_row_featured_view_not_matching_interface(self): class ViewsFunctionalTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_FUNCTIONAL_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) diff --git a/src/cs_dynamicpages/tests/test_vocab_image_position.py b/src/cs_dynamicpages/tests/test_vocab_image_position.py index b5ce923..ad21df9 100644 --- a/src/cs_dynamicpages/tests/test_vocab_image_position.py +++ b/src/cs_dynamicpages/tests/test_vocab_image_position.py @@ -1,5 +1,5 @@ from cs_dynamicpages import _ -from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING # noqa +from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from zope.component import getUtility @@ -10,22 +10,21 @@ class ImagePositionIntegrationTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_INTEGRATION_TESTING def setUp(self): """Custom shared utility setup for tests.""" - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) def test_vocab_image_position(self): - vocab_name = 'cs_dynamicpages.ImagePosition' + vocab_name = "cs_dynamicpages.ImagePosition" factory = getUtility(IVocabularyFactory, vocab_name) self.assertTrue(IVocabularyFactory.providedBy(factory)) vocabulary = factory(self.portal) self.assertTrue(IVocabularyTokenized.providedBy(vocabulary)) self.assertEqual( - vocabulary.getTerm('sony-a7r-iii').title, - _(u'Sony Aplha 7R III'), + vocabulary.getTerm("sony-a7r-iii").title, + _("Sony Aplha 7R III"), ) diff --git a/src/cs_dynamicpages/tests/test_vocab_row_columns.py b/src/cs_dynamicpages/tests/test_vocab_row_columns.py index afe4d49..4bc54cd 100644 --- a/src/cs_dynamicpages/tests/test_vocab_row_columns.py +++ b/src/cs_dynamicpages/tests/test_vocab_row_columns.py @@ -1,5 +1,5 @@ from cs_dynamicpages import _ -from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING # noqa +from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from zope.component import getUtility @@ -10,22 +10,21 @@ class RowColumnsIntegrationTest(unittest.TestCase): - layer = CS_DYNAMICPAGES_INTEGRATION_TESTING def setUp(self): """Custom shared utility setup for tests.""" - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) def test_vocab_row_columns(self): - vocab_name = 'cs_dynamicpages.RowColumns' + vocab_name = "cs_dynamicpages.RowColumns" factory = getUtility(IVocabularyFactory, vocab_name) self.assertTrue(IVocabularyFactory.providedBy(factory)) vocabulary = factory(self.portal) self.assertTrue(IVocabularyTokenized.providedBy(vocabulary)) self.assertEqual( - vocabulary.getTerm('sony-a7r-iii').title, - _(u'Sony Aplha 7R III'), + vocabulary.getTerm("sony-a7r-iii").title, + _("Sony Aplha 7R III"), ) diff --git a/src/cs_dynamicpages/views/accordion_view.pt b/src/cs_dynamicpages/views/accordion_view.pt index 491f6e8..c638c84 100644 --- a/src/cs_dynamicpages/views/accordion_view.pt +++ b/src/cs_dynamicpages/views/accordion_view.pt @@ -1,22 +1,38 @@ -
    -
    + " + tal:condition="elements" +> +
    -
    -
    -
    +
    - + " + > +
    -
    \ No newline at end of file +
    diff --git a/src/cs_dynamicpages/views/configure.zcml b/src/cs_dynamicpages/views/configure.zcml index 15a3539..9fa7921 100644 --- a/src/cs_dynamicpages/views/configure.zcml +++ b/src/cs_dynamicpages/views/configure.zcml @@ -8,22 +8,22 @@ + name="view" + for="cs_dynamicpages.content.dynamic_page_row_featured.IDynamicPageRowFeatured" + class=".dynamic_page_row_featured_view.DynamicPageRowFeaturedView" + template="dynamic_page_row_featured_view.pt" + permission="zope2.View" + layer="cs_dynamicpages.interfaces.IBrowserLayer" + /> + name="view" + for="cs_dynamicpages.content.dynamic_page_folder.IDynamicPageFolder" + class=".dynamic_page_folder_view.DynamicPageFolderView" + template="dynamic_page_folder_view.pt" + permission="zope2.View" + layer="cs_dynamicpages.interfaces.IBrowserLayer" + /> + - + - -

    - + +

    +
    - -

    - + +

    +
    - - + + - - - - - - - View dynamic page - - + + + + + + + View dynamic page + + - - - + + + - \ No newline at end of file + diff --git a/src/cs_dynamicpages/views/dynamic_page_folder_view.py b/src/cs_dynamicpages/views/dynamic_page_folder_view.py index b68079d..d58cd86 100644 --- a/src/cs_dynamicpages/views/dynamic_page_folder_view.py +++ b/src/cs_dynamicpages/views/dynamic_page_folder_view.py @@ -1,13 +1,14 @@ - # from cs_dynamicpages import _ from Products.Five.browser import BrowserView from zope.interface import implementer from zope.interface import Interface + # from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile + class IDynamicPageFolderView(Interface): - """ Marker Interface for IDynamicPageFolderView""" + """Marker Interface for IDynamicPageFolderView""" @implementer(IDynamicPageFolderView) diff --git a/src/cs_dynamicpages/views/dynamic_page_row_featured_view.pt b/src/cs_dynamicpages/views/dynamic_page_row_featured_view.pt index 980e00f..78af5c0 100644 --- a/src/cs_dynamicpages/views/dynamic_page_row_featured_view.pt +++ b/src/cs_dynamicpages/views/dynamic_page_row_featured_view.pt @@ -1,25 +1,29 @@ - + - + - -

    - + +

    +
    - -

    - + +

    +
    - - + + - - + + - - - + + + - \ No newline at end of file + diff --git a/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py b/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py index 8366298..84422a9 100644 --- a/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py +++ b/src/cs_dynamicpages/views/dynamic_page_row_featured_view.py @@ -1,13 +1,14 @@ - # from cs_dynamicpages import _ from Products.Five.browser import BrowserView from zope.interface import implementer from zope.interface import Interface + # from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile + class IDynamicPageRowFeaturedView(Interface): - """ Marker Interface for IDynamicPageRowFeaturedView""" + """Marker Interface for IDynamicPageRowFeaturedView""" @implementer(IDynamicPageRowFeaturedView) diff --git a/src/cs_dynamicpages/views/dynamic_page_row_view.pt b/src/cs_dynamicpages/views/dynamic_page_row_view.pt index 7eb711f..38f6aae 100644 --- a/src/cs_dynamicpages/views/dynamic_page_row_view.pt +++ b/src/cs_dynamicpages/views/dynamic_page_row_view.pt @@ -1,26 +1,29 @@ - + - + - -

    - + +

    +
    - -

    - + +

    +
    - - + + - - + + - - - + + + - \ No newline at end of file + diff --git a/src/cs_dynamicpages/views/dynamic_page_row_view.py b/src/cs_dynamicpages/views/dynamic_page_row_view.py index df39bd6..a15d1ea 100644 --- a/src/cs_dynamicpages/views/dynamic_page_row_view.py +++ b/src/cs_dynamicpages/views/dynamic_page_row_view.py @@ -3,6 +3,7 @@ from zope.interface import implementer from zope.interface import Interface + # from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile diff --git a/src/cs_dynamicpages/views/dynamic_view.pt b/src/cs_dynamicpages/views/dynamic_view.pt index 6b15d3a..2c78757 100644 --- a/src/cs_dynamicpages/views/dynamic_view.pt +++ b/src/cs_dynamicpages/views/dynamic_view.pt @@ -1,104 +1,102 @@ - +
    -
    +
    - - - + +
    - + -
    -
    +
    +
    -
    - Add new row + -

    +

    diff --git a/src/cs_dynamicpages/views/dynamic_view.py b/src/cs_dynamicpages/views/dynamic_view.py index ca6396a..fb20f67 100644 --- a/src/cs_dynamicpages/views/dynamic_view.py +++ b/src/cs_dynamicpages/views/dynamic_view.py @@ -26,7 +26,10 @@ def rows(self): def dynamic_page_folder_element(self): return api.content.find( - portal_type="DynamicPageFolder", context=self.context, depth=1, sort_on="getObjPositionInParent" + portal_type="DynamicPageFolder", + context=self.context, + depth=1, + sort_on="getObjPositionInParent", ) def dynamic_page_folder_element_url(self): diff --git a/src/cs_dynamicpages/views/featured_overlay_view.pt b/src/cs_dynamicpages/views/featured_overlay_view.pt index 34fc3da..07742fc 100644 --- a/src/cs_dynamicpages/views/featured_overlay_view.pt +++ b/src/cs_dynamicpages/views/featured_overlay_view.pt @@ -2,18 +2,26 @@ + " + tal:condition="image" + > + title=image.Title())" + />
    ${context/Title}
    -

    ${context/Description}

    - ${context/link_text} +

    ${context/Description}

    + ${context/link_text}
    - \ No newline at end of file + diff --git a/src/cs_dynamicpages/views/featured_view.pt b/src/cs_dynamicpages/views/featured_view.pt index 915d80b..839626b 100644 --- a/src/cs_dynamicpages/views/featured_view.pt +++ b/src/cs_dynamicpages/views/featured_view.pt @@ -1,23 +1,35 @@
    + image view/related_image; + " + tal:condition="image" + > + title=image.Title())" + />
    + tal:attributes=" + class python:context.image_position=='right' and 'col-md-6 order-first' or 'col-md-6'; + " + >

    ${context/Title}

    ${context/Description}

    - + - ${context/link_text} + ${context/link_text}
    -
    \ No newline at end of file + diff --git a/src/cs_dynamicpages/views/featured_view.py b/src/cs_dynamicpages/views/featured_view.py index 0f6e7c5..ae977c9 100644 --- a/src/cs_dynamicpages/views/featured_view.py +++ b/src/cs_dynamicpages/views/featured_view.py @@ -1,7 +1,7 @@ # from cs_dynamicpages import _ +from cs_dynamicpages.views.dynamic_page_row_view import DynamicPageRowView from zope.interface import implementer from zope.interface import Interface -from cs_dynamicpages.views.dynamic_page_row_view import DynamicPageRowView class IFeaturedView(Interface): diff --git a/src/cs_dynamicpages/views/features_view.pt b/src/cs_dynamicpages/views/features_view.pt index c85ec5d..664903b 100644 --- a/src/cs_dynamicpages/views/features_view.pt +++ b/src/cs_dynamicpages/views/features_view.pt @@ -1,32 +1,50 @@ -
    -
    + " + tal:condition="elements" +> +
    + element brain/getObject; + ">
    + image element/related_image_object; + " + tal:condition="image" + tal:on-error="nothing" + > + title=element.Title())" + />
    - ${element/Title} -

    ${element/Title}

    -

    ${element/Description}

    - + ${element/Title} +

    ${element/Title}

    +

    ${element/Description}

    +
    -
    \ No newline at end of file +
    diff --git a/src/cs_dynamicpages/views/horizontal_rule_view.pt b/src/cs_dynamicpages/views/horizontal_rule_view.pt index 84af8ad..af383ee 100644 --- a/src/cs_dynamicpages/views/horizontal_rule_view.pt +++ b/src/cs_dynamicpages/views/horizontal_rule_view.pt @@ -1 +1 @@ -
    \ No newline at end of file +
    diff --git a/src/cs_dynamicpages/views/query_columns_view.pt b/src/cs_dynamicpages/views/query_columns_view.pt index 5b11405..5c23d4e 100644 --- a/src/cs_dynamicpages/views/query_columns_view.pt +++ b/src/cs_dynamicpages/views/query_columns_view.pt @@ -1,6 +1,8 @@ -
    +

    ${context/Title}

    @@ -11,21 +13,32 @@
    + " + tal:condition="image" + tal:on-error="nothing" + > + title=element.Title())" + />
    - ${element/Title} -

    -

    + ${element/Title} +

    +

    ${element/Description}

    @@ -36,7 +49,9 @@
    -
    \ No newline at end of file +
    diff --git a/src/cs_dynamicpages/views/query_columns_view.py b/src/cs_dynamicpages/views/query_columns_view.py index 5ae8b6d..29a923a 100644 --- a/src/cs_dynamicpages/views/query_columns_view.py +++ b/src/cs_dynamicpages/views/query_columns_view.py @@ -1,8 +1,9 @@ # from cs_dynamicpages import _ +from cs_dynamicpages.views.dynamic_page_row_view import DynamicPageRowView from plone.app.contenttypes.browser.collection import CollectionView from zope.interface import implementer from zope.interface import Interface -from cs_dynamicpages.views.dynamic_page_row_view import DynamicPageRowView + # from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile diff --git a/src/cs_dynamicpages/views/slider_view.pt b/src/cs_dynamicpages/views/slider_view.pt index bad68cb..7892d95 100644 --- a/src/cs_dynamicpages/views/slider_view.pt +++ b/src/cs_dynamicpages/views/slider_view.pt @@ -1,44 +1,74 @@ - \ No newline at end of file +
    diff --git a/src/cs_dynamicpages/views/slider_view.py b/src/cs_dynamicpages/views/slider_view.py index ca22b3e..1ce0fd2 100644 --- a/src/cs_dynamicpages/views/slider_view.py +++ b/src/cs_dynamicpages/views/slider_view.py @@ -1,8 +1,8 @@ # from cs_dynamicpages import _ +from cs_dynamicpages.views.dynamic_page_row_view import DynamicPageRowView from plone import api from zope.interface import implementer from zope.interface import Interface -from cs_dynamicpages.views.dynamic_page_row_view import DynamicPageRowView class ISliderView(Interface): diff --git a/src/cs_dynamicpages/views/spacer_view.pt b/src/cs_dynamicpages/views/spacer_view.pt index ae4bf27..903b036 100644 --- a/src/cs_dynamicpages/views/spacer_view.pt +++ b/src/cs_dynamicpages/views/spacer_view.pt @@ -1,2 +1,2 @@
    -
    \ No newline at end of file + diff --git a/src/cs_dynamicpages/views/text_view.pt b/src/cs_dynamicpages/views/text_view.pt index c739948..fd65f8d 100644 --- a/src/cs_dynamicpages/views/text_view.pt +++ b/src/cs_dynamicpages/views/text_view.pt @@ -1,4 +1,6 @@
    - - -
    \ No newline at end of file + + + diff --git a/src/cs_dynamicpages/vocabularies/configure.zcml b/src/cs_dynamicpages/vocabularies/configure.zcml index 4db2909..74e9b6b 100644 --- a/src/cs_dynamicpages/vocabularies/configure.zcml +++ b/src/cs_dynamicpages/vocabularies/configure.zcml @@ -2,16 +2,16 @@ - + - + list: }, ] + @pytest.fixture() def portal(functional): return functional["portal"] @@ -58,19 +59,19 @@ def portal(functional): @pytest.fixture() def browser(functional): - browser = Browser(functional['app']) + browser = Browser(functional["app"]) browser.handleErrors = False - browser.addHeader( - "Authorization", f"Basic {SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}" - ) + browser.addHeader("Authorization", f"Basic {SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}") return browser + @pytest.fixture() def my_request(functional): - req = functional['request'] + req = functional["request"] alsoProvides(req, IBrowserLayer) return req + @pytest.fixture def create_contents(contents): """Helper fixture to create initial content.""" diff --git a/tests/content/test_content_operations.py b/tests/content/test_content_operations.py index dc90db9..9ed19fb 100644 --- a/tests/content/test_content_operations.py +++ b/tests/content/test_content_operations.py @@ -1,11 +1,11 @@ -import transaction +from ..base import TestBase from plone import api + import pytest -from ..base import TestBase +import transaction class TestContent(TestBase): - @pytest.fixture(autouse=True) def create_content(self, portal): with api.env.adopt_roles(["Manager"]): @@ -18,7 +18,7 @@ def create_content(self, portal): assert self.folder is not None assert self.folder.id == "folder" - self.folder.setLayout('dynamic-view') + self.folder.setLayout("dynamic-view") self.dpf = api.content.create( container=self.folder, type="DynamicPageFolder", id="dpf", title="DPF" @@ -40,21 +40,22 @@ def create_content(self, portal): assert self.row2.id == "row-2" transaction.commit() + def test_view(self, browser): - """ check that the folder is rendered correctly with the basic instructions """ + """check that the folder is rendered correctly with the basic instructions""" browser.open(self.folder.absolute_url()) # We have 2 rows, so there must be an option to delete a row - assert 'Delete row' in browser.contents + assert "Delete row" in browser.contents # assert "Row 1" in browser.contents # assert "Row 2" in browser.contents # There must be an option to add a new row - assert 'Add new row' in browser.contents + assert "Add new row" in browser.contents def test_add_row(self, browser): - """ click add row""" + """click add row""" browser.open(self.folder.absolute_url()) link = browser.getLink("Add new row") link.click() diff --git a/tests/setup/test_setup_install.py b/tests/setup/test_setup_install.py index ebd4363..77abfa1 100644 --- a/tests/setup/test_setup_install.py +++ b/tests/setup/test_setup_install.py @@ -2,10 +2,7 @@ from cs_dynamicpages import PACKAGE_NAME - class TestSetupInstall(TestBase): - - def test_addon_installed(self, installer): """Test if cs_dynamicpages is installed.""" assert installer.is_product_installed(PACKAGE_NAME) is True From ded2edc019016130e314959f9cc66799f661f988 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 17 Jul 2025 15:21:41 +0200 Subject: [PATCH 05/24] lint --- tests/base.py | 3 +- tests/content/conftest.py | 2 +- tests/content/test.html | 1895 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1898 insertions(+), 2 deletions(-) create mode 100644 tests/content/test.html diff --git a/tests/base.py b/tests/base.py index 0491fe8..d758a3e 100644 --- a/tests/base.py +++ b/tests/base.py @@ -15,5 +15,6 @@ def installed(self, portal, installer): """ try: installer.install_product(PACKAGE_NAME) - except: + except Exception as e: + logger.info(e) logger.info("Package already installed") diff --git a/tests/content/conftest.py b/tests/content/conftest.py index 4537134..ea2cf40 100644 --- a/tests/content/conftest.py +++ b/tests/content/conftest.py @@ -114,7 +114,7 @@ def func(portal) -> dict: def portal_with_content(app, portal, create_contents): """Plone portal with initial content.""" with api.env.adopt_roles(["Manager"]): - content_ids = create_contents(portal) + create_contents(portal) # transaction.commit() yield portal # with api.env.adopt_roles(["Manager"]): diff --git a/tests/content/test.html b/tests/content/test.html new file mode 100644 index 0000000..eb08124 --- /dev/null +++ b/tests/content/test.html @@ -0,0 +1,1895 @@ + + + + + + folder — Plone site + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    + + +
    +
    + + + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    + + Template: horizontal-rule-view + + + Edit + Delete row + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + Template: horizontal-rule-view + + + Edit + Delete row + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + Template: horizontal-rule-view + + + Edit + Delete row + +
    + +
    + +
    +
    +
    +
    + + + + + +
    +
    + + +
    +
    +
    + + +
    + + +
    + + + + + From 0016a8527256a3a22417473ef5dcb65f6af3b267 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 18 Jul 2025 11:38:48 +0200 Subject: [PATCH 06/24] remove unneeded file --- tests/content/test.html | 1895 --------------------------------------- 1 file changed, 1895 deletions(-) delete mode 100644 tests/content/test.html diff --git a/tests/content/test.html b/tests/content/test.html deleted file mode 100644 index eb08124..0000000 --- a/tests/content/test.html +++ /dev/null @@ -1,1895 +0,0 @@ - - - - - - folder — Plone site - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - -
    - - -
    -
    - - - -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    - - Template: horizontal-rule-view - - - Edit - Delete row - -
    - -
    - -
    -
    -
    -
    - -
    -
    - - Template: horizontal-rule-view - - - Edit - Delete row - -
    - -
    - -
    -
    -
    -
    - -
    -
    - - Template: horizontal-rule-view - - - Edit - Delete row - -
    - -
    - -
    -
    -
    -
    - - - - - -
    -
    - - -
    -
    -
    - - -
    - - -
    - - - - - From 287f41421903bc08246242ad1c2a2bb4c7b67551 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 18 Jul 2025 11:39:42 +0200 Subject: [PATCH 07/24] add namespaces --- src/cs_dynamicpages/views/accordion_view.pt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cs_dynamicpages/views/accordion_view.pt b/src/cs_dynamicpages/views/accordion_view.pt index c638c84..196e04b 100644 --- a/src/cs_dynamicpages/views/accordion_view.pt +++ b/src/cs_dynamicpages/views/accordion_view.pt @@ -1,4 +1,8 @@ -
    Install environment$(RESET)" - @uv venv --python=3.10 $(VENV_FOLDER) + @uv venv --python=$(python-version) $(VENV_FOLDER) @uv pip install -r requirements-mxdev.txt .PHONY: sync From 4b8af14460b596d2a110cebbf6d340c7077e083e Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 18 Jul 2025 12:25:56 +0200 Subject: [PATCH 09/24] Revert "adjust python version to run tests" This reverts commit b508c790e057618a5d833c33ce5c2fa4e120cad3. --- Makefile | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Makefile b/Makefile index a82064a..c80356e 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,6 @@ else PLONE_VERSION := 6.1.2 endif -ifdef python-version -python-version := $(python-version) -else -python-version := 3.10 -endif - VENV_FOLDER=$(BACKEND_FOLDER)/.venv export VIRTUAL_ENV=$(VENV_FOLDER) BIN_FOLDER=$(VENV_FOLDER)/bin @@ -60,7 +54,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" - @uv venv --python=$(python-version) $(VENV_FOLDER) + @uv venv --python=3.10 $(VENV_FOLDER) @uv pip install -r requirements-mxdev.txt .PHONY: sync From 751f6c3f69b764f3e8dea232f103bf23642f801d Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 18 Jul 2025 12:27:57 +0200 Subject: [PATCH 10/24] don't be explicit about the python version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c80356e..991eda2 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" - @uv venv --python=3.10 $(VENV_FOLDER) + @uv venv $(VENV_FOLDER) @uv pip install -r requirements-mxdev.txt .PHONY: sync From c0948daa3ba8683b6379585a772e2de9f4f8166a Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:07:58 +0200 Subject: [PATCH 11/24] fix: set python version in Makefile, and let it come from environment, to be able to run CI in GHA --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 991eda2..4699031 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,13 @@ else PLONE_VERSION := 6.1.2 endif +ifdef python-version +PYTHON_VERSION := $(python-version) +else +PYTHON_VERSION := 3.10 +endif + + VENV_FOLDER=$(BACKEND_FOLDER)/.venv export VIRTUAL_ENV=$(VENV_FOLDER) BIN_FOLDER=$(VENV_FOLDER)/bin @@ -54,7 +61,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" - @uv venv $(VENV_FOLDER) + @uv venv --python= ${PYTHON_VERSION} $(VENV_FOLDER) @uv pip install -r requirements-mxdev.txt .PHONY: sync From 0a2b0b8c248d1fd6f4dbfe67f26d002f796ba876 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:08:58 +0200 Subject: [PATCH 12/24] fix syntax --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4699031..8c49288 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" - @uv venv --python= ${PYTHON_VERSION} $(VENV_FOLDER) + @uv venv --python ${PYTHON_VERSION} $(VENV_FOLDER) @uv pip install -r requirements-mxdev.txt .PHONY: sync From 7cb40af37f7e669a8b284518dcef053461f8eed9 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:13:37 +0200 Subject: [PATCH 13/24] explicitely run on the created venv --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8c49288..19fc366 100644 --- a/Makefile +++ b/Makefile @@ -62,12 +62,12 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" @uv venv --python ${PYTHON_VERSION} $(VENV_FOLDER) - @uv pip install -r requirements-mxdev.txt + @uv $(VENV_FOLDER)/bin/pip install -r requirements-mxdev.txt .PHONY: sync sync: $(VENV_FOLDER) ## Sync project dependencies @echo "$(GREEN)==> Sync project dependencies$(RESET)" - @uv pip install -r requirements-mxdev.txt + @uv $(VENV_FOLDER)/pip install -r requirements-mxdev.txt instance/etc/zope.ini instance/etc/zope.conf: instance.yaml ## Create instance configuration @echo "$(GREEN)==> Create instance configuration$(RESET)" From f025f4b76e5880557ea74e9a8eb4ac04d296b195 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:14:31 +0200 Subject: [PATCH 14/24] restore --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 19fc366..8c49288 100644 --- a/Makefile +++ b/Makefile @@ -62,12 +62,12 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" @uv venv --python ${PYTHON_VERSION} $(VENV_FOLDER) - @uv $(VENV_FOLDER)/bin/pip install -r requirements-mxdev.txt + @uv pip install -r requirements-mxdev.txt .PHONY: sync sync: $(VENV_FOLDER) ## Sync project dependencies @echo "$(GREEN)==> Sync project dependencies$(RESET)" - @uv $(VENV_FOLDER)/pip install -r requirements-mxdev.txt + @uv pip install -r requirements-mxdev.txt instance/etc/zope.ini instance/etc/zope.conf: instance.yaml ## Create instance configuration @echo "$(GREEN)==> Create instance configuration$(RESET)" From 6c55e0c40e9345f4a3927c400fb5bf093d535455 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:17:46 +0200 Subject: [PATCH 15/24] change env var check --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8c49288..d6e9ba0 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ else PLONE_VERSION := 6.1.2 endif -ifdef python-version +ifdef $(python-version) PYTHON_VERSION := $(python-version) else PYTHON_VERSION := 3.10 From 5ff07d8b83af877dbbb2d1c604fca3c0db52c671 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:23:21 +0200 Subject: [PATCH 16/24] explicitely activate the venv --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d6e9ba0..d398e76 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,6 @@ else PYTHON_VERSION := 3.10 endif - VENV_FOLDER=$(BACKEND_FOLDER)/.venv export VIRTUAL_ENV=$(VENV_FOLDER) BIN_FOLDER=$(VENV_FOLDER)/bin @@ -62,6 +61,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" @uv venv --python ${PYTHON_VERSION} $(VENV_FOLDER) + @source .venv/bin/activate @uv pip install -r requirements-mxdev.txt .PHONY: sync From cdef0ad9eae982b41a1bbd81b3c95eff9fef7c5a Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:25:04 +0200 Subject: [PATCH 17/24] do not be exoplicit --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d398e76..af4743a 100644 --- a/Makefile +++ b/Makefile @@ -60,8 +60,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file $(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies @echo "$(GREEN)==> Install environment$(RESET)" - @uv venv --python ${PYTHON_VERSION} $(VENV_FOLDER) - @source .venv/bin/activate + @uv venv $(VENV_FOLDER) @uv pip install -r requirements-mxdev.txt .PHONY: sync From c792347cd348de08589cfe891e0d10d669ef45ac Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:29:58 +0200 Subject: [PATCH 18/24] ignore python versions --- Makefile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Makefile b/Makefile index af4743a..991eda2 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,6 @@ else PLONE_VERSION := 6.1.2 endif -ifdef $(python-version) -PYTHON_VERSION := $(python-version) -else -PYTHON_VERSION := 3.10 -endif - VENV_FOLDER=$(BACKEND_FOLDER)/.venv export VIRTUAL_ENV=$(VENV_FOLDER) BIN_FOLDER=$(VENV_FOLDER)/bin From 1defe85b350a55629afd92648805b5c78e388b30 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:33:06 +0200 Subject: [PATCH 19/24] adjust deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 04c36e3..e8b66c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "Products.CMFPlone", "plone.api", "collective.z3cform.datagridfield", - "z3c.jbot", + "plone.app.z3cform", ] [project.optional-dependencies] From ddcac3dd057b5f4181bba1d4af5da743d091cf1f Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 24 Jul 2025 14:35:02 +0200 Subject: [PATCH 20/24] remove dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8b66c9..65913da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ dependencies = [ "Products.CMFPlone", "plone.api", "collective.z3cform.datagridfield", - "plone.app.z3cform", ] [project.optional-dependencies] From 160b92f8bad98e0887ea5d39d15963d9e451cae6 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 25 Jul 2025 18:50:34 +0200 Subject: [PATCH 21/24] feat: make browser view registrations conditional on being Plone 6.1 because they are using a feature only available on Plone 6.1 --- src/cs_dynamicpages/views/configure.zcml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cs_dynamicpages/views/configure.zcml b/src/cs_dynamicpages/views/configure.zcml index 9fa7921..931c9f1 100644 --- a/src/cs_dynamicpages/views/configure.zcml +++ b/src/cs_dynamicpages/views/configure.zcml @@ -2,6 +2,7 @@ xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:plone="http://namespaces.plone.org/plone" + xmlns:zcml="http://namespaces.zope.org/zcml" i18n_domain="cs_dynamicpages" > @@ -54,11 +55,13 @@ @@ -98,6 +102,7 @@ From eaac651fd92da8607bd8a4ad07ecdbdf42dbba9d Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 25 Jul 2025 18:51:03 +0200 Subject: [PATCH 22/24] feat: support Plone 6.0 configuring the RelatedItems widget --- src/cs_dynamicpages/behaviors/related_image.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cs_dynamicpages/behaviors/related_image.py b/src/cs_dynamicpages/behaviors/related_image.py index 4326972..792b13b 100644 --- a/src/cs_dynamicpages/behaviors/related_image.py +++ b/src/cs_dynamicpages/behaviors/related_image.py @@ -1,4 +1,4 @@ -from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget +# from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget from plone.autoform import directives as form from plone.autoform.interfaces import IFormFieldProvider from plone.supermodel import model @@ -11,6 +11,13 @@ from zope.interface import Interface from zope.interface import provider +try: + # This is for Plone 6.1 + from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget as RelatedImageFieldWidget +except: + # This is for previous versions of Plone + from plone.app.z3cform.widgets.relateditems import RelatedItemsFieldWidget as RelatedImageFieldWidget + class IRelatedImageMarker(Interface): pass @@ -30,7 +37,7 @@ class IRelatedImage(model.Schema): form.widget( "related_image", - ContentBrowserFieldWidget, + RelatedImageFieldWidget, vocabulary="plone.app.vocabularies.Catalog", pattern_options={ "recentlyUsed": True, From d734e406ee05137bde5e8434dd44cd079560c19a Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 25 Jul 2025 18:55:44 +0200 Subject: [PATCH 23/24] internal: lint and format --- src/cs_dynamicpages/behaviors/related_image.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/cs_dynamicpages/behaviors/related_image.py b/src/cs_dynamicpages/behaviors/related_image.py index 792b13b..afcf6d3 100644 --- a/src/cs_dynamicpages/behaviors/related_image.py +++ b/src/cs_dynamicpages/behaviors/related_image.py @@ -11,12 +11,17 @@ from zope.interface import Interface from zope.interface import provider + try: # This is for Plone 6.1 - from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget as RelatedImageFieldWidget -except: + from plone.app.z3cform.widgets.contentbrowser import ( + ContentBrowserFieldWidget as RelatedImageFieldWidget, + ) +except ImportError: # This is for previous versions of Plone - from plone.app.z3cform.widgets.relateditems import RelatedItemsFieldWidget as RelatedImageFieldWidget + from plone.app.z3cform.widgets.relateditems import ( + RelatedItemsFieldWidget as RelatedImageFieldWidget, + ) class IRelatedImageMarker(Interface): From 2a6a7a6d64265664086f4c31b85239f964bdf271 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 25 Jul 2025 18:57:06 +0200 Subject: [PATCH 24/24] internal: add releasing features --- Makefile | 6 ++++++ pyproject.toml | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/Makefile b/Makefile index 991eda2..72b1955 100644 --- a/Makefile +++ b/Makefile @@ -132,3 +132,9 @@ test-coverage: $(VENV_FOLDER) ## run tests with coverage ## Add bobtemplates features (check bobtemplates.plone's documentation to get the list of available features) add: $(VENV_FOLDER) @uvx plonecli add -b .mrbob.ini $(filter-out $@,$(MAKECMDGOALS)) + +.PHONY: release +release: $(VENV_FOLDER) ## Create a release + @echo "$(GREEN)==> Create a release$(RESET)" + @uv pip install -e ".[release]" + @uv run fullrelease \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 65913da..811ef4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,11 @@ test = [ "pytest-cov", "pytest-plone>=0.5.0", ] +release = [ + "zest.releaser[recommended]", + "zestreleaser.towncrier", + "zest.pocompile", +] [project.urls] Homepage = "https://github.com/collective/cs_dynamicpages" @@ -169,3 +174,7 @@ source_pkgs = ["cs_dynamicpages", "tests"] branch = true parallel = true omit = ["src/cs_dynamicpages/locales/*.py"] + + +[tool.zest-releaser] +python-file-with-version = "src/cs_dynamicpages/__init__.py"