diff --git a/news/86.feature b/news/86.feature
new file mode 100644
index 0000000..7cb673f
--- /dev/null
+++ b/news/86.feature
@@ -0,0 +1 @@
+Make it easy to customize which content gets exported @gforcada
diff --git a/src/plone/exportimport/exporters/configure.zcml b/src/plone/exportimport/exporters/configure.zcml
index ba9c8bb..05fa2d3 100644
--- a/src/plone/exportimport/exporters/configure.zcml
+++ b/src/plone/exportimport/exporters/configure.zcml
@@ -15,6 +15,11 @@
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
name="plone.exporter.content"
/>
+
Generator:
+ def __init__(self, obj) -> None:
+ self.obj = obj
+
+ def get_objects(self, query, errors) -> Generator:
"""Return all objects to be serialized."""
- query = self.query
catalog = api.portal.get_tool("portal_catalog")
if "object_provides" not in query:
query["object_provides"] = "plone.dexterity.interfaces.IDexterityContent"
@@ -39,7 +36,7 @@ def all_objects(self) -> Generator:
except Exception:
brain_path = brain.getPath()
msg = f"Error getting object {brain_path} from brain"
- self.errors.append({"path": brain_path, "message": msg})
+ errors.append({"path": brain_path, "message": msg})
logger.exception(msg, exc_info=True)
else:
yield obj
@@ -47,6 +44,20 @@ def all_objects(self) -> Generator:
if not index % 100:
logger.info(f"Content Exporter: Handled {index} items...")
+
+@implementer(interfaces.INamedExporter)
+class ContentExporter(BaseExporter):
+ name: str = "content"
+ query: dict = None
+ filename_fmt: str = settings.EXPORT_CONTENT_FILEPATH
+ metadata: types.ExportImportMetadata = None
+ default_site_language: str = "en"
+
+ def all_objects(self) -> Generator:
+ """Return all objects to be serialized."""
+ adapter = getAdapter(self.site, interfaces.IObjectsExporter)
+ yield from adapter.get_objects(self.query, self.errors)
+
def serialize(self, obj: DexterityContent) -> dict:
"""Serialize object."""
obj_uid = content_utils.get_uid(obj)
@@ -152,7 +163,6 @@ def export_data(
) -> list[Path]:
# Content in a subpath of base_path
base_path = base_path / self.name
- query = query if query else {}
site = self.site
self.query = query if query else {"path": content_utils.get_obj_path(site)}
metadata = types.ExportImportMetadata()
diff --git a/src/plone/exportimport/interfaces.py b/src/plone/exportimport/interfaces.py
index fd2b3fb..a98ec34 100644
--- a/src/plone/exportimport/interfaces.py
+++ b/src/plone/exportimport/interfaces.py
@@ -21,3 +21,7 @@ class INamedExporter(Interface):
class INamedImporter(Interface):
"""Component to import content from a Plone Site."""
+
+
+class IObjectsExporter(Interface):
+ """Component to decide which objects are exported from a Plone Site."""
diff --git a/tests/exporters/test_exporters_content.py b/tests/exporters/test_exporters_content.py
index b90b59a..df25430 100644
--- a/tests/exporters/test_exporters_content.py
+++ b/tests/exporters/test_exporters_content.py
@@ -4,6 +4,7 @@
from zope.component import getAdapter
from zope.component.hooks import setSite
+import inspect
import json
import pytest
@@ -100,6 +101,29 @@ def test_content_is_exported(self, export_path, paths_as_relative, path):
assert "parent" not in keys
+class TestExporterObjects:
+
+ @pytest.fixture(autouse=True)
+ def _init(self, portal):
+ self.src_portal = portal
+ self.exporter = content.ObjectsExporter(portal)
+
+ def test_adapter_is_registered(self):
+ adapter = getAdapter(self.src_portal, interfaces.IObjectsExporter)
+ assert isinstance(adapter, content.ObjectsExporter)
+
+ def test_output_is_generator(self):
+ exporter = self.exporter
+ result = exporter.get_objects({}, [])
+ assert inspect.isgenerator(result)
+
+ def test_output_objects(self):
+ exporter = self.exporter
+ result = exporter.get_objects({}, [])
+ objects = [x for x in result]
+ assert len(objects) == 9
+
+
class TestExporterContentMetadata:
@pytest.fixture(autouse=True)
def _init(self, portal, export_path):