diff --git a/news/118.bugfix b/news/118.bugfix new file mode 100644 index 0000000..c9ece02 --- /dev/null +++ b/news/118.bugfix @@ -0,0 +1 @@ +Fix handling of relation-fields for working copies of folderish content. [pbauer] diff --git a/plone/app/iterate/base.py b/plone/app/iterate/base.py index 001263e..062574c 100644 --- a/plone/app/iterate/base.py +++ b/plone/app/iterate/base.py @@ -34,8 +34,6 @@ from plone.app.iterate.interfaces import ICheckinCheckoutPolicy from plone.app.iterate.interfaces import IObjectCopier from plone.app.iterate.util import get_storage -from Products.CMFCore import interfaces as cmf_ifaces -from Products.CMFCore.utils import getToolByName from zc.relation.interfaces import ICatalog from zope import component from zope.component import queryAdapter @@ -114,43 +112,6 @@ class BaseContentCopier: def __init__(self, context): self.context = context - def _recursivelyReattachUIDs(self, baseline, new_baseline): - original_refs = len(new_baseline.getRefs()) - original_back_refs = len(new_baseline.getBRefs()) - new_baseline._setUID(baseline.UID()) - new_refs = len(new_baseline.getRefs()) - new_back_refs = len(new_baseline.getBRefs()) - if original_refs != new_refs: - self._removeDuplicateReferences(new_baseline, backrefs=False) - if original_back_refs != new_back_refs: - self._removeDuplicateReferences(new_baseline, backrefs=True) - - if cmf_ifaces.IFolderish.providedBy(baseline): - new_ids = new_baseline.contentIds() - for child in baseline.contentValues(): - if child.getId() in new_ids: - self._recursivelyReattachUIDs(child, new_baseline[child.getId()]) - - def _removeDuplicateReferences(self, item, backrefs=False): - # Remove duplicate (back) references from this item. - reference_tool = getToolByName(self.context, "reference_catalog") - if backrefs: - ref_func = reference_tool.getBackReferences - else: - ref_func = reference_tool.getReferences - try: - # Plone 4.1 or later - brains = ref_func(item, objects=False) - except TypeError: - # Plone 4.0 or earlier. Nothing to fix here - return - for brain in brains: - if brain.getObject() is None: - reference_tool.uncatalog_object(brain.getPath()) - - ################################# - # Checkout Support Methods - def _copyBaseline(self, container): # copy the context from source to the target container source_container = aq_parent(aq_inner(self.context)) diff --git a/plone/app/iterate/dexterity/copier.py b/plone/app/iterate/dexterity/copier.py index 5a952b0..bfa8ab4 100644 --- a/plone/app/iterate/dexterity/copier.py +++ b/plone/app/iterate/dexterity/copier.py @@ -5,11 +5,16 @@ from plone.app.iterate.dexterity import ITERATE_RELATION_NAME from plone.app.iterate.dexterity.relation import StagingRelationValue from plone.app.iterate.event import AfterCheckinEvent +from plone.app.relationfield.event import update_behavior_relations from plone.dexterity.utils import createContentInContainer from plone.dexterity.utils import iterSchemata from Products.CMFCore.utils import getToolByName from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition from z3c.relationfield import event +from z3c.relationfield import RelationValue +from z3c.relationfield.event import updateRelations +from z3c.relationfield.schema import RelationChoice +from z3c.relationfield.schema import RelationList from zc.relation.interfaces import ICatalog from ZODB.PersistentMapping import PersistentMapping from zope import component @@ -191,9 +196,19 @@ def _copyBaseline(self, container): obj.expiration_date = self.context.expiration_date elif name == "subjects": obj.setSubject(self.context.Subject()) + elif isinstance(field, RelationList): + if value: + field.set( + obj, + [RelationValue(i.to_id) for i in value if not i.isBroken()], + ) + elif isinstance(field, RelationChoice): + if value and not value.isBroken(): + field.set(obj, RelationValue(value.to_id)) else: field.set(obj, value) + update_relation_catalog(obj) obj.reindexObject() # copy annotations @@ -232,9 +247,18 @@ def _replaceBaseline(self, baseline): baseline.expiration_date = self.context.expiration_date elif name == "subjects": baseline.setSubject(self.context.Subject()) + elif isinstance(field, RelationList): + if value: + field.set( + baseline, + [RelationValue(i.to_id) for i in value if not i.isBroken()], + ) + elif isinstance(field, RelationChoice): + if value and not value.isBroken(): + field.set(baseline, RelationValue(value.to_id)) else: field.set(baseline, value) - + update_relation_catalog(baseline) baseline.reindexObject() # Move working children (newly created objects) @@ -259,3 +283,12 @@ def _replaceBaseline(self, baseline): wc_container._delObject(wc_id) return baseline + + +def update_relation_catalog(obj): + # updateRelations from z3c.relationfield does not properly update relations in behaviors + # that are registered with a marker-interface. + # update_behavior_relations from plone.app.relationfield does that but does not update + # those in the main schema. + updateRelations(obj, None) + update_behavior_relations(obj, None) diff --git a/plone/app/iterate/tests/test_containers.py b/plone/app/iterate/tests/test_containers.py index f295f3a..b79735d 100644 --- a/plone/app/iterate/tests/test_containers.py +++ b/plone/app/iterate/tests/test_containers.py @@ -29,6 +29,7 @@ from plone.app.testing import TEST_USER_ID from plone.app.testing import TEST_USER_NAME from plone.dexterity.utils import createContentInContainer +from z3c.relationfield import RelationValue from zc.relation.interfaces import ICatalog from zope import component from zope.intid.interfaces import IIntIds @@ -304,3 +305,28 @@ def test_relationship_deleted_on_cancel_checkout(self): rels = list(catalog.findRelations({"from_id": obj_id})) self.assertEqual(len(rels), 0) + + def test_relationfield_handling(self): + # relations are not simply copied + folder = self.portal.docs + doc = folder.doc1 + target = folder.doc2 + intids = component.getUtility(IIntIds) + doc.relatedItems = [RelationValue(intids.getId(target))] + wc = ICheckinCheckoutPolicy(doc).checkout(doc) + self.assertNotEqual(doc.relatedItems[0], wc.relatedItems[0]) + self.assertEqual(doc.relatedItems[0].to_object, target) + self.assertEqual(doc.relatedItems[0].from_object, doc) + self.assertEqual(wc.relatedItems[0].to_object, target) + self.assertEqual(wc.relatedItems[0].from_object, wc) + wc = ICheckinCheckoutPolicy(wc).checkin("modified") + self.assertEqual(doc.relatedItems[0].to_object, target) + self.assertEqual(doc.relatedItems[0].from_object, doc) + self.assertEqual(len(doc.relatedItems), 1) + + wc = ICheckinCheckoutPolicy(doc).checkout(doc) + wc.relatedItems = [RelationValue(intids.getId(folder))] + wc = ICheckinCheckoutPolicy(wc).checkin("modified") + self.assertEqual(doc.relatedItems[0].to_object, folder) + self.assertEqual(doc.relatedItems[0].from_object, doc) + self.assertEqual(len(doc.relatedItems), 1) diff --git a/setup.py b/setup.py index 9363559..13dd918 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ install_requires=[ "Acquisition", "DateTime", + "plone.app.relationfield", "plone.locking", "plone.memoize", "Products.CMFEditions",