1

I have a couple of broken objects that I wish to loop through in python script. My use case is as follows: I have renamed my custom product from my.oldproduct to my.newproduct. This has caused the previous objects saved with my.oldproduct to be broken and thus inaccessible. There is a workaround to this as detailed here: Updating broken objects

Now what I want to do is create a python script in the ZMI to loop though all the broken content, change/update them, and thus cause them to be saved using my.newproduct.

I've been unable get the old objects as they are not listed. See a sample of my python script to list all the content in the site, yet they still do not show:

from Products.CMFCore.utils import getToolByName

app = context.restrictedTraverse('/')
sm = app.plone.getSiteManager()

catalog = getToolByName(context, 'portal_catalog')
results = catalog.searchResults()

count = 0
for obj in results:
    print obj.meta_type
    count += 1

print str("Found " + str(count) + " matching objects")

return printed

How can I get the broken objects from my.oldproduct to be listed?

Community
  • 1
  • 1
Frankline
  • 40,277
  • 8
  • 44
  • 75

1 Answers1

4

You'll need to traverse your whole ZODB manually, I'm afraid. If these objects are content objects, you should be able to use the standard OFS methods:

from collections import deque
from datetime import datetime

import transaction
from zope.app.component.hooks import setSite
from Testing.makerequest import makerequest
from AccessControl.SecurityManagement import newSecurityManager

from my.newproduct.types import ArchetypesContentType


site_id = 'Plone'     # adjust to match your Plone site object id.
admin_user = 'admin'  # usually 'admin', probably won't need adjusting
app = makerequest(app)
site = app[site_id]
setSite(site)
user = app.acl_users.getUser(admin_user).__of__(site.acl_users)
newSecurityManager(None, user)


def treeWalker(root):
    # stack holds (parent, id, obj) tuples
    stack = deque([(None, None, root)])
    while stack:
        parent, id, next = stack.popleft()
        try:
            stack.extend((next, id, child) for id, child in next.objectItems())
        except AttributeError:
            # No objectItems method
            pass
        yield parent, id, next


count = 0
for parent, id, obj in treeWalker(site):
    if isinstance(obj, ArchetypesContentType):
        print 'Found content type object {} at {}'.format(id, '/'.join(object.getPhysicalPath()))
        obj._p_changed = True  # mark it as changed, force a commit
        count += 1
        if count % 100 == 0:
            # flush changes so far to disk to minimize memory usage
            transaction.savepoint(True)
            print '{} - Processed {} items'.format(datetime.now(), count)

transaction.commit()

This assumes you already included the work-around you linked to; there is little point in trying to do the above with ZODB.broken.Broken objects.

The above script acts as a bin/instance run script, run it as such:

bin/instance run path/to/this/script.py

You are going to process everything in the site, a fairly hefty process that will involve a lot of cache churn and possibly a large commit with potential conflicts. You do not want to run this as a through-the-web script, really.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thanks Martijn but I'm unable to execute this code as a python script in the ZMI due to insufficient privileges. (RestrictedPython). I'm finding it odd to add this to my product's file system to allow executing the above considering it is a one-off task. – Frankline Apr 12 '13 at 05:16
  • 1
    @Frankline: Fleshed it out some more; you *really* do not want to run this through-the-web. It is possible (replace the `deque` with a regular list and `.pop(0)` instead of `.popleft()`, leave out the `transaction.commit()` and `print` statements), I just don't think that'd be a good idea. – Martijn Pieters Apr 12 '13 at 08:36