plone.exportimport 1.0.0a5__py3-none-any.whl

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.
Files changed (64) hide show
  1. plone/exportimport/__init__.py +8 -0
  2. plone/exportimport/cli/__init__.py +73 -0
  3. plone/exportimport/configure.zcml +23 -0
  4. plone/exportimport/deserializers/__init__.py +0 -0
  5. plone/exportimport/deserializers/blob.py +44 -0
  6. plone/exportimport/deserializers/blocks.py +15 -0
  7. plone/exportimport/deserializers/configure.zcml +4 -0
  8. plone/exportimport/exporters/__init__.py +80 -0
  9. plone/exportimport/exporters/base.py +66 -0
  10. plone/exportimport/exporters/configure.zcml +58 -0
  11. plone/exportimport/exporters/content.py +159 -0
  12. plone/exportimport/exporters/discussions.py +21 -0
  13. plone/exportimport/exporters/portlets.py +19 -0
  14. plone/exportimport/exporters/principals.py +24 -0
  15. plone/exportimport/exporters/redirects.py +19 -0
  16. plone/exportimport/exporters/relations.py +19 -0
  17. plone/exportimport/exporters/translations.py +21 -0
  18. plone/exportimport/importers/__init__.py +65 -0
  19. plone/exportimport/importers/base.py +71 -0
  20. plone/exportimport/importers/configure.zcml +58 -0
  21. plone/exportimport/importers/content.py +160 -0
  22. plone/exportimport/importers/discussions.py +16 -0
  23. plone/exportimport/importers/portlets.py +16 -0
  24. plone/exportimport/importers/principals.py +34 -0
  25. plone/exportimport/importers/redirects.py +16 -0
  26. plone/exportimport/importers/relations.py +23 -0
  27. plone/exportimport/importers/translations.py +16 -0
  28. plone/exportimport/interfaces.py +23 -0
  29. plone/exportimport/serializers/__init__.py +0 -0
  30. plone/exportimport/serializers/blob.py +100 -0
  31. plone/exportimport/serializers/blocks.py +15 -0
  32. plone/exportimport/serializers/configure.zcml +9 -0
  33. plone/exportimport/serializers/fields.py +78 -0
  34. plone/exportimport/settings.py +19 -0
  35. plone/exportimport/testing/__init__.py +27 -0
  36. plone/exportimport/types.py +67 -0
  37. plone/exportimport/utils/__init__.py +1 -0
  38. plone/exportimport/utils/cli.py +58 -0
  39. plone/exportimport/utils/content/__init__.py +18 -0
  40. plone/exportimport/utils/content/blocks.py +47 -0
  41. plone/exportimport/utils/content/core.py +81 -0
  42. plone/exportimport/utils/content/export_helpers.py +322 -0
  43. plone/exportimport/utils/content/import_helpers.py +307 -0
  44. plone/exportimport/utils/content/revisions.py +45 -0
  45. plone/exportimport/utils/dates.py +21 -0
  46. plone/exportimport/utils/discussions.py +137 -0
  47. plone/exportimport/utils/path.py +21 -0
  48. plone/exportimport/utils/portlets.py +282 -0
  49. plone/exportimport/utils/principals/__init__.py +4 -0
  50. plone/exportimport/utils/principals/groups.py +50 -0
  51. plone/exportimport/utils/principals/helpers.py +37 -0
  52. plone/exportimport/utils/principals/members.py +126 -0
  53. plone/exportimport/utils/redirects.py +30 -0
  54. plone/exportimport/utils/relations.py +103 -0
  55. plone/exportimport/utils/translations.py +134 -0
  56. plone/exportimport/utils/zca.py +13 -0
  57. plone.exportimport-1.0.0a5-py3.10-nspkg.pth +1 -0
  58. plone.exportimport-1.0.0a5.dist-info/LICENSE +339 -0
  59. plone.exportimport-1.0.0a5.dist-info/METADATA +156 -0
  60. plone.exportimport-1.0.0a5.dist-info/RECORD +64 -0
  61. plone.exportimport-1.0.0a5.dist-info/WHEEL +5 -0
  62. plone.exportimport-1.0.0a5.dist-info/entry_points.txt +6 -0
  63. plone.exportimport-1.0.0a5.dist-info/namespace_packages.txt +1 -0
  64. plone.exportimport-1.0.0a5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,8 @@
1
+ """Plone Distribution support."""
2
+
3
+ import logging
4
+
5
+
6
+ PACKAGE_NAME = "plone.exportimport"
7
+
8
+ logger = logging.getLogger(PACKAGE_NAME)
@@ -0,0 +1,73 @@
1
+ from plone import api
2
+ from plone.exportimport.exporters import get_exporter
3
+ from plone.exportimport.importers import get_importer
4
+ from plone.exportimport.utils import cli as cli_helpers
5
+
6
+ import argparse
7
+ import sys
8
+ import transaction
9
+
10
+
11
+ CLI_SPEC = {
12
+ "exporter": {
13
+ "description": "Export Plone Site content",
14
+ "options": {
15
+ "zopeconf": "Path to zope.conf",
16
+ "site": "Plone site ID to export the content from",
17
+ "path": "Path to export the content",
18
+ },
19
+ },
20
+ "importer": {
21
+ "description": "Import content into a Plone Site",
22
+ "options": {
23
+ "zopeconf": "Path to zope.conf",
24
+ "site": "Plone site ID to import the content to",
25
+ "path": "Path to import the content from",
26
+ },
27
+ },
28
+ }
29
+
30
+
31
+ def _parse_args(description: str, options: dict, args: list):
32
+ parser = argparse.ArgumentParser(description=description)
33
+ for key, help in options.items():
34
+ parser.add_argument(key, help=help)
35
+ namespace, _ = parser.parse_known_args(args[1:])
36
+ return namespace
37
+
38
+
39
+ def exporter_cli(args=sys.argv):
40
+ """Export a Plone site."""
41
+ logger = cli_helpers.get_logger("Exporter")
42
+ exporter_cli = CLI_SPEC["exporter"]
43
+ namespace = _parse_args(exporter_cli["description"], exporter_cli["options"], args)
44
+ app = cli_helpers.get_app(namespace.zopeconf)
45
+ path = cli_helpers._process_path(namespace.path)
46
+ if not path:
47
+ logger.error(f"{namespace.path} does not exist, please create it first.")
48
+ sys.exit(1)
49
+ site = cli_helpers.get_site(app, namespace.site, logger)
50
+ with api.env.adopt_roles(["Manager"]):
51
+ results = get_exporter(site).export_site(path)
52
+ logger.info(f" Using path {path} to export content from Plone site at /{site.id}")
53
+ for item in results[1:]:
54
+ logger.info(f" Wrote {item}")
55
+
56
+
57
+ def importer_cli(args=sys.argv):
58
+ """Import content to a Plone site."""
59
+ logger = cli_helpers.get_logger("Importer")
60
+ importer_cli = CLI_SPEC["importer"]
61
+ namespace = _parse_args(importer_cli["description"], importer_cli["options"], args)
62
+ app = cli_helpers.get_app(namespace.zopeconf)
63
+ path = cli_helpers._process_path(namespace.path)
64
+ if not path:
65
+ logger.error(f"{namespace.path} does not exist, aborting import.")
66
+ sys.exit(1)
67
+ site = cli_helpers.get_site(app, namespace.site, logger)
68
+ with api.env.adopt_roles(["Manager"]):
69
+ results = get_importer(site).import_site(path)
70
+ logger.info(f" Using path {path} to import content to Plone site at /{site.id}")
71
+ for item in results:
72
+ logger.info(f" - {item}")
73
+ transaction.commit()
@@ -0,0 +1,23 @@
1
+ <configure
2
+ xmlns="http://namespaces.zope.org/zope"
3
+ xmlns:i18n="http://namespaces.zope.org/i18n"
4
+ xmlns:zcml="http://namespaces.zope.org/zcml"
5
+ i18n_domain="plone"
6
+ >
7
+
8
+ <!-- Dependencies -->
9
+ <include package="Products.CMFCore" />
10
+ <include package="Products.CMFPlone" />
11
+ <include package="plone.namedfile" />
12
+ <include package="plone.app.dexterity" />
13
+ <include package="plone.app.discussion" />
14
+ <include package="plone.app.multilingual" />
15
+ <include package="plone.restapi" />
16
+
17
+ <include package=".serializers" />
18
+ <include package=".deserializers" />
19
+ <include package=".exporters" />
20
+ <include package=".importers" />
21
+
22
+
23
+ </configure>
File without changes
@@ -0,0 +1,44 @@
1
+ from pathlib import Path
2
+ from plone.dexterity.interfaces import IDexterityContent
3
+ from plone.exportimport import settings
4
+ from plone.exportimport.interfaces import IExportImportRequestMarker
5
+ from plone.exportimport.utils import path as path_utils
6
+ from plone.namedfile.interfaces import INamedField
7
+ from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
8
+ from plone.restapi.interfaces import IFieldDeserializer
9
+ from zope.component import adapter
10
+ from zope.globalrequest import getRequest
11
+ from zope.interface import implementer
12
+
13
+ import codecs
14
+
15
+
16
+ def load_blob(path: str) -> bytes:
17
+ """Load blob from fs and encode it as base64."""
18
+ request = getRequest()
19
+ content_import_path = Path(request[settings.IMPORT_PATH_KEY])
20
+ path = content_import_path / path
21
+ if not path.exists():
22
+ raise ValueError(f"Blob not found at {path}")
23
+ data = path_utils.encode_file_contents(path)
24
+ return codecs.decode(data, "base64")
25
+
26
+
27
+ @adapter(INamedField, IDexterityContent, IExportImportRequestMarker)
28
+ @implementer(IFieldDeserializer)
29
+ class ExportImportNamedFieldDeserializer(DefaultFieldDeserializer):
30
+ def __call__(self, value):
31
+ result = None
32
+ blob_path = value.pop("blob_path", "")
33
+ content_type = value.get("content-type", "application/octet-stream")
34
+ filename = value.get("filename", None)
35
+ data = load_blob(blob_path)
36
+ # Convert if we have data
37
+ if data:
38
+ result = self.field._type(
39
+ data=data, contentType=content_type, filename=filename
40
+ )
41
+
42
+ # Always validate to check for required fields
43
+ self.field.validate(result)
44
+ return result
@@ -0,0 +1,15 @@
1
+ from plone.exportimport.interfaces import IExportImportRequestMarker
2
+ from plone.restapi.behaviors import IBlocks
3
+ from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
4
+ from plone.restapi.interfaces import IFieldDeserializer
5
+ from plone.schema import IJSONField
6
+ from zope.component import adapter
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(IFieldDeserializer)
11
+ @adapter(IJSONField, IBlocks, IExportImportRequestMarker)
12
+ class ExportImportBlocksDeserializer(DefaultFieldDeserializer):
13
+ """We skip the subscribers that deserialize the blocks from the frontend.
14
+ We only need the raw data.
15
+ """
@@ -0,0 +1,4 @@
1
+ <configure xmlns="http://namespaces.zope.org/zope">
2
+ <adapter factory=".blob.ExportImportNamedFieldDeserializer" />
3
+ <adapter factory=".blocks.ExportImportBlocksDeserializer" />
4
+ </configure>
@@ -0,0 +1,80 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone import api
4
+ from plone.exportimport import interfaces
5
+ from plone.exportimport import logger
6
+ from plone.exportimport import PACKAGE_NAME
7
+ from Products.CMFPlone.Portal import PloneSite
8
+ from tempfile import mkdtemp
9
+ from typing import Dict
10
+ from typing import List
11
+ from typing import Optional
12
+ from zope.component import getAdapter
13
+ from zope.component import hooks
14
+ from zope.component import queryAdapter
15
+ from zope.interface import implementer
16
+
17
+
18
+ EXPORTER_NAMES = [
19
+ "plone.exporter.content",
20
+ "plone.exporter.principals",
21
+ "plone.exporter.redirects",
22
+ "plone.exporter.relations",
23
+ "plone.exporter.translations",
24
+ "plone.exporter.discussions",
25
+ "plone.exporter.portlets",
26
+ ]
27
+
28
+
29
+ ExporterMapping = Dict[str, BaseExporter]
30
+
31
+
32
+ @implementer(interfaces.IExporter)
33
+ class Exporter:
34
+ """Export content from a Plone Site."""
35
+
36
+ exporters: ExporterMapping
37
+
38
+ def __init__(self, site):
39
+ self.site = site
40
+ self.exporters = self.all_exporters()
41
+
42
+ def all_exporters(self) -> ExporterMapping:
43
+ """Return all exporters."""
44
+ exporters = {}
45
+ for exporter_name in EXPORTER_NAMES:
46
+ exporter = queryAdapter(
47
+ self.site, interfaces.INamedExporter, name=exporter_name
48
+ )
49
+ if exporter:
50
+ exporters[exporter_name] = exporter
51
+ return exporters
52
+
53
+ @staticmethod
54
+ def _prepare_path(path: Optional[Path] = None) -> Path:
55
+ """Return a valid path to use for the export.
56
+
57
+ If base_path is not given, create a temporary directory to export
58
+ the content.
59
+ """
60
+ valid_path = path.is_dir() if path else False
61
+ if not valid_path:
62
+ path = Path(mkdtemp(prefix=PACKAGE_NAME))
63
+ return path
64
+
65
+ def export_site(self, path: Optional[Path] = None) -> List[Path]:
66
+ """Export the given site to the filesystem."""
67
+ path = self._prepare_path(path)
68
+ paths: List[Path] = [path]
69
+ with hooks.site(self.site):
70
+ for exporter_name, exporter in self.exporters.items():
71
+ logger.debug(f"Exporting {self.site} with {exporter_name} to {path}")
72
+ paths.extend(exporter.export_data(path))
73
+ return paths
74
+
75
+
76
+ def get_exporter(site: PloneSite = None) -> Exporter:
77
+ """Get the exporter."""
78
+ if site is None:
79
+ site = api.portal.get()
80
+ return getAdapter(site, interfaces.IExporter)
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ from plone.exportimport import types
3
+ from plone.exportimport.utils import content as utils
4
+ from plone.exportimport.utils import path as path_utils
5
+ from Products.CMFPlone.Portal import PloneSite
6
+ from typing import Any
7
+ from typing import Callable
8
+ from typing import List
9
+ from zope.globalrequest import getRequest
10
+
11
+ import json
12
+
13
+
14
+ class BaseExporter:
15
+ name: str
16
+ base_path: Path
17
+ errors: list = None
18
+ request: types.HTTPRequest = None
19
+ data_hooks: List[Callable] = None
20
+ obj_hooks: List[Callable] = None
21
+
22
+ def __init__(
23
+ self,
24
+ site: PloneSite = None,
25
+ ):
26
+ self.site = site
27
+ self.errors = []
28
+ self.request = getRequest()
29
+
30
+ @property
31
+ def filepath(self) -> Path:
32
+ """Filepath to be used during export."""
33
+ filename = f"{self.name}.json"
34
+ return self.base_path / filename
35
+
36
+ def _serializer(self, obj: Any) -> Callable:
37
+ """Serializer for object."""
38
+ return utils.get_serializer(obj, self.request)
39
+
40
+ def _dump(self, data: dict, filepath: Path = None) -> Path:
41
+ """Dump serialized data to disk."""
42
+ filepath = filepath if filepath else self.filepath
43
+ # Create container, if it does not exist
44
+ path_utils.get_parent_folder(filepath)
45
+ with open(filepath, "w") as fh:
46
+ json.dump(data, fh, indent=2, sort_keys=True)
47
+ return filepath
48
+
49
+ def dump(self) -> List[Path]:
50
+ """Serialize objects."""
51
+ return []
52
+
53
+ def export_data(
54
+ self,
55
+ base_path: Path,
56
+ data_hooks: List[Callable] = None,
57
+ obj_hooks: List[Callable] = None,
58
+ ) -> List[Path]:
59
+ """Write data to filesystem."""
60
+ if not base_path.exists():
61
+ base_path.mkdir(parents=True)
62
+ self.base_path = base_path
63
+ self.data_hooks = self.data_hooks or data_hooks or []
64
+ self.obj_hooks = self.obj_hooks or obj_hooks or []
65
+ paths = self.dump()
66
+ return paths
@@ -0,0 +1,58 @@
1
+ <configure
2
+ xmlns="http://namespaces.zope.org/zope"
3
+ xmlns:zcml="http://namespaces.zope.org/zcml"
4
+ >
5
+ <!-- Main exporter -->
6
+ <adapter
7
+ factory=".Exporter"
8
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
9
+ />
10
+
11
+ <!-- Exporters -->
12
+ <adapter
13
+ factory=".content.ContentExporter"
14
+ provides="plone.exportimport.interfaces.INamedExporter"
15
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
16
+ name="plone.exporter.content"
17
+ />
18
+ <adapter
19
+ factory=".principals.PrincipalsExporter"
20
+ provides="plone.exportimport.interfaces.INamedExporter"
21
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
22
+ name="plone.exporter.principals"
23
+ />
24
+ <adapter
25
+ factory=".redirects.RedirectsExporter"
26
+ provides="plone.exportimport.interfaces.INamedExporter"
27
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
28
+ name="plone.exporter.redirects"
29
+ />
30
+ <adapter
31
+ factory=".relations.RelationsExporter"
32
+ provides="plone.exportimport.interfaces.INamedExporter"
33
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
34
+ name="plone.exporter.relations"
35
+ />
36
+ <adapter
37
+ factory=".translations.TranslationsExporter"
38
+ provides="plone.exportimport.interfaces.INamedExporter"
39
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
40
+ name="plone.exporter.translations"
41
+ />
42
+ <adapter
43
+ factory=".discussions.DiscussionsExporter"
44
+ provides="plone.exportimport.interfaces.INamedExporter"
45
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
46
+ name="plone.exporter.discussions"
47
+ />
48
+
49
+ <configure zcml:condition="installed plone.app.portlets">
50
+ <adapter
51
+ factory=".portlets.PortletsExporter"
52
+ provides="plone.exportimport.interfaces.INamedExporter"
53
+ for="plone.base.interfaces.siteroot.IPloneSiteRoot"
54
+ name="plone.exporter.portlets"
55
+ />
56
+ </configure>
57
+
58
+ </configure>
@@ -0,0 +1,159 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone import api
4
+ from plone.base.interfaces import IPloneSiteRoot
5
+ from plone.dexterity.content import DexterityContent
6
+ from plone.exportimport import interfaces
7
+ from plone.exportimport import logger
8
+ from plone.exportimport import settings
9
+ from plone.exportimport import types
10
+ from plone.exportimport.interfaces import IExportImportRequestMarker
11
+ from plone.exportimport.utils import content as content_utils
12
+ from plone.exportimport.utils import request_provides
13
+ from typing import Callable
14
+ from typing import Generator
15
+ from typing import List
16
+ from typing import Optional
17
+ from zope.interface import implementer
18
+
19
+
20
+ @implementer(interfaces.INamedExporter)
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"
27
+
28
+ def all_objects(self) -> Generator:
29
+ """Return all objects to be serialized."""
30
+ query = self.query
31
+ catalog = api.portal.get_tool("portal_catalog")
32
+ if "object_provides" not in query:
33
+ query["object_provides"] = "plone.dexterity.interfaces.IDexterityContent"
34
+ brains = catalog.unrestrictedSearchResults(**query)
35
+ logger.info(f"Exporting {len(brains)}")
36
+ for index, brain in enumerate(brains, start=1):
37
+ try:
38
+ obj = brain.getObject()
39
+ except Exception:
40
+ brain_path = brain.getPath()
41
+ msg = f"Error getting object {brain_path} from brain"
42
+ self.errors.append({"path": brain_path, "message": msg})
43
+ logger.exception(msg, exc_info=True)
44
+ else:
45
+ yield obj
46
+
47
+ if not index % 100:
48
+ logger.info(f"Handled {index} items...")
49
+
50
+ def serialize(self, obj: DexterityContent) -> dict:
51
+ """Serialize object."""
52
+ obj_uid = content_utils.get_uid(obj)
53
+ obj_path = content_utils.get_obj_path(obj)
54
+ config = types.ExporterConfig(
55
+ site=self.site,
56
+ site_root_uid=self.site.UID(),
57
+ request=self.request,
58
+ serializer=self._serializer(obj),
59
+ logger_prefix=f"- {obj_path}:",
60
+ )
61
+ kwargs = {}
62
+ is_site_root = IPloneSiteRoot.providedBy(obj)
63
+ is_folderish = content_utils.is_folderish(obj)
64
+ if is_folderish:
65
+ if not is_site_root:
66
+ kwargs["include_items"] = False
67
+ # Process metadata
68
+ for helper in content_utils.metadata_helpers():
69
+ logger.debug(f"{config.logger_prefix} Updating metadata {helper.name}")
70
+ value = helper.func(obj, config)
71
+ if value is not None:
72
+ getattr(self.metadata, helper.name)[obj_uid] = value
73
+
74
+ # Apply object hooks
75
+ for func in self.obj_hooks:
76
+ logger.debug(
77
+ f"{config.logger_prefix} Running object hook {func.__name__} on object"
78
+ )
79
+ obj = func(obj, config)
80
+
81
+ # Serialize
82
+ data = config.serializer(**kwargs)
83
+
84
+ # Fix serialized data
85
+ for fixer in content_utils.fixers():
86
+ logger.debug(
87
+ f"{config.logger_prefix} Running {fixer.name} on serialized data"
88
+ )
89
+ data = fixer.func(data, obj, config)
90
+ # Enrich
91
+ for enricher in content_utils.enrichers():
92
+ logger.debug(f"{config.logger_prefix} Running {enricher.name}")
93
+ additional = enricher.func(obj, config)
94
+ if additional:
95
+ data.update(additional)
96
+
97
+ # Apply data hooks
98
+ for func in self.data_hooks:
99
+ logger.debug(
100
+ f"{config.logger_prefix} Running data hook {func.__name__} on payload"
101
+ )
102
+ data = func(data, obj, config)
103
+
104
+ # Cleanup data
105
+ for cleaner in content_utils.cleaners():
106
+ logger.debug(
107
+ f"{config.logger_prefix} Running {cleaner.name} on serialized data"
108
+ )
109
+ data = cleaner.func(data, config)
110
+ return data
111
+
112
+ def dump_one(self, obj: DexterityContent) -> Path:
113
+ """Serialize object and dump it to disk."""
114
+ obj_path = content_utils.get_obj_path(obj)
115
+ # Serialize object
116
+ data = self.serialize(obj)
117
+ base_path = self.base_path
118
+ filepath = base_path / self.filename_fmt.format(**data)
119
+ filepath = self._dump(data, filepath)
120
+ logger.debug(f"- {obj_path}: Wrote serialized data to {filepath}")
121
+ # Add to list of files
122
+ self.metadata._all_[obj_path] = str(filepath.relative_to(base_path))
123
+ return filepath
124
+
125
+ def dump_metadata(self) -> Path:
126
+ metadata = self.metadata.export()
127
+ filepath = self.base_path / "__metadata__.json"
128
+ return self._dump(metadata, filepath)
129
+
130
+ def dump(self) -> List[Path]:
131
+ """Serialize contents and dump them to disk."""
132
+ paths = []
133
+ with request_provides(self.request, IExportImportRequestMarker):
134
+ for obj in self.all_objects():
135
+ path = self.dump_one(obj)
136
+ if path:
137
+ paths.append(path)
138
+ # Add list of blobs to serialization
139
+ paths.insert(0, self.dump_metadata())
140
+ return paths
141
+
142
+ def export_data(
143
+ self,
144
+ base_path: Path,
145
+ data_hooks: List[Callable] = None,
146
+ obj_hooks: List[Callable] = None,
147
+ query: Optional[dict] = None,
148
+ ) -> List[Path]:
149
+ # Content in a subpath of base_path
150
+ base_path = base_path / self.name
151
+ query = query if query else {}
152
+ site = self.site
153
+ self.query = query if query else {"path": content_utils.get_obj_path(site)}
154
+ metadata = types.ExportImportMetadata()
155
+ self.metadata = metadata
156
+ self.request[settings.EXPORT_CONTENT_METADATA_KEY] = metadata
157
+ self.request[settings.EXPORT_PATH_KEY] = base_path
158
+ self.default_site_language = site.language
159
+ return super().export_data(base_path, data_hooks, obj_hooks)
@@ -0,0 +1,21 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone.exportimport import interfaces
4
+ from plone.exportimport import logger
5
+ from plone.exportimport.utils import discussions as utils
6
+ from typing import List
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(interfaces.INamedExporter)
11
+ class DiscussionsExporter(BaseExporter):
12
+ name: str = "discussions"
13
+
14
+ def dump(self) -> List[Path]:
15
+ """Serialize object and dump it to disk."""
16
+ discussions = utils.get_discussions()
17
+ filepath = self._dump(discussions, self.filepath)
18
+ logger.debug(
19
+ f"- Discussions: Wrote {len(discussions)} discussions to {filepath}"
20
+ )
21
+ return [filepath]
@@ -0,0 +1,19 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone.exportimport import interfaces
4
+ from plone.exportimport import logger
5
+ from plone.exportimport.utils import portlets as utils
6
+ from typing import List
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(interfaces.INamedExporter)
11
+ class PortletsExporter(BaseExporter):
12
+ name: str = "portlets"
13
+
14
+ def dump(self) -> List[Path]:
15
+ """Serialize object and dump it to disk."""
16
+ portlets = utils.get_portlets()
17
+ filepath = self._dump(portlets, self.filepath)
18
+ logger.debug(f"- Portlets: Wrote {len(portlets)} portlets to {filepath}")
19
+ return [filepath]
@@ -0,0 +1,24 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone.exportimport import interfaces
4
+ from plone.exportimport import logger
5
+ from plone.exportimport.utils import principals as principals_utils
6
+ from typing import List
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(interfaces.INamedExporter)
11
+ class PrincipalsExporter(BaseExporter):
12
+ name: str = "principals"
13
+
14
+ def dump(self) -> List[Path]:
15
+ """Serialize object and dump it to disk."""
16
+ data = {
17
+ "groups": principals_utils.export_groups(),
18
+ "members": principals_utils.export_members(),
19
+ }
20
+ filepath = self._dump(data, self.filepath)
21
+ logger.debug(
22
+ f"- Principals: Wrote {len(data['groups'])} groups and {len(data['members'])} members to {filepath}"
23
+ )
24
+ return [filepath]
@@ -0,0 +1,19 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone.exportimport import interfaces
4
+ from plone.exportimport import logger
5
+ from plone.exportimport.utils import redirects as utils
6
+ from typing import List
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(interfaces.INamedExporter)
11
+ class RedirectsExporter(BaseExporter):
12
+ name: str = "redirects"
13
+
14
+ def dump(self) -> List[Path]:
15
+ """Serialize object and dump it to disk."""
16
+ redirects = utils.get_redirects()
17
+ filepath = self._dump(redirects, self.filepath)
18
+ logger.debug(f"- Redirects: Wrote {len(redirects)} redirects to {filepath}")
19
+ return [filepath]
@@ -0,0 +1,19 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone.exportimport import interfaces
4
+ from plone.exportimport import logger
5
+ from plone.exportimport.utils import relations as utils
6
+ from typing import List
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(interfaces.INamedExporter)
11
+ class RelationsExporter(BaseExporter):
12
+ name: str = "relations"
13
+
14
+ def dump(self) -> List[Path]:
15
+ """Serialize object and dump it to disk."""
16
+ relations = utils.get_relations()
17
+ filepath = self._dump(relations, self.filepath)
18
+ logger.debug(f"- Relations: Wrote {len(relations)} relations to {filepath}")
19
+ return [filepath]
@@ -0,0 +1,21 @@
1
+ from .base import BaseExporter
2
+ from pathlib import Path
3
+ from plone.exportimport import interfaces
4
+ from plone.exportimport import logger
5
+ from plone.exportimport.utils import translations as utils
6
+ from typing import List
7
+ from zope.interface import implementer
8
+
9
+
10
+ @implementer(interfaces.INamedExporter)
11
+ class TranslationsExporter(BaseExporter):
12
+ name: str = "translations"
13
+
14
+ def dump(self) -> List[Path]:
15
+ """Serialize object and dump it to disk."""
16
+ translations = utils.get_translations()
17
+ filepath = self._dump(translations, self.filepath)
18
+ logger.debug(
19
+ f"- Translations: Wrote {len(translations)} translations to {filepath}"
20
+ )
21
+ return [filepath]