Skip to content

Commit

Permalink
Merge pull request #71 from collective/constrains
Browse files Browse the repository at this point in the history
export and import constrains
  • Loading branch information
pbauer committed Dec 8, 2021
2 parents 5583517 + 43a1a50 commit 0bd90cf
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 2 deletions.
15 changes: 15 additions & 0 deletions src/collective/exportimport/export_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from plone import api
from plone.i18n.normalizer.interfaces import IIDNormalizer
from plone.restapi.interfaces import ISerializeToJson
from Products.CMFPlone.interfaces.constrains import ISelectableConstrainTypes
from Products.CMFPlone.interfaces.constrains import ENABLED
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.component import getMultiAdapter
Expand Down Expand Up @@ -279,6 +281,7 @@ def export_content(self):
else:
item = serializer()
item = self.fix_url(item, obj)
item = self.export_constraints(item, obj)
if self.migration:
item = self.update_data_for_migration(item, obj)
item = self.global_dict_hook(item, obj)
Expand Down Expand Up @@ -421,6 +424,18 @@ def fix_url(self, item, obj):
item["parent"]["@id"] = parent_url
return item

def export_constraints(self, item, obj):
constrains = ISelectableConstrainTypes(obj, None)
if constrains is None:
return item
if constrains.getConstrainTypesMode() == ENABLED:
key = "exportimport.constrains"
item[key] = {
"locally_allowed_types": constrains.getLocallyAllowedTypes(),
"immediately_addable_types": constrains.getImmediatelyAddableTypes(),
}
return item


def fix_portal_type(portal_type):
normalizer = getUtility(IIDNormalizer)
Expand Down
20 changes: 18 additions & 2 deletions src/collective/exportimport/import_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from plone.namedfile.file import NamedBlobFile
from plone.namedfile.file import NamedBlobImage
from plone.restapi.interfaces import IDeserializeFromJson
from Products.CMFPlone.interfaces.constrains import ISelectableConstrainTypes
from Products.CMFPlone.interfaces.constrains import ENABLED
from Products.CMFPlone.utils import _createObjectByType
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from six.moves.urllib.parse import unquote
Expand Down Expand Up @@ -324,8 +327,8 @@ def import_new_content(self, data): # noqa: C901
)

if not self.update_existing:
container.invokeFactory(item["@type"], item["id"], **factory_kwargs)
new = container[item["id"]]
# create without checking constrains and permissions
new = _createObjectByType(item["@type"], container, item["id"], **factory_kwargs)

new, item = self.global_obj_hook_before_deserializing(new, item)

Expand All @@ -344,6 +347,7 @@ def import_new_content(self, data): # noqa: C901
# based on whether or not there is a blob_path somewhere in the item.
# So handle this case with a separate method.
self.import_blob_paths(new, item)
self.import_constrains(new, item)

self.global_obj_hook(new, item)
self.custom_obj_hook(new, item)
Expand Down Expand Up @@ -476,6 +480,18 @@ def custom_dict_hook(self, item):
item = modifier(item)
return item

def import_constrains(self, obj, item):
if not item.get("exportimport.constrains"):
return
constrains = ISelectableConstrainTypes(obj, None)
if constrains is None:
return
constrains.setConstrainTypesMode(ENABLED)
locally_allowed_types = item["exportimport.constrains"]["locally_allowed_types"]
constrains.setLocallyAllowedTypes(locally_allowed_types)
immediately_addable_types = item["exportimport.constrains"]["immediately_addable_types"]
constrains.setImmediatelyAddableTypes(immediately_addable_types)

def global_obj_hook_before_deserializing(self, obj, item):
"""Hook to modify the created obj before deserializing the data.
Example that applies marker-interfaces:
Expand Down
78 changes: 78 additions & 0 deletions src/collective/exportimport/tests/test_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.namedfile.file import NamedImage
from plone.namedfile.file import NamedBlobImage
from Products.CMFPlone.interfaces.constrains import ENABLED
from Products.CMFPlone.interfaces.constrains import ISelectableConstrainTypes
from Products.CMFPlone.tests import dummy

import json
Expand Down Expand Up @@ -652,6 +654,82 @@ def test_import_contenttree(self):
self.assertEqual(portal["events"].portal_type, "Folder")
self.assertEqual(portal["image"].image.data, dummy.Image().data)

def test_import_imports_but_ignores_constrains(self):
"""Constrains are exported and imported but not checked during import
"""
# First create some content to export.
app = self.layer["app"]
portal = self.layer["portal"]
login(app, SITE_OWNER_NAME)
self.create_demo_content()
self.assertIn("events", portal.contentIds())
self.assertIn("image", portal.contentIds())

# create a collection in self.about
collection = api.content.create(
container=self.about,
type="Collection",
id="collection",
title=u"Collection",
)
# constrain self.about to only allow documents
constrains = ISelectableConstrainTypes(self.about)
constrains.setConstrainTypesMode(ENABLED)
constrains.setLocallyAllowedTypes(["Document"])
constrains.setImmediatelyAddableTypes(["Document"])
from plone.api.exc import InvalidParameterError
with self.assertRaises(InvalidParameterError):
api.content.create(
container=self.about,
type="Collection",
id="collection2",
title=u"Collection 2",
)
transaction.commit()

# Now export the complete portal.
browser = self.open_page("@@export_content")
browser.getControl(name="portal_type").value = [
"Folder",
"Image",
"Link",
"Document",
"Collection",
]
browser.getForm(action="@@export_content").submit(name="submit")
contents = browser.contents
if not browser.contents:
contents = DATA[-1]

data = json.loads(contents)
self.assertEqual(len(data), 7)

# Remove the added content.
self.remove_demo_content()
transaction.commit()
self.assertNotIn("events", portal.contentIds())

# Now import it.
browser = self.open_page("@@import_content")
upload = browser.getControl(name="jsonfile")
upload.add_file(contents, "application/json", "Document.json")
browser.getForm(action="@@import_content").submit()
self.assertIn("Imported 7 items", browser.contents)

# The collection is imported despite the constrain
constrains = ISelectableConstrainTypes(portal["about"])
self.assertEqual(constrains.getConstrainTypesMode(), ENABLED)
self.assertEqual(constrains.getLocallyAllowedTypes(), ["Document"])
self.assertEqual(constrains.getLocallyAllowedTypes(), ["Document"])
self.assertEqual(portal["about"]["collection"].portal_type, "Collection")
with self.assertRaises(InvalidParameterError):
api.content.create(
container=portal["about"],
type="Collection",
id="collection2",
title=u"Collection 2",
)

def _disabled_test_import_blob_path(self):
# This test is disabled, because the demo storage
# has no 'fshelper' from which we can ask the blob path.
Expand Down

0 comments on commit 0bd90cf

Please sign in to comment.