plone.exportimport 2.0.2__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/CHANGES.md +33 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/PKG-INFO +38 -2
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/setup.py +5 -2
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/cli/__init__.py +45 -17
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/deserializers/blob.py +18 -5
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/base.py +19 -6
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/configure.zcml +5 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/content.py +57 -18
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/discussions.py +3 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/portlets.py +3 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/redirects.py +3 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/relations.py +3 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/translations.py +3 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/__init__.py +8 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/content.py +39 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/interfaces.py +4 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/serializers/blob.py +6 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/testing/__init__.py +3 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/configure.zcml +43 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/content/dummy.py +43 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/profiles/testing/metadata.xml +5 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/profiles/testing/rolemap.xml +24 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/profiles/testing/types/DummyContent.xml +53 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/profiles/testing/types/Plone_Site.xml +11 -0
- plone_exportimport-2.2.0/src/plone/exportimport/testing/profiles/testing/types.xml +11 -0
- plone_exportimport-2.2.0/src/plone/exportimport/utils/cli.py +132 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/content/__init__.py +2 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/content/blocks.py +4 -2
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/content/core.py +26 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/content/export_helpers.py +7 -3
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/discussions.py +12 -4
- plone_exportimport-2.2.0/src/plone/exportimport/utils/path.py +85 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/portlets.py +7 -3
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/redirects.py +9 -2
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/relations.py +15 -1
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/translations.py +12 -2
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/PKG-INFO +38 -2
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/SOURCES.txt +18 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/requires.txt +4 -0
- plone_exportimport-2.2.0/tests/_resources/cli_import/content/0e1d9d4a2c3b4f5e8a7b6c5d4e3f2a1b/data.json +34 -0
- plone_exportimport-2.2.0/tests/_resources/cli_import/content/__metadata__.json +14 -0
- plone_exportimport-2.2.0/tests/_resources/cli_import/content/plone_site_root/data.json +28 -0
- plone_exportimport-2.2.0/tests/cli/conftest.py +94 -0
- plone_exportimport-2.2.0/tests/cli/test_cli_exporter.py +51 -0
- plone_exportimport-2.2.0/tests/cli/test_cli_exporter_partial.py +82 -0
- plone_exportimport-2.2.0/tests/cli/test_cli_importer.py +81 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/conftest.py +12 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/conftest.py +1 -3
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_content.py +96 -0
- plone_exportimport-2.2.0/tests/exporters/test_exporters_partial.py +137 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/conftest.py +1 -3
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_content.py +106 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/services/test_services.py +10 -1
- plone_exportimport-2.2.0/tests/utils/conftest.py +0 -0
- plone_exportimport-2.2.0/tests/utils/test_utils_cli.py +89 -0
- plone_exportimport-2.2.0/tests/utils/test_utils_content_blocks.py +66 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_content_export_helpers.py +1 -0
- plone_exportimport-2.2.0/tests/utils/test_utils_path.py +105 -0
- plone_exportimport-2.0.2/src/plone/exportimport/utils/cli.py +0 -68
- plone_exportimport-2.0.2/src/plone/exportimport/utils/path.py +0 -21
- plone_exportimport-2.0.2/tests/utils/test_utils_path.py +0 -48
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/.editorconfig +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/.flake8 +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/.gitignore +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/.meta.toml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/.pre-commit-config.yaml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/CONTRIBUTING.md +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/LICENSE +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/MANIFEST.in +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/Makefile +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/README.md +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/constraints.txt +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/instance.yaml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/mx.ini +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/pyproject.toml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/requirements.txt +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/setup.cfg +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/configure.zcml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/deserializers/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/deserializers/blocks.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/deserializers/configure.zcml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/principals.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/base.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/configure.zcml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/discussions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/final.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/portlets.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/principals.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/redirects.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/relations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/translations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/permissions.zcml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/serializers/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/serializers/blocks.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/serializers/configure.zcml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/serializers/fields.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/services/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/services/configure.zcml +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/settings.py +0 -0
- /plone_exportimport-2.0.2/tests/_resources/empty_import/.gitkeep → /plone_exportimport-2.2.0/src/plone/exportimport/testing/content/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/types.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/content/import_helpers.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/content/revisions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/dates.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/permissions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/principals/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/principals/groups.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/principals/helpers.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/principals/members.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/zca.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/utils/zip_utils.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/dependency_links.txt +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/entry_points.txt +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/not-zip-safe +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone.exportimport.egg-info/top_level.txt +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/35661c9bb5be42c68f665aa1ed291418/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/3e0dd7c4b2714eafa1d6fc6a1493f953/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/3e0dd7c4b2714eafa1d6fc6a1493f953/file/plone.pdf +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/45b0b46f17104a7b8fa7bb94d3dd5bd9/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/5d77cd5686184ec49ab077017b1fbc3a/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/70844f7bec1843b8ab2796c972c9ebfe/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/7c1393f615c4447c80db0d784390c5b7/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/7c1393f615c4447c80db0d784390c5b7/image/brazil-coins.png +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/90b11c863598495ba699b22ca76b1041/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/90b11c863598495ba699b22ca76b1041/image/2025.png +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/__metadata__.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/e7359727ace64e609b79c4091c38822a/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/content/plone_site_root/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/discussions.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/portlets.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/principals.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/redirects.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/relations.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/base_import/translations.json +0 -0
- /plone_exportimport-2.0.2/tests/utils/conftest.py → /plone_exportimport-2.2.0/tests/_resources/empty_import/.gitkeep +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/01fbca4cccd448d880959ba93aa8176e/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/1cbf4ae73c74459485d3fd6cb9714e43/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/20737423549c43a88488242ec629e087/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/2be023c76b3e4e0aa9908c70f19a14df/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/2d099ac593344be7aaf2e4aafbba9065/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/3e3d4bf2cac4482f9c03e34b52092f02/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/496cbe4f17904d6bb9321b23f91dc8cc/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/59e06b23024a4c4b9d93672fe440e019/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/5ffe1d0c89c745eaab26df0e71ac82de/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/6d8a2b38d445462aa19f604b0801a13c/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/85a60611c2744e7abe53e3f356df236a/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/__metadata__.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/b69412b6d0b0438d85b340c13a39fc8c/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/cecc6f7007344d3cbac68f1da4bd084a/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/ddb27456033d4b86a78f3ec783874b63/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/f40d3a236b464aa487b715adf3c0be92/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/f9767fbdadf041bd892010d21b290cb5/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/f98b033fa8ce46f487ca8923ddae7480/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/fbcd98e2d1a741e2a90b8f0389203530/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/content/plone_site_root/data.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/discussions.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/principals.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/redirects.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/relations.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/multilingual_import/translations.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/portlets_import/portlets.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/_resources/principals_import/principals.json +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_discussions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_portlets.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_principals.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_redirects.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_relations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/exporters/test_exporters_translations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_discussions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_final.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_portlets.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_principals.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_redirects.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_relations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/importers/test_importers_translations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/services/__init__.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/services/conftest.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_content_core.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_content_revisions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_dates.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_discussions.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_principals_groups.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_principals_members.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_redirects.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_relations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tests/utils/test_utils_translations.py +0 -0
- {plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/tox.ini +0 -0
|
@@ -9,6 +9,39 @@
|
|
|
9
9
|
|
|
10
10
|
<!-- towncrier release notes start -->
|
|
11
11
|
|
|
12
|
+
## 2.2.0 (2026-10-01)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### New features
|
|
16
|
+
|
|
17
|
+
- Add `--path` and `--paths` options to `plone-exporter` to export only parts of a site. Relations, translations, discussions, portlets and redirects are limited to the exported content @ericof #86
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug fixes
|
|
21
|
+
|
|
22
|
+
- Do not fail exporting content whose ``language`` is ``None``. Such an item serializes to ``"language": null``, and ``fix_language`` raised ``AttributeError: 'NoneType' object has no attribute 'get'``. It is now treated like a missing language and gets the default-language placeholder, which the importer resolves. @kunalKumar-13 #95
|
|
23
|
+
- Fixed export and import of grid blocks whose images have no ``image_scales`` information. @ericof #109
|
|
24
|
+
- 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 #110
|
|
25
|
+
- Do not silently drop file and image fields on import when a blob's file name has another Unicode normalization form on disk. Blob files are now exported under a normalized file name (NFC, without path separators or characters invalid on Windows), the import also finds the NFC form of a recorded blob path, and items that could not be fully deserialized are listed as imported with errors, making `plone-importer` exit with status 1 @ericof #114
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Internal
|
|
29
|
+
|
|
30
|
+
- Declared ``plone.autoform``, ``plone.behavior``, ``plone.supermodel`` and ``Products.GenericSetup`` as dependencies. @ericof
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Tests
|
|
34
|
+
|
|
35
|
+
- Fix intermittent failure of the services export/import roundtrip test, which compared ZIP archives including file timestamps @ericof
|
|
36
|
+
- Fixed test isolation with pytest-plone 1.1.0 by keeping importers and fixtures from committing transactions during tests. @ericof
|
|
37
|
+
|
|
38
|
+
## 2.1.0 (2026-09-10)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### New features
|
|
42
|
+
|
|
43
|
+
- Make it easy to customize which content gets exported @gforcada #86
|
|
44
|
+
|
|
12
45
|
## 2.0.2 (2026-08-21)
|
|
13
46
|
|
|
14
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plone.exportimport
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Plone content export / import support
|
|
5
5
|
Home-page: https://plone.org
|
|
6
6
|
Author: Plone Foundation
|
|
@@ -17,7 +17,6 @@ Classifier: Framework :: Plone
|
|
|
17
17
|
Classifier: Framework :: Plone :: 6.2
|
|
18
18
|
Classifier: Framework :: Plone :: Core
|
|
19
19
|
Classifier: Framework :: Zope :: 5
|
|
20
|
-
Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
|
|
21
20
|
Classifier: Operating System :: OS Independent
|
|
22
21
|
Classifier: Programming Language :: Python
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -35,14 +34,18 @@ Requires-Dist: plone.app.dexterity
|
|
|
35
34
|
Requires-Dist: plone.app.redirector
|
|
36
35
|
Requires-Dist: plone.app.textfield
|
|
37
36
|
Requires-Dist: plone.app.users
|
|
37
|
+
Requires-Dist: plone.autoform
|
|
38
38
|
Requires-Dist: plone.base
|
|
39
|
+
Requires-Dist: plone.behavior
|
|
39
40
|
Requires-Dist: plone.dexterity
|
|
40
41
|
Requires-Dist: plone.namedfile
|
|
41
42
|
Requires-Dist: plone.restapi
|
|
43
|
+
Requires-Dist: plone.supermodel
|
|
42
44
|
Requires-Dist: plone.uuid
|
|
43
45
|
Requires-Dist: Products.CMFEditions
|
|
44
46
|
Requires-Dist: Products.CMFPlone
|
|
45
47
|
Requires-Dist: Products.DCWorkflow
|
|
48
|
+
Requires-Dist: Products.GenericSetup
|
|
46
49
|
Requires-Dist: Products.PlonePAS
|
|
47
50
|
Requires-Dist: Products.PortalTransforms
|
|
48
51
|
Requires-Dist: z3c.relationfield
|
|
@@ -241,6 +244,39 @@ The project is licensed under the GPLv2.
|
|
|
241
244
|
|
|
242
245
|
<!-- towncrier release notes start -->
|
|
243
246
|
|
|
247
|
+
## 2.2.0 (2026-10-01)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
### New features
|
|
251
|
+
|
|
252
|
+
- Add `--path` and `--paths` options to `plone-exporter` to export only parts of a site. Relations, translations, discussions, portlets and redirects are limited to the exported content @ericof #86
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
### Bug fixes
|
|
256
|
+
|
|
257
|
+
- Do not fail exporting content whose ``language`` is ``None``. Such an item serializes to ``"language": null``, and ``fix_language`` raised ``AttributeError: 'NoneType' object has no attribute 'get'``. It is now treated like a missing language and gets the default-language placeholder, which the importer resolves. @kunalKumar-13 #95
|
|
258
|
+
- Fixed export and import of grid blocks whose images have no ``image_scales`` information. @ericof #109
|
|
259
|
+
- 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 #110
|
|
260
|
+
- Do not silently drop file and image fields on import when a blob's file name has another Unicode normalization form on disk. Blob files are now exported under a normalized file name (NFC, without path separators or characters invalid on Windows), the import also finds the NFC form of a recorded blob path, and items that could not be fully deserialized are listed as imported with errors, making `plone-importer` exit with status 1 @ericof #114
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
### Internal
|
|
264
|
+
|
|
265
|
+
- Declared ``plone.autoform``, ``plone.behavior``, ``plone.supermodel`` and ``Products.GenericSetup`` as dependencies. @ericof
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
### Tests
|
|
269
|
+
|
|
270
|
+
- Fix intermittent failure of the services export/import roundtrip test, which compared ZIP archives including file timestamps @ericof
|
|
271
|
+
- Fixed test isolation with pytest-plone 1.1.0 by keeping importers and fixtures from committing transactions during tests. @ericof
|
|
272
|
+
|
|
273
|
+
## 2.1.0 (2026-09-10)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
### New features
|
|
277
|
+
|
|
278
|
+
- Make it easy to customize which content gets exported @gforcada #86
|
|
279
|
+
|
|
244
280
|
## 2.0.2 (2026-08-21)
|
|
245
281
|
|
|
246
282
|
|
|
@@ -8,7 +8,7 @@ long_description = f"""
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="plone.exportimport",
|
|
11
|
-
version="2.0
|
|
11
|
+
version="2.2.0",
|
|
12
12
|
description="Plone content export / import support",
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
@@ -19,7 +19,6 @@ setup(
|
|
|
19
19
|
"Framework :: Plone :: 6.2",
|
|
20
20
|
"Framework :: Plone :: Core",
|
|
21
21
|
"Framework :: Zope :: 5",
|
|
22
|
-
"License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
|
|
23
22
|
"Operating System :: OS Independent",
|
|
24
23
|
"Programming Language :: Python",
|
|
25
24
|
"Programming Language :: Python :: 3.10",
|
|
@@ -50,14 +49,18 @@ setup(
|
|
|
50
49
|
"plone.app.redirector",
|
|
51
50
|
"plone.app.textfield",
|
|
52
51
|
"plone.app.users",
|
|
52
|
+
"plone.autoform",
|
|
53
53
|
"plone.base",
|
|
54
|
+
"plone.behavior",
|
|
54
55
|
"plone.dexterity",
|
|
55
56
|
"plone.namedfile",
|
|
56
57
|
"plone.restapi",
|
|
58
|
+
"plone.supermodel",
|
|
57
59
|
"plone.uuid",
|
|
58
60
|
"Products.CMFEditions",
|
|
59
61
|
"Products.CMFPlone",
|
|
60
62
|
"Products.DCWorkflow",
|
|
63
|
+
"Products.GenericSetup",
|
|
61
64
|
"Products.PlonePAS",
|
|
62
65
|
"Products.PortalTransforms",
|
|
63
66
|
"z3c.relationfield",
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/cli/__init__.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from plone import api
|
|
2
1
|
from plone.exportimport import logger as package_logger
|
|
3
2
|
from plone.exportimport.exporters import get_exporter
|
|
4
3
|
from plone.exportimport.importers import get_importer
|
|
@@ -13,19 +12,44 @@ CLI_SPEC = {
|
|
|
13
12
|
"exporter": {
|
|
14
13
|
"description": "Export Plone Site content",
|
|
15
14
|
"options": {
|
|
16
|
-
"zopeconf": "Path to zope.conf",
|
|
17
|
-
"site":
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
"zopeconf": {"help": "Path to zope.conf"},
|
|
16
|
+
"site": {
|
|
17
|
+
"help": "Plone site ID or path to site to export the content from"
|
|
18
|
+
},
|
|
19
|
+
"path": {"help": "Path to export the content"},
|
|
20
|
+
"--include-revisions": {
|
|
21
|
+
"action": "store_true",
|
|
22
|
+
"help": "Include revision history",
|
|
23
|
+
},
|
|
24
|
+
"--path": {
|
|
25
|
+
"action": "append",
|
|
26
|
+
"dest": "include_paths",
|
|
27
|
+
"metavar": "CONTENT_PATH",
|
|
28
|
+
"help": (
|
|
29
|
+
"Export only this content, and everything inside it. "
|
|
30
|
+
"Relative to the site root. Can be repeated"
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
"--paths": {
|
|
34
|
+
"dest": "paths_file",
|
|
35
|
+
"metavar": "FILE",
|
|
36
|
+
"help": (
|
|
37
|
+
"File with the content paths to export, one per line. "
|
|
38
|
+
"Empty lines and lines starting with # are ignored"
|
|
39
|
+
),
|
|
40
|
+
},
|
|
20
41
|
},
|
|
21
42
|
},
|
|
22
43
|
"importer": {
|
|
23
44
|
"description": "Import content into a Plone Site",
|
|
24
45
|
"options": {
|
|
25
|
-
"zopeconf": "Path to zope.conf",
|
|
26
|
-
"site": "Plone site ID or path to site to import the content to",
|
|
27
|
-
"path": "Path to import the content from",
|
|
28
|
-
"--quiet":
|
|
46
|
+
"zopeconf": {"help": "Path to zope.conf"},
|
|
47
|
+
"site": {"help": "Plone site ID or path to site to import the content to"},
|
|
48
|
+
"path": {"help": "Path to import the content from"},
|
|
49
|
+
"--quiet": {
|
|
50
|
+
"action": "store_true",
|
|
51
|
+
"help": "Do not report items being imported",
|
|
52
|
+
},
|
|
29
53
|
},
|
|
30
54
|
},
|
|
31
55
|
}
|
|
@@ -33,11 +57,8 @@ CLI_SPEC = {
|
|
|
33
57
|
|
|
34
58
|
def _parse_args(description: str, options: dict, args: list):
|
|
35
59
|
parser = argparse.ArgumentParser(description=description)
|
|
36
|
-
for key,
|
|
37
|
-
|
|
38
|
-
parser.add_argument(key, action="store_true", help=help)
|
|
39
|
-
else:
|
|
40
|
-
parser.add_argument(key, help=help)
|
|
60
|
+
for key, kwargs in options.items():
|
|
61
|
+
parser.add_argument(key, **kwargs)
|
|
41
62
|
namespace, _ = parser.parse_known_args(args[1:])
|
|
42
63
|
return namespace
|
|
43
64
|
|
|
@@ -54,7 +75,10 @@ def exporter_cli(args=sys.argv):
|
|
|
54
75
|
logger.error(f"{namespace.path} does not exist, please create it first.")
|
|
55
76
|
sys.exit(1)
|
|
56
77
|
site = cli_helpers.get_site(app, namespace.site, logger)
|
|
57
|
-
|
|
78
|
+
namespace.query = cli_helpers.get_export_query(
|
|
79
|
+
site, namespace.include_paths, namespace.paths_file, logger
|
|
80
|
+
)
|
|
81
|
+
with hooks.site(site):
|
|
58
82
|
results = get_exporter(site).export_site(path, options=namespace)
|
|
59
83
|
logger.info(f" Using path {path} to export content from Plone site at /{site.id}")
|
|
60
84
|
for item in results[1:]:
|
|
@@ -75,9 +99,13 @@ def importer_cli(args=sys.argv):
|
|
|
75
99
|
if not namespace.quiet:
|
|
76
100
|
cli_helpers.setup_logger_console(package_logger)
|
|
77
101
|
site = cli_helpers.get_site(app, namespace.site, logger)
|
|
78
|
-
with hooks.site(site)
|
|
102
|
+
with hooks.site(site):
|
|
79
103
|
logger.info(f" Using path {path} to import content to Plone site at /{site.id}")
|
|
80
|
-
|
|
104
|
+
importer = get_importer(site)
|
|
105
|
+
results = importer.import_site(path)
|
|
81
106
|
for item in results:
|
|
82
107
|
logger.info(f" - {item}")
|
|
83
108
|
transaction.commit()
|
|
109
|
+
if incomplete := importer.incomplete:
|
|
110
|
+
logger.error(f" {len(incomplete)} items were imported with errors")
|
|
111
|
+
sys.exit(1)
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/deserializers/blob.py
RENAMED
|
@@ -11,16 +11,29 @@ from zope.globalrequest import getRequest
|
|
|
11
11
|
from zope.interface import implementer
|
|
12
12
|
|
|
13
13
|
import codecs
|
|
14
|
+
import unicodedata
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def load_blob(path: str) -> bytes:
|
|
17
|
-
"""Load blob from fs and encode it as base64.
|
|
18
|
+
"""Load blob from fs and encode it as base64.
|
|
19
|
+
|
|
20
|
+
If no file exists at ``path``, the NFC form of ``path`` is tried as well:
|
|
21
|
+
exports may record the name in NFD while the file on disk is NFC (for
|
|
22
|
+
instance, after a commit with git's ``core.precomposeunicode``), and only
|
|
23
|
+
some filesystems treat both forms as the same name.
|
|
24
|
+
|
|
25
|
+
:param path: Blob path, relative to the content import directory.
|
|
26
|
+
:returns: Contents of the blob file.
|
|
27
|
+
:raises ValueError: If the blob file does not exist.
|
|
28
|
+
"""
|
|
18
29
|
request = getRequest()
|
|
19
30
|
content_import_path = Path(request[settings.IMPORT_PATH_KEY])
|
|
20
|
-
|
|
21
|
-
if not
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
blob_path = content_import_path / path
|
|
32
|
+
if not blob_path.exists():
|
|
33
|
+
blob_path = content_import_path / unicodedata.normalize("NFC", path)
|
|
34
|
+
if not blob_path.exists():
|
|
35
|
+
raise ValueError(f"Blob not found at {content_import_path / path}")
|
|
36
|
+
data = path_utils.encode_file_contents(blob_path)
|
|
24
37
|
return codecs.decode(data, "base64")
|
|
25
38
|
|
|
26
39
|
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/base.py
RENAMED
|
@@ -14,10 +14,10 @@ import json
|
|
|
14
14
|
class BaseExporter:
|
|
15
15
|
name: str
|
|
16
16
|
base_path: Path
|
|
17
|
-
errors: list
|
|
18
|
-
request: types.HTTPRequest
|
|
19
|
-
data_hooks: list[Callable] = None
|
|
20
|
-
obj_hooks: list[Callable] = None
|
|
17
|
+
errors: list
|
|
18
|
+
request: types.HTTPRequest
|
|
19
|
+
data_hooks: list[Callable] | None = None
|
|
20
|
+
obj_hooks: list[Callable] | None = None
|
|
21
21
|
options: argparse.Namespace | None = None
|
|
22
22
|
|
|
23
23
|
def __init__(
|
|
@@ -31,6 +31,18 @@ class BaseExporter:
|
|
|
31
31
|
def get_option(self, name, default=None):
|
|
32
32
|
return getattr(self.options, name, default)
|
|
33
33
|
|
|
34
|
+
def exported_content(self) -> dict[str, str] | None:
|
|
35
|
+
"""Return the content selected for a partial export.
|
|
36
|
+
|
|
37
|
+
A partial export is requested by setting ``options.query``.
|
|
38
|
+
Exporters use this to keep only data related to the exported content.
|
|
39
|
+
|
|
40
|
+
:returns: Mapping of UID to physical path of the exported content, or
|
|
41
|
+
``None`` when the whole site is exported.
|
|
42
|
+
"""
|
|
43
|
+
query = self.get_option("query")
|
|
44
|
+
return utils.get_content_paths(query) if query else None
|
|
45
|
+
|
|
34
46
|
@property
|
|
35
47
|
def filepath(self) -> Path:
|
|
36
48
|
"""Filepath to be used during export."""
|
|
@@ -63,9 +75,10 @@ class BaseExporter:
|
|
|
63
75
|
def export_data(
|
|
64
76
|
self,
|
|
65
77
|
base_path: Path,
|
|
66
|
-
data_hooks: list[Callable] = None,
|
|
67
|
-
obj_hooks: list[Callable] = None,
|
|
78
|
+
data_hooks: list[Callable] | None = None,
|
|
79
|
+
obj_hooks: list[Callable] | None = None,
|
|
68
80
|
options: argparse.Namespace | None = None,
|
|
81
|
+
**kwargs: Any,
|
|
69
82
|
) -> list[Path]:
|
|
70
83
|
"""Write data to filesystem."""
|
|
71
84
|
if not base_path.exists():
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
|
|
16
16
|
name="plone.exporter.content"
|
|
17
17
|
/>
|
|
18
|
+
<adapter
|
|
19
|
+
factory=".content.ObjectsExporter"
|
|
20
|
+
provides="plone.exportimport.interfaces.IObjectsExporter"
|
|
21
|
+
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
|
|
22
|
+
/>
|
|
18
23
|
<adapter
|
|
19
24
|
factory=".principals.PrincipalsExporter"
|
|
20
25
|
provides="plone.exportimport.interfaces.INamedExporter"
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/content.py
RENAMED
|
@@ -12,26 +12,22 @@ from plone.exportimport import types
|
|
|
12
12
|
from plone.exportimport.interfaces import IExportImportRequestMarker
|
|
13
13
|
from plone.exportimport.utils import content as content_utils
|
|
14
14
|
from plone.exportimport.utils import request_provides
|
|
15
|
+
from typing import Any
|
|
16
|
+
from zope.component import getAdapter
|
|
15
17
|
from zope.interface import implementer
|
|
16
18
|
|
|
17
19
|
import argparse
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
class ContentExporter(BaseExporter):
|
|
22
|
-
name: str = "content"
|
|
23
|
-
query: dict = None
|
|
24
|
-
filename_fmt: str = settings.EXPORT_CONTENT_FILEPATH
|
|
25
|
-
metadata: types.ExportImportMetadata = None
|
|
26
|
-
default_site_language: str = "en"
|
|
22
|
+
class ObjectsExporter:
|
|
27
23
|
|
|
28
|
-
def
|
|
24
|
+
def __init__(self, obj) -> None:
|
|
25
|
+
self.obj = obj
|
|
26
|
+
|
|
27
|
+
def get_objects(self, query, errors) -> Generator:
|
|
29
28
|
"""Return all objects to be serialized."""
|
|
30
|
-
query = self.query
|
|
31
29
|
catalog = api.portal.get_tool("portal_catalog")
|
|
32
|
-
|
|
33
|
-
query["object_provides"] = "plone.dexterity.interfaces.IDexterityContent"
|
|
34
|
-
brains = catalog.unrestrictedSearchResults(**query)
|
|
30
|
+
brains = catalog.unrestrictedSearchResults(**content_utils.content_query(query))
|
|
35
31
|
logger.info(f"Exporting {len(brains)}")
|
|
36
32
|
for index, brain in enumerate(brains, start=1):
|
|
37
33
|
try:
|
|
@@ -39,7 +35,7 @@ class ContentExporter(BaseExporter):
|
|
|
39
35
|
except Exception:
|
|
40
36
|
brain_path = brain.getPath()
|
|
41
37
|
msg = f"Error getting object {brain_path} from brain"
|
|
42
|
-
|
|
38
|
+
errors.append({"path": brain_path, "message": msg})
|
|
43
39
|
logger.exception(msg, exc_info=True)
|
|
44
40
|
else:
|
|
45
41
|
yield obj
|
|
@@ -47,6 +43,20 @@ class ContentExporter(BaseExporter):
|
|
|
47
43
|
if not index % 100:
|
|
48
44
|
logger.info(f"Content Exporter: Handled {index} items...")
|
|
49
45
|
|
|
46
|
+
|
|
47
|
+
@implementer(interfaces.INamedExporter)
|
|
48
|
+
class ContentExporter(BaseExporter):
|
|
49
|
+
name: str = "content"
|
|
50
|
+
query: dict = None
|
|
51
|
+
filename_fmt: str = settings.EXPORT_CONTENT_FILEPATH
|
|
52
|
+
metadata: types.ExportImportMetadata = None
|
|
53
|
+
default_site_language: str = "en"
|
|
54
|
+
|
|
55
|
+
def all_objects(self) -> Generator:
|
|
56
|
+
"""Return all objects to be serialized."""
|
|
57
|
+
adapter = getAdapter(self.site, interfaces.IObjectsExporter)
|
|
58
|
+
yield from adapter.get_objects(self.query, self.errors)
|
|
59
|
+
|
|
50
60
|
def serialize(self, obj: DexterityContent) -> dict:
|
|
51
61
|
"""Serialize object."""
|
|
52
62
|
obj_uid = content_utils.get_uid(obj)
|
|
@@ -142,19 +152,48 @@ class ContentExporter(BaseExporter):
|
|
|
142
152
|
paths.insert(0, self.dump_metadata())
|
|
143
153
|
return paths
|
|
144
154
|
|
|
155
|
+
def _prepare_query(
|
|
156
|
+
self, query: dict | None = None, options: argparse.Namespace | None = None
|
|
157
|
+
) -> dict:
|
|
158
|
+
"""Return the catalog query used to select the content to export.
|
|
159
|
+
|
|
160
|
+
An explicit ``query`` takes precedence over ``options.query``. When
|
|
161
|
+
neither is set, all content in the site is selected.
|
|
162
|
+
|
|
163
|
+
:param query: Explicit catalog query.
|
|
164
|
+
:param options: Export options, checked for a ``query`` attribute.
|
|
165
|
+
:returns: The catalog query to use.
|
|
166
|
+
"""
|
|
167
|
+
query = query or getattr(options, "query", None)
|
|
168
|
+
return query if query else {"path": content_utils.get_obj_path(self.site)}
|
|
169
|
+
|
|
145
170
|
def export_data(
|
|
146
171
|
self,
|
|
147
172
|
base_path: Path,
|
|
148
|
-
data_hooks: list[Callable] = None,
|
|
149
|
-
obj_hooks: list[Callable] = None,
|
|
150
|
-
query: dict | None = None,
|
|
173
|
+
data_hooks: list[Callable] | None = None,
|
|
174
|
+
obj_hooks: list[Callable] | None = None,
|
|
151
175
|
options: argparse.Namespace | None = None,
|
|
176
|
+
query: dict | None = None,
|
|
177
|
+
**kwargs: Any,
|
|
152
178
|
) -> list[Path]:
|
|
179
|
+
"""Export content to a ``content`` folder inside ``base_path``.
|
|
180
|
+
|
|
181
|
+
Arguments other than ``base_path`` should be passed by keyword, as
|
|
182
|
+
``query`` comes after ``options`` in this signature.
|
|
183
|
+
|
|
184
|
+
:param base_path: Directory where the export is written.
|
|
185
|
+
:param data_hooks: Callables applied to the serialized data.
|
|
186
|
+
:param obj_hooks: Callables applied to each object before serialization.
|
|
187
|
+
:param options: Export options; ``options.query`` is used when no
|
|
188
|
+
``query`` is passed.
|
|
189
|
+
:param query: Catalog query selecting the content to export. It takes
|
|
190
|
+
precedence over ``options.query``. Defaults to all content in the site.
|
|
191
|
+
:returns: Paths of the files written.
|
|
192
|
+
"""
|
|
153
193
|
# Content in a subpath of base_path
|
|
154
194
|
base_path = base_path / self.name
|
|
155
|
-
query = query if query else {}
|
|
156
195
|
site = self.site
|
|
157
|
-
self.query = query
|
|
196
|
+
self.query = self._prepare_query(query, options)
|
|
158
197
|
metadata = types.ExportImportMetadata()
|
|
159
198
|
self.metadata = metadata
|
|
160
199
|
self.request[settings.EXPORT_CONTENT_METADATA_KEY] = metadata
|
|
@@ -24,7 +24,9 @@ class DiscussionsExporter(BaseExporter):
|
|
|
24
24
|
|
|
25
25
|
from plone.exportimport.utils import discussions as utils
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
content = self.exported_content()
|
|
28
|
+
uids = set(content) if content is not None else None
|
|
29
|
+
discussions = utils.get_discussions(uids=uids)
|
|
28
30
|
filepath = self._dump(discussions, self.filepath)
|
|
29
31
|
logger.debug(
|
|
30
32
|
f"- Discussions: Wrote {len(discussions)} discussions to {filepath}"
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/portlets.py
RENAMED
|
@@ -12,7 +12,9 @@ class PortletsExporter(BaseExporter):
|
|
|
12
12
|
|
|
13
13
|
def dump(self) -> list[Path]:
|
|
14
14
|
"""Serialize object and dump it to disk."""
|
|
15
|
-
|
|
15
|
+
content = self.exported_content()
|
|
16
|
+
uids = set(content) if content is not None else None
|
|
17
|
+
portlets = utils.get_portlets(uids=uids)
|
|
16
18
|
filepath = self._dump(portlets, self.filepath)
|
|
17
19
|
logger.debug(f"- Portlets: Wrote {len(portlets)} portlets to {filepath}")
|
|
18
20
|
return [filepath]
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/redirects.py
RENAMED
|
@@ -12,7 +12,9 @@ class RedirectsExporter(BaseExporter):
|
|
|
12
12
|
|
|
13
13
|
def dump(self) -> list[Path]:
|
|
14
14
|
"""Serialize object and dump it to disk."""
|
|
15
|
-
|
|
15
|
+
content = self.exported_content()
|
|
16
|
+
paths = set(content.values()) if content is not None else None
|
|
17
|
+
redirects = utils.get_redirects(paths=paths)
|
|
16
18
|
filepath = self._dump(redirects, self.filepath)
|
|
17
19
|
logger.debug(f"- Redirects: Wrote {len(redirects)} redirects to {filepath}")
|
|
18
20
|
return [filepath]
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/exporters/relations.py
RENAMED
|
@@ -12,7 +12,9 @@ class RelationsExporter(BaseExporter):
|
|
|
12
12
|
|
|
13
13
|
def dump(self) -> list[Path]:
|
|
14
14
|
"""Serialize object and dump it to disk."""
|
|
15
|
-
|
|
15
|
+
content = self.exported_content()
|
|
16
|
+
uids = set(content) if content is not None else None
|
|
17
|
+
relations = utils.get_relations(uids=uids)
|
|
16
18
|
filepath = self._dump(relations, self.filepath)
|
|
17
19
|
logger.debug(f"- Relations: Wrote {len(relations)} relations to {filepath}")
|
|
18
20
|
return [filepath]
|
|
@@ -12,7 +12,9 @@ class TranslationsExporter(BaseExporter):
|
|
|
12
12
|
|
|
13
13
|
def dump(self) -> list[Path]:
|
|
14
14
|
"""Serialize object and dump it to disk."""
|
|
15
|
-
|
|
15
|
+
content = self.exported_content()
|
|
16
|
+
uids = set(content) if content is not None else None
|
|
17
|
+
translations = utils.get_translations(uids=uids)
|
|
16
18
|
filepath = self._dump(translations, self.filepath)
|
|
17
19
|
logger.debug(
|
|
18
20
|
f"- Translations: Wrote {len(translations)} translations to {filepath}"
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/__init__.py
RENAMED
|
@@ -55,6 +55,14 @@ class Importer:
|
|
|
55
55
|
report.append(importer.import_data(path))
|
|
56
56
|
return report
|
|
57
57
|
|
|
58
|
+
@property
|
|
59
|
+
def incomplete(self) -> dict[str, list[str]]:
|
|
60
|
+
"""Items imported with errors, mapping each path to its failed fields."""
|
|
61
|
+
result: dict[str, list[str]] = {}
|
|
62
|
+
for importer in self.importers.values():
|
|
63
|
+
result.update(getattr(importer, "incomplete", {}))
|
|
64
|
+
return result
|
|
65
|
+
|
|
58
66
|
|
|
59
67
|
def get_importer(site: PloneSite | None = None) -> Importer:
|
|
60
68
|
"""Get the importer."""
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/importers/content.py
RENAMED
|
@@ -13,11 +13,41 @@ from plone.exportimport.utils import request_provides
|
|
|
13
13
|
import json
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def _failed_fields(exc: Exception) -> list[str]:
|
|
17
|
+
"""Return the names of the fields reported in a deserialization error.
|
|
18
|
+
|
|
19
|
+
plone.restapi collects field errors and raises a single
|
|
20
|
+
:class:`zExceptions.BadRequest` whose first argument is a list of dicts,
|
|
21
|
+
each with an optional ``field`` key.
|
|
22
|
+
|
|
23
|
+
:param exc: Exception raised by the deserializer.
|
|
24
|
+
:returns: Sorted field names, or an empty list if the error does not
|
|
25
|
+
name any field.
|
|
26
|
+
"""
|
|
27
|
+
errors = exc.args[0] if exc.args else None
|
|
28
|
+
if not isinstance(errors, list):
|
|
29
|
+
return []
|
|
30
|
+
fields = {
|
|
31
|
+
error["field"]
|
|
32
|
+
for error in errors
|
|
33
|
+
if isinstance(error, dict) and error.get("field")
|
|
34
|
+
}
|
|
35
|
+
return sorted(fields)
|
|
36
|
+
|
|
37
|
+
|
|
16
38
|
class ContentImporter(BaseImporter):
|
|
17
39
|
name: str = "content"
|
|
18
40
|
metadata: types.ExportImportMetadata | None = None
|
|
19
41
|
languages: types.PortalLanguages | None = None
|
|
20
|
-
dropped: set[str]
|
|
42
|
+
dropped: set[str]
|
|
43
|
+
incomplete: dict[str, list[str]]
|
|
44
|
+
|
|
45
|
+
def __init__(self, site):
|
|
46
|
+
super().__init__(site)
|
|
47
|
+
# Items not created at all
|
|
48
|
+
self.dropped = set()
|
|
49
|
+
# Items created, but not fully deserialized: path -> failed fields
|
|
50
|
+
self.incomplete = {}
|
|
21
51
|
|
|
22
52
|
def _cleanse_ordering(self, raw_data: dict[str, int]) -> dict[str, int]:
|
|
23
53
|
"""Prepare ordering data before deserialization.
|
|
@@ -60,6 +90,8 @@ class ContentImporter(BaseImporter):
|
|
|
60
90
|
logger.error(
|
|
61
91
|
f"{config.logger_prefix} Error deserializing {obj}", exc_info=exc
|
|
62
92
|
)
|
|
93
|
+
# The object exists, but some of its data was not applied
|
|
94
|
+
self.incomplete[data["@id"]] = _failed_fields(exc)
|
|
63
95
|
return obj
|
|
64
96
|
|
|
65
97
|
def construct(self, item: dict) -> DexterityContent | None:
|
|
@@ -210,6 +242,12 @@ class ContentImporter(BaseImporter):
|
|
|
210
242
|
logger.warning("List of items dropped during import")
|
|
211
243
|
for item_path in dropped:
|
|
212
244
|
logger.warning(f" - {item_path}")
|
|
245
|
+
# Report items that were created with missing data
|
|
246
|
+
if self.incomplete:
|
|
247
|
+
logger.error("List of items imported with errors")
|
|
248
|
+
for item_path, fields in sorted(self.incomplete.items()):
|
|
249
|
+
details = f" (fields: {', '.join(fields)})" if fields else ""
|
|
250
|
+
logger.error(f" - {item_path}{details}")
|
|
213
251
|
return result
|
|
214
252
|
|
|
215
253
|
def start(self):
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/serializers/blob.py
RENAMED
|
@@ -78,6 +78,11 @@ def export_blob(
|
|
|
78
78
|
|
|
79
79
|
Root path: settings.EXPORT_PATH_KEY
|
|
80
80
|
File path: settings.EXPORT_CONTENT_BLOB_FILEPATH
|
|
81
|
+
|
|
82
|
+
The file name is normalized with
|
|
83
|
+
:func:`plone.exportimport.utils.path.normalize_filename`, so the path
|
|
84
|
+
written to disk, the returned path and the ``_blob_files_`` entry are the
|
|
85
|
+
same on every filesystem.
|
|
81
86
|
"""
|
|
82
87
|
request = getRequest()
|
|
83
88
|
# Metadata
|
|
@@ -85,7 +90,7 @@ def export_blob(
|
|
|
85
90
|
uid = settings.SITE_ROOT_UID if content_utils.is_site_root(obj) else obj.UID()
|
|
86
91
|
content_export_path = Path(request[settings.EXPORT_PATH_KEY])
|
|
87
92
|
blob_path = settings.EXPORT_CONTENT_BLOB_FILEPATH.format(
|
|
88
|
-
UID=uid, fieldname=fieldname, filename=filename
|
|
93
|
+
UID=uid, fieldname=fieldname, filename=path_utils.normalize_filename(filename)
|
|
89
94
|
)
|
|
90
95
|
target_file = content_export_path / blob_path
|
|
91
96
|
# Create target directory
|
{plone_exportimport-2.0.2 → plone_exportimport-2.2.0}/src/plone/exportimport/testing/__init__.py
RENAMED
|
@@ -10,14 +10,17 @@ class ExportImportLayer(PloneSandboxLayer):
|
|
|
10
10
|
|
|
11
11
|
def setUpZope(self, app, configurationContext):
|
|
12
12
|
import plone.exportimport
|
|
13
|
+
import plone.exportimport.testing
|
|
13
14
|
import plone.volto
|
|
14
15
|
|
|
15
16
|
self.loadZCML(package=plone.volto)
|
|
16
17
|
self.loadZCML(package=plone.exportimport)
|
|
18
|
+
self.loadZCML(package=plone.exportimport.testing)
|
|
17
19
|
|
|
18
20
|
def setUpPloneSite(self, portal):
|
|
19
21
|
st = portal.portal_setup
|
|
20
22
|
st.runAllImportStepsFromProfile("plone.volto:default")
|
|
23
|
+
st.runAllImportStepsFromProfile("plone.exportimport.testing:testing")
|
|
21
24
|
|
|
22
25
|
# Enable plone.constraintypes behavior,
|
|
23
26
|
# which is not enabled by default in plone.volto
|