I am trying to make a control panel for my Plone 4 site so that when a button is clicked, broken relations are removed from the site. Unfortunately, I'm having trouble removing broken relations.
I am testing for two dexterity content types currently:
Agreement (my.product.agreement)
Project Sample(my.project.projectsample)
The Agreement has a workflow with several states. When the Agreement reaches the Activated state, a Project Sample is created (through a Products.DCWorkflow.interfaces.IAfterTransitionEvent subscriber) . The Agreement's relatedItems is set to include the Project Sample and the Project Sample's relatedItems is set to include the Agreement.
from plone import api
from z3c.relationfield import RelationValue
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
def getProjectSamplesFolder():
....
....
def agreementTransitioned(obj,event):
workflow_tool = api.portal.get_tool(name='portal_workflow')
current_user = api.user.get_current()
projects_folder = getProjectSamplesFolder()
action = event.status['action']
if action <> 'activate':
return
if len(obj.relatedItems) == 0:
int_ids = getUtility(IIntIds)
#use api to create ProjectSample and place it in projects_folder
new_project_sample = api.create(....)
....
ps_id = int_ids.getId(new_project_sample)
obj.relatedItems = [RelationValue(ps_id)]
obj_id = int_ids.getId(obj)
new_project_sample.relatedItems = [RelationValue(obj_id)]
new_project.reindexObject()
obj.reindexObject()
When viewing an Agreement or Project Sample, the related items portlet shows the related item as it should. However, if I delete one or the other, the relatedItems portlet on the one that wasn't deleted won't render correctly, because the relations are broken.
It shows up as a message where the portlet should be shown:
error while rendering plone.belowcontentbody.relateditems
Originally I was going to do a delete event, but I was having trouble with the delete event firing multiple times. So I figured a control panel that I could run once in a while would clean up broken relations, outside of just the two content types.
from plone.app.controlpanel.form import ControlPanelForm
from zope.interface import Interface
from zope.formlib import form as formlib
from plone import api
from my.product import MessageFactory as _
class IBrokenRelationsCleanupUtility(Interface):
"""Interface for utility that removes broken relations
"""
pass
class BrokenRelationsCleanupUtility(ControlPanelForm):
form_fields = IBrokenRelationsCleanupUtility
label = u"Broken Relations Cleanup Utility"
description = u"Utility for cleaning up broken relations"
form_name = u"Broken Relations Cleanup Utility"
@formlib.action(_(u'label cleanup broken relations', default=u'Run Cleanup'),
name=u'clean-broken-relations')
def handle_cleanup_broken_relations(self, action, data):
#run code for cleaning up broken relations
The handler runs. I tried two different approaches:
The first approach is based on an answer I found on a StackOverflow question:
...
from z3c.relationfield.event import updateRelations
from z3c.relationfield.interfaces import IHasRelations
from zc.relation.interfaces import ICatalog
from zope.component import getUtility
....
class BrokenRelationsCleanupUtility(ControlPanelForm):
....
def handle_cleanup_broken_relations(self, action, data):
rcatalog = getUtility(ICatalog)
rcatalog.clear()
pc = api.portal.get_tool(name='portal_catalog')
brains = pc.searchResults(object_provides=IHasRelations.__identifier__)
for brain in brains:
obj = brain.getObject()
updateRelations(obj, None)
print "Catalog rebuilt for %s objects" % len(brains)
The code runs and it prints:
Catalog rebuilt for 885 objects
However, there are still broken relations as the relatedItems portlet is broken still.
The second approach uses collective.cleanup, which just redirects the browser to the page where collective.catalogcleanup (https://github.com/collective/collective.catalogcleanup) runs its code:
class BrokenRelationsCleanupUtility(ControlPanelForm)
....
def handle_cleanup_broken_relations(self, action, data):
catalogcleanup = api.portal.get().absolute_url() + "/@@collective-catalogcleanup?dry_run=false"
self.request.response.redirect(catalogcleanup)
It redirects to the page and it says:
Starting catalog cleanup.
NO dry_run SELECTED. CHANGES ARE PERMANENT.
Handling catalog portal_catalog.
Brains in portal_catalog: 885
portal_catalog: removed 0 brains without UID.
portal_catalog: removed no brains in object check.
portal_catalog: 0 non unique uids found.
portal_catalog: 0 items given new unique uids.
portal_catalog: total problems: 0
Handling catalog uid_catalog.
Brains in uid_catalog: 0
uid_catalog: removed 0 brains without UID.
uid_catalog: removed no brains in object check.
uid_catalog: 0 non unique uids found.
uid_catalog: 0 items given new unique uids.
uid_catalog: total problems: 0
Handling catalog reference_catalog.
Brains in reference_catalog: 0
reference_catalog: removed 0 brains without UID.
reference_catalog: removed no brains in object check.
reference_catalog: removed no brains in reference check.
reference_catalog: total problems: 0
Done with catalog cleanup.
Unfortunately, its the same thing with relatedItems rendering improperly.
I'm afraid I'm stuck on what to do. Am I approaching relations incorrectly?