Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/86.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make it easy to customize which content gets exported @gforcada
5 changes: 5 additions & 0 deletions src/plone/exportimport/exporters/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
name="plone.exporter.content"
/>
<adapter
factory=".content.ObjectsExporter"
provides="plone.exportimport.interfaces.IObjectsExporter"
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
/>
<adapter
factory=".principals.PrincipalsExporter"
provides="plone.exportimport.interfaces.INamedExporter"
Expand Down
32 changes: 21 additions & 11 deletions src/plone/exportimport/exporters/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,19 @@
from plone.exportimport.interfaces import IExportImportRequestMarker
from plone.exportimport.utils import content as content_utils
from plone.exportimport.utils import request_provides
from zope.component import getAdapter
from zope.interface import implementer

import argparse


@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"
class ObjectsExporter:

def all_objects(self) -> 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"
Expand All @@ -39,14 +36,28 @@ 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

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)
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 4 additions & 0 deletions src/plone/exportimport/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
24 changes: 24 additions & 0 deletions tests/exporters/test_exporters_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from zope.component import getAdapter
from zope.component.hooks import setSite

import inspect
import json
import pytest

Expand Down Expand Up @@ -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):
Expand Down