From 9befbf945533440565a5e8fe1663af82aa2e58cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Wed, 23 Sep 2026 15:19:22 -0300 Subject: [PATCH 1/3] Handle blocks without image_scales in _fix_image_paths (#109) Grid block images exported without image_scales (missing or null), with an empty scales list, or without a scales key no longer break export and import. Closes #109 --- news/109.bugfix | 1 + .../exportimport/utils/content/blocks.py | 6 +- tests/utils/test_utils_content_blocks.py | 66 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 news/109.bugfix create mode 100644 tests/utils/test_utils_content_blocks.py diff --git a/news/109.bugfix b/news/109.bugfix new file mode 100644 index 0000000..3fc5c80 --- /dev/null +++ b/news/109.bugfix @@ -0,0 +1 @@ +Fixed export and import of grid blocks whose images have no ``image_scales`` information. @ericof diff --git a/src/plone/exportimport/utils/content/blocks.py b/src/plone/exportimport/utils/content/blocks.py index 8625958..f1007bc 100644 --- a/src/plone/exportimport/utils/content/blocks.py +++ b/src/plone/exportimport/utils/content/blocks.py @@ -6,11 +6,13 @@ def _fix_image_paths(data: list) -> list[dict]: """ parsed = [] for info in data: - image_scales = info["image_scales"] + image_scales = info.get("image_scales") or {} for field in image_scales: + if not image_scales[field]: + continue field_data = image_scales[field][0] field_data["download"] = f"@@images/{field}" - for key, scale in field_data["scales"].items(): + for key, scale in field_data.get("scales", {}).items(): scale["download"] = f"@@images/{field}/{key}" parsed.append(info) return parsed diff --git a/tests/utils/test_utils_content_blocks.py b/tests/utils/test_utils_content_blocks.py new file mode 100644 index 0000000..cc47793 --- /dev/null +++ b/tests/utils/test_utils_content_blocks.py @@ -0,0 +1,66 @@ +from plone.exportimport.utils.content import blocks + +import pytest + + +def _image_info(**extra) -> dict: + info = { + "@id": "/image", + "image_scales": { + "image": [ + { + "download": "http://localhost/image/@@images/image-800-abc.png", + "scales": { + "thumb": { + "download": "http://localhost/image/@@images/thumb.png" + }, + }, + } + ] + }, + } + info.update(extra) + return info + + +def test_fix_image_paths(): + func = blocks._fix_image_paths + result = func([_image_info()]) + field_data = result[0]["image_scales"]["image"][0] + assert field_data["download"] == "@@images/image" + assert field_data["scales"]["thumb"]["download"] == "@@images/image/thumb" + + +@pytest.mark.parametrize( + "info", + [ + {"@id": "/image"}, + {"@id": "/image", "image_scales": None}, + {"@id": "/image", "image_scales": {}}, + {"@id": "/image", "image_scales": {"image": []}}, + {"@id": "/image", "image_scales": {"image": [{"download": "foo.png"}]}}, + ], +) +def test_fix_image_paths_incomplete_data(info: dict): + func = blocks._fix_image_paths + result = func([info]) + assert result == [info] + + +@pytest.mark.parametrize("block_type", ["__grid", "grid"]) +def test_parse_blocks_grid_without_image_scales(block_type: str): + data = { + "block-1": { + "@type": block_type, + "columns": [ + {"@type": "teaser", "preview_image": [{"@id": "/image"}]}, + {"@type": "image", "image": [_image_info()]}, + {"@type": "slate"}, + ], + } + } + result = blocks.parse_blocks(data) + columns = result["block-1"]["columns"] + assert columns[0]["preview_image"] == [{"@id": "/image"}] + field_data = columns[1]["image"][0]["image_scales"]["image"][0] + assert field_data["download"] == "@@images/image" From 651deafcc5c54c6ba321d43082593c0f974c771d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Wed, 23 Sep 2026 16:40:54 -0300 Subject: [PATCH 2/3] Run CLI tools without adopt_roles and with the site hook (#110) Under adopt_roles(["Manager"]), a permission check only succeeds when the permission is granted to Manager, bypassing the unrestricted system user set up by get_app. Fields whose read permission is granted only to other roles (e.g. Anonymous and Authenticated, as for the header settings behavior of sc.voltolighttheme on the Plone Site root) were silently dropped from exports. Both exporter_cli and importer_cli now run inside hooks.site(site) without overriding roles. get_site is annotated as returning PloneSite, since it exits when the site is not found. Tests: - New testing profile with a DummyContent type and a dummy_settings behavior on the Plone Site, with fields protected by custom read and write permissions. - CLI tests for exporter and importer, running on the functional layer. Closes #110 --- news/110.bugfix | 1 + src/plone/exportimport/cli/__init__.py | 5 +- src/plone/exportimport/testing/__init__.py | 3 + src/plone/exportimport/testing/configure.zcml | 43 +++++++++ .../exportimport/testing/content/__init__.py | 0 .../exportimport/testing/content/dummy.py | 43 +++++++++ .../testing/profiles/testing/metadata.xml | 5 + .../testing/profiles/testing/rolemap.xml | 24 +++++ .../testing/profiles/testing/types.xml | 11 +++ .../profiles/testing/types/DummyContent.xml | 53 +++++++++++ .../profiles/testing/types/Plone_Site.xml | 11 +++ src/plone/exportimport/utils/cli.py | 2 +- .../data.json | 34 +++++++ .../cli_import/content/__metadata__.json | 14 +++ .../content/plone_site_root/data.json | 28 ++++++ tests/cli/conftest.py | 94 +++++++++++++++++++ tests/cli/test_cli_exporter.py | 51 ++++++++++ tests/cli/test_cli_importer.py | 48 ++++++++++ 18 files changed, 466 insertions(+), 4 deletions(-) create mode 100644 news/110.bugfix create mode 100644 src/plone/exportimport/testing/configure.zcml create mode 100644 src/plone/exportimport/testing/content/__init__.py create mode 100644 src/plone/exportimport/testing/content/dummy.py create mode 100644 src/plone/exportimport/testing/profiles/testing/metadata.xml create mode 100644 src/plone/exportimport/testing/profiles/testing/rolemap.xml create mode 100644 src/plone/exportimport/testing/profiles/testing/types.xml create mode 100644 src/plone/exportimport/testing/profiles/testing/types/DummyContent.xml create mode 100644 src/plone/exportimport/testing/profiles/testing/types/Plone_Site.xml create mode 100644 tests/_resources/cli_import/content/0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b/data.json create mode 100644 tests/_resources/cli_import/content/__metadata__.json create mode 100644 tests/_resources/cli_import/content/plone_site_root/data.json create mode 100644 tests/cli/conftest.py create mode 100644 tests/cli/test_cli_exporter.py create mode 100644 tests/cli/test_cli_importer.py diff --git a/news/110.bugfix b/news/110.bugfix new file mode 100644 index 0000000..42b0b20 --- /dev/null +++ b/news/110.bugfix @@ -0,0 +1 @@ +Fixed ``plone-exporter`` silently skipping fields protected by a read permission not granted to the ``Manager`` role, such as behavior fields on the Plone Site root. Both command line tools now run with the site hook set and without overriding the roles of the system user. @ericof diff --git a/src/plone/exportimport/cli/__init__.py b/src/plone/exportimport/cli/__init__.py index 1cf59a4..f7c57c3 100644 --- a/src/plone/exportimport/cli/__init__.py +++ b/src/plone/exportimport/cli/__init__.py @@ -1,4 +1,3 @@ -from plone import api from plone.exportimport import logger as package_logger from plone.exportimport.exporters import get_exporter from plone.exportimport.importers import get_importer @@ -54,7 +53,7 @@ def exporter_cli(args=sys.argv): logger.error(f"{namespace.path} does not exist, please create it first.") sys.exit(1) site = cli_helpers.get_site(app, namespace.site, logger) - with api.env.adopt_roles(["Manager"]): + with hooks.site(site): results = get_exporter(site).export_site(path, options=namespace) logger.info(f" Using path {path} to export content from Plone site at /{site.id}") for item in results[1:]: @@ -75,7 +74,7 @@ def importer_cli(args=sys.argv): if not namespace.quiet: cli_helpers.setup_logger_console(package_logger) site = cli_helpers.get_site(app, namespace.site, logger) - with hooks.site(site), api.env.adopt_roles(["Manager"]): + with hooks.site(site): logger.info(f" Using path {path} to import content to Plone site at /{site.id}") results = get_importer(site).import_site(path) for item in results: diff --git a/src/plone/exportimport/testing/__init__.py b/src/plone/exportimport/testing/__init__.py index 30f736c..1d8cac1 100644 --- a/src/plone/exportimport/testing/__init__.py +++ b/src/plone/exportimport/testing/__init__.py @@ -10,14 +10,17 @@ class ExportImportLayer(PloneSandboxLayer): def setUpZope(self, app, configurationContext): import plone.exportimport + import plone.exportimport.testing import plone.volto self.loadZCML(package=plone.volto) self.loadZCML(package=plone.exportimport) + self.loadZCML(package=plone.exportimport.testing) def setUpPloneSite(self, portal): st = portal.portal_setup st.runAllImportStepsFromProfile("plone.volto:default") + st.runAllImportStepsFromProfile("plone.exportimport.testing:testing") # Enable plone.constraintypes behavior, # which is not enabled by default in plone.volto diff --git a/src/plone/exportimport/testing/configure.zcml b/src/plone/exportimport/testing/configure.zcml new file mode 100644 index 0000000..6416aa9 --- /dev/null +++ b/src/plone/exportimport/testing/configure.zcml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + diff --git a/src/plone/exportimport/testing/content/__init__.py b/src/plone/exportimport/testing/content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/plone/exportimport/testing/content/dummy.py b/src/plone/exportimport/testing/content/dummy.py new file mode 100644 index 0000000..84b5cd4 --- /dev/null +++ b/src/plone/exportimport/testing/content/dummy.py @@ -0,0 +1,43 @@ +from plone.autoform import directives +from plone.autoform.interfaces import IFormFieldProvider +from plone.dexterity.content import Container +from plone.supermodel.model import Schema +from zope import schema +from zope.interface import implementer +from zope.interface import provider + + +class IDummyContent(Schema): + """Schema with fields protected by custom read and write permissions.""" + + secure_field = schema.TextLine( + title="Secure field", + description="Secure field.", + required=False, + ) + + secure_setting = schema.Bool( + title="Secure Setting", + description="Secure setting.", + required=False, + default=True, + ) + + directives.read_permission( + secure_field="plone.exportimport.testing.dummy.view", + secure_setting="plone.exportimport.testing.dummy.view", + ) + directives.write_permission( + secure_field="plone.exportimport.testing.dummy.edit", + secure_setting="plone.exportimport.testing.dummy.edit", + ) + + +@provider(IFormFieldProvider) +class IDummySettings(IDummyContent): + """Behavior exposing the protected fields, used on the Plone Site root.""" + + +@implementer(IDummyContent) +class DummyContent(Container): + """Dexterity container that represents a dummy content.""" diff --git a/src/plone/exportimport/testing/profiles/testing/metadata.xml b/src/plone/exportimport/testing/profiles/testing/metadata.xml new file mode 100644 index 0000000..971b284 --- /dev/null +++ b/src/plone/exportimport/testing/profiles/testing/metadata.xml @@ -0,0 +1,5 @@ + + + 1000 + + diff --git a/src/plone/exportimport/testing/profiles/testing/rolemap.xml b/src/plone/exportimport/testing/profiles/testing/rolemap.xml new file mode 100644 index 0000000..b3b130a --- /dev/null +++ b/src/plone/exportimport/testing/profiles/testing/rolemap.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/plone/exportimport/testing/profiles/testing/types.xml b/src/plone/exportimport/testing/profiles/testing/types.xml new file mode 100644 index 0000000..f7b097a --- /dev/null +++ b/src/plone/exportimport/testing/profiles/testing/types.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/src/plone/exportimport/testing/profiles/testing/types/DummyContent.xml b/src/plone/exportimport/testing/profiles/testing/types/DummyContent.xml new file mode 100644 index 0000000..38610ee --- /dev/null +++ b/src/plone/exportimport/testing/profiles/testing/types/DummyContent.xml @@ -0,0 +1,53 @@ + + + + + Dummy + Dummy content + + True + DummyContent + + + True + + + + True + + + plone.exportimport.testing.dummy.add + plone.exportimport.testing.content.dummy.DummyContent + plone.exportimport.testing.content.dummy.IDummyContent + + + + + + + + + + + + + + + string:${folder_url}/++add++DummyContent + view + view + False + + + + diff --git a/src/plone/exportimport/testing/profiles/testing/types/Plone_Site.xml b/src/plone/exportimport/testing/profiles/testing/types/Plone_Site.xml new file mode 100644 index 0000000..783f8f3 --- /dev/null +++ b/src/plone/exportimport/testing/profiles/testing/types/Plone_Site.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/plone/exportimport/utils/cli.py b/src/plone/exportimport/utils/cli.py index 3c62991..2df623a 100644 --- a/src/plone/exportimport/utils/cli.py +++ b/src/plone/exportimport/utils/cli.py @@ -59,7 +59,7 @@ def get_app(zopeconf: Path): return app -def get_site(app, site_id: str, logger: logging.Logger) -> PloneSite | None: +def get_site(app, site_id: str, logger: logging.Logger) -> PloneSite: """Get Plone Site""" site = app.unrestrictedTraverse(site_id, None) if not site: diff --git a/tests/_resources/cli_import/content/0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b/data.json b/tests/_resources/cli_import/content/0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b/data.json new file mode 100644 index 0000000..6a64585 --- /dev/null +++ b/tests/_resources/cli_import/content/0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b/data.json @@ -0,0 +1,34 @@ +{ + "@id": "/dummy", + "@type": "DummyContent", + "UID": "0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b", + "allow_discussion": null, + "blocks": {}, + "blocks_layout": { + "items": [] + }, + "contributors": [], + "created": "2026-09-23T18:00:00+00:00", + "creators": [ + "admin" + ], + "description": "", + "exportimport.constrains": {}, + "exportimport.conversation": [], + "exportimport.versions": {}, + "id": "dummy", + "is_folderish": true, + "language": "##DEFAULT##", + "modified": "2026-09-23T18:00:00+00:00", + "parent": { + "@id": "/Plone", + "@type": "Plone Site", + "UID": "plone_site_root" + }, + "review_state": "private", + "secure_field": "A secure value", + "secure_setting": false, + "title": "A Dummy content", + "type_title": "Dummy", + "workflow_history": {} +} diff --git a/tests/_resources/cli_import/content/__metadata__.json b/tests/_resources/cli_import/content/__metadata__.json new file mode 100644 index 0000000..cf1dcb5 --- /dev/null +++ b/tests/_resources/cli_import/content/__metadata__.json @@ -0,0 +1,14 @@ +{ + "__version__": "1.0.0", + "_blob_files_": [], + "_data_files_": [ + "plone_site_root/data.json", + "0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b/data.json" + ], + "default_page": {}, + "local_permissions": {}, + "local_roles": {}, + "ordering": { + "0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b": 0 + } +} diff --git a/tests/_resources/cli_import/content/plone_site_root/data.json b/tests/_resources/cli_import/content/plone_site_root/data.json new file mode 100644 index 0000000..ef5a7e5 --- /dev/null +++ b/tests/_resources/cli_import/content/plone_site_root/data.json @@ -0,0 +1,28 @@ +{ + "@id": "/Plone", + "@type": "Plone Site", + "UID": "plone_site_root", + "allow_discussion": null, + "blocks": {}, + "blocks_layout": { + "items": [] + }, + "contributors": [], + "creators": [ + "admin" + ], + "description": "", + "exportimport.constrains": {}, + "exportimport.conversation": [], + "exportimport.versions": {}, + "id": "Plone", + "is_folderish": true, + "language": "##DEFAULT##", + "parent": {}, + "review_state": null, + "secure_field": "A secure site value", + "secure_setting": false, + "title": "Site", + "type_title": "Plone Site", + "workflow_history": {} +} diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py new file mode 100644 index 0000000..f7d0798 --- /dev/null +++ b/tests/cli/conftest.py @@ -0,0 +1,94 @@ +from collections.abc import Callable +from pathlib import Path +from plone import api +from plone.exportimport.utils import cli as cli_helpers +from zope.component import hooks +from zope.globalrequest import getRequest +from zope.globalrequest import setRequest + +import pytest +import Zope2 + + +@pytest.fixture() +def app(functional): + """Zope application from the functional layer. + + ``importer_cli`` commits the transaction, so these tests need the + per-test storage isolation of the functional layer. + """ + return functional["app"] + + +@pytest.fixture() +def portal(functional): + """Plone Site from the functional layer.""" + return functional["portal"] + + +@pytest.fixture() +def cli_import_path() -> Path: + """Import Path with values for permission-protected fields.""" + return Path(__file__).parent.parent / "_resources" / "cli_import" + + +@pytest.fixture() +def zopeconf(tmp_path) -> Path: + """Placeholder zope.conf, never parsed as WSGI startup is patched out.""" + path = tmp_path / "zope.conf" + path.write_text("") + return path + + +@pytest.fixture() +def patch_zope_startup(app, monkeypatch): + """Make the CLI use the test layer application instead of starting Zope.""" + monkeypatch.setattr(cli_helpers, "make_wsgi_app", lambda *args: None) + monkeypatch.setattr(Zope2, "app", lambda: app) + + +@pytest.fixture() +def run_cli(portal, patch_zope_startup) -> Callable: + """Run a CLI entry point the way a fresh process would. + + A fresh process has no local site hook set, so we clear it before + calling the CLI and restore it, and the global request, afterwards. + """ + + def func(cli: Callable, args: list[str]): + request = getRequest() + hooks.setSite(None) + try: + return cli(args) + finally: + hooks.setSite(portal) + setRequest(request) + + return func + + +@pytest.fixture() +def dummy_content(portal): + """DummyContent with values set on its permission-protected fields.""" + with api.env.adopt_roles(["Manager"]): + content = api.content.create( + container=portal, + type="DummyContent", + id="dummy", + title="A Dummy content", + secure_field="A secure value", + secure_setting=False, + ) + return content + + +@pytest.fixture() +def site_settings(portal): + """Plone Site with values set on the dummy_settings behavior fields.""" + from plone.exportimport.testing.content.dummy import IDummySettings + + with api.env.adopt_roles(["Manager"]): + settings = IDummySettings(portal) + settings.secure_field = "A secure site value" + settings.secure_setting = False + return portal diff --git a/tests/cli/test_cli_exporter.py b/tests/cli/test_cli_exporter.py new file mode 100644 index 0000000..98ca0ea --- /dev/null +++ b/tests/cli/test_cli_exporter.py @@ -0,0 +1,51 @@ +from plone.exportimport.cli import exporter_cli + +import pytest + + +class TestExporterCLI: + @pytest.fixture(autouse=True) + def _init(self, portal, dummy_content, run_cli, zopeconf, export_path, load_json): + self.portal = portal + self.uid = dummy_content.UID() + run_cli( + exporter_cli, + ["plone-exporter", str(zopeconf), portal.getId(), str(export_path)], + ) + self.data = load_json(export_path, f"content/{self.uid}/data.json") + + def test_content_exported(self): + assert self.data["@type"] == "DummyContent" + assert self.data["UID"] == self.uid + + @pytest.mark.parametrize( + "field,expected", + [ + ["secure_field", "A secure value"], + ["secure_setting", False], + ], + ) + def test_protected_field_value(self, field: str, expected): + assert field in self.data + assert self.data[field] == expected + + +class TestExporterCLISiteRoot: + @pytest.fixture(autouse=True) + def _init(self, site_settings, run_cli, zopeconf, export_path, load_json): + run_cli( + exporter_cli, + ["plone-exporter", str(zopeconf), site_settings.getId(), str(export_path)], + ) + self.data = load_json(export_path, "content/plone_site_root/data.json") + + @pytest.mark.parametrize( + "field,expected", + [ + ["secure_field", "A secure site value"], + ["secure_setting", False], + ], + ) + def test_protected_behavior_field_value(self, field: str, expected): + assert field in self.data + assert self.data[field] == expected diff --git a/tests/cli/test_cli_importer.py b/tests/cli/test_cli_importer.py new file mode 100644 index 0000000..12cddb6 --- /dev/null +++ b/tests/cli/test_cli_importer.py @@ -0,0 +1,48 @@ +from plone import api +from plone.exportimport.cli import importer_cli +from plone.exportimport.testing.content.dummy import IDummySettings + +import pytest + + +class TestImporterCLI: + @pytest.fixture(autouse=True) + def _init(self, portal, run_cli, zopeconf, cli_import_path): + self.portal = portal + run_cli( + importer_cli, + [ + "plone-importer", + str(zopeconf), + portal.getId(), + str(cli_import_path), + "--quiet", + ], + ) + + def test_content_imported(self): + content = api.content.get(UID="0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b") + assert content is not None + assert content.portal_type == "DummyContent" + + @pytest.mark.parametrize( + "field,expected", + [ + ["secure_field", "A secure value"], + ["secure_setting", False], + ], + ) + def test_protected_field_value(self, field: str, expected): + content = api.content.get(UID="0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b") + assert getattr(content, field) == expected + + @pytest.mark.parametrize( + "field,expected", + [ + ["secure_field", "A secure site value"], + ["secure_setting", False], + ], + ) + def test_protected_behavior_field_value(self, field: str, expected): + settings = IDummySettings(self.portal) + assert getattr(settings, field) == expected From 6663e67d2f4f1831ae235b5e665aa8a887b3442c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Wed, 23 Sep 2026 17:10:17 -0300 Subject: [PATCH 3/3] Fix test isolation with pytest-plone 1.1.0 and declare dependencies pytest-plone 1.1.0 keeps test layers set up for the whole session. Importers commit the transaction by default (intermediate_commits), and the portal_multilingual fixtures committed through transaction.manager, so imported content, users and groups leaked into later integration tests. Previously each layer was rebuilt between tests, which hid the leak. - Add an autouse fixture disabling importer intermediate commits. - Stop committing in the portal_multilingual fixtures. - Declare plone.autoform, plone.behavior, plone.supermodel and Products.GenericSetup, imported by the plone.exportimport.testing package. - Drop the license classifier from setup.py. --- news/+dependencies.internal | 1 + news/+pytest-plone.tests | 1 + setup.py | 5 ++++- tests/conftest.py | 12 ++++++++++++ tests/exporters/conftest.py | 4 +--- tests/importers/conftest.py | 4 +--- 6 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 news/+dependencies.internal create mode 100644 news/+pytest-plone.tests diff --git a/news/+dependencies.internal b/news/+dependencies.internal new file mode 100644 index 0000000..550fe3c --- /dev/null +++ b/news/+dependencies.internal @@ -0,0 +1 @@ +Declared ``plone.autoform``, ``plone.behavior``, ``plone.supermodel`` and ``Products.GenericSetup`` as dependencies. @ericof diff --git a/news/+pytest-plone.tests b/news/+pytest-plone.tests new file mode 100644 index 0000000..e23b522 --- /dev/null +++ b/news/+pytest-plone.tests @@ -0,0 +1 @@ +Fixed test isolation with pytest-plone 1.1.0 by keeping importers and fixtures from committing transactions during tests. @ericof diff --git a/setup.py b/setup.py index cf2350f..9bdbf9f 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,6 @@ "Framework :: Plone :: 6.2", "Framework :: Plone :: Core", "Framework :: Zope :: 5", - "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", @@ -50,14 +49,18 @@ "plone.app.redirector", "plone.app.textfield", "plone.app.users", + "plone.autoform", "plone.base", + "plone.behavior", "plone.dexterity", "plone.namedfile", "plone.restapi", + "plone.supermodel", "plone.uuid", "Products.CMFEditions", "Products.CMFPlone", "Products.DCWorkflow", + "Products.GenericSetup", "Products.PlonePAS", "Products.PortalTransforms", "z3c.relationfield", diff --git a/tests/conftest.py b/tests/conftest.py index 2e2f02d..5ab3774 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,18 @@ ) +@pytest.fixture(autouse=True) +def disable_intermediate_commits(monkeypatch): + """Keep importers from committing the transaction during tests. + + The layers are kept for the whole test session, so a commit would leak + imported content, users and groups into the tests that follow. + """ + from plone.exportimport.importers.base import BaseImporter + + monkeypatch.setattr(BaseImporter, "intermediate_commits", False) + + @pytest.fixture() def base_import_path(): """Base content import Path.""" diff --git a/tests/exporters/conftest.py b/tests/exporters/conftest.py index 4bdaea0..5acb48c 100644 --- a/tests/exporters/conftest.py +++ b/tests/exporters/conftest.py @@ -1,7 +1,6 @@ from plone.exportimport.importers import get_importer import pytest -import transaction @pytest.fixture() @@ -25,8 +24,7 @@ def portal_multilingual( ): """Plone portal with imported content.""" portal = app["plone"] - with transaction.manager: - setup_multilingual_site(portal, "en", ["en", "de", "es"]) + setup_multilingual_site(portal, "en", ["en", "de", "es"]) importer = get_importer(portal) importer.import_site(path=multilingual_import_path) # Create new users and groups diff --git a/tests/importers/conftest.py b/tests/importers/conftest.py index ebd9690..89f891b 100644 --- a/tests/importers/conftest.py +++ b/tests/importers/conftest.py @@ -1,13 +1,11 @@ import pytest -import transaction @pytest.fixture() def portal_multilingual(app, setup_multilingual_site): """Plone portal with multilingual support.""" portal = app["plone"] - with transaction.manager: - setup_multilingual_site(portal, "en", ["en", "de", "es"]) + setup_multilingual_site(portal, "en", ["en", "de", "es"]) yield portal