Thursday, June 9, 2011

Migrate files from Compound-/ArrayField to File objects in folder

A migration script. The use case is a site which currently allows users to add a list of files to custom objects. I want to make these objects folderish, extract the files from field and store them as regular Plone files, so we can migrate to Plone 4 and use blobstorage.

import transaction
from zope.component import queryUtility
from Acquisition import aq_parent

from plone.i18n.normalizer.interfaces import IIDNormalizer
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import _createObjectByType
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

class MyContentTypeMigrationView(BrowserView):
""" Migrate from non-folderish objects with files in CompoundField/ArrayField
to folderish (containing ATFile objects)."""

__call__ = ViewPageTemplateFile("templates/")

def __init__(self, *args, **kwargs):
super(MyContentTypeMigrationView, self).__init__(*args, **kwargs)
self._catalog = getToolByName(self.context, 'portal_catalog')
self._file_counter = 0
self._mycustomtype_counter = 0
self._normalizer = queryUtility(IIDNormalizer)

def rename(self, obj):
"""Rename object: strip trailing '.pdf', at least from id. """
stripped_extensions = ['.pdf',]
for extension in stripped_extensions:
# No need to set a Title yet, not sure if we have to at all.
# obj.setTitle(obj.Title().rstrip(extension))
parent = aq_parent(obj)
oid = obj.getId()
if oid.endswith(extension):
parent.manage_renameObject(oid, oid.rstrip(extension))
changed = True

def create_file_objects(self, obj):
"""Read files from 'documents' CompoundField, create ATFile objects and
delete files from 'documents' CompoundField.

files = obj.getDocuments()
for field_file in files:
# file from CompoundField may be empty
if field_file is not None:
filename = field_file.filename
if not filename:
# filename may be empty, in that case take it from Plone
# object url
filename = field_file.absolute_url().split('/')[-2]
file_field_id =
# filename must be unicode
filename_safe_uni = safe_unicode(filename)
new_id = self._normalizer.normalize(filename_safe_uni)
self.context.plone_log("Creating file %s (%s)" %
(filename, file_field_id) )
if hasattr(obj, new_id):
self.context.plone_log("Object with id %s exists, skipping" % new_id)
_createObjectByType('File', obj, id=new_id, title=filename)
self.context.plone_log("File object created")
file_object = obj.get(new_id)
self._file_counter += 1
self.context.plone_log("File content set (#%d)" % self._file_counter)
# "Delete" file from CompoundField
delattr(obj, file_field_id)
self.context.plone_log("File deleted from CompoundField")
self.context.plone_log("ERROR creating / setting / deleting file")
import pdb; pdb.set_trace()

def html(self):
"""Render something"""
html = ''
brains = self._catalog(
for brain in brains:
obj = brain.getObject()
# commit subtransaction
self._mycustomtype_counter += 1
self.context.plone_log("MyContentType object nr %d)" % self._mycustomtype_counter)
html += 'migrated <a href="%s">%s</a><br />' % (
obj.absolute_url(), obj.getId() )
return html

How to plug into Plone's id (for url) generating mechanism

Plone can convert titles to id's, which are used in the URL. To use this conversion in your own code, use the IIDNormalizer utility.

[edit: removed broken link to Plone's developer documentation, added code snippet]

from zope.component import queryUtility
from plone.i18n.normalizer.interfaces import IIDNormalizer

id = queryUtility(IIDNormalizer).normalize(title)

Wednesday, June 1, 2011

Plone 3 on Natty: Python 2.4 with PIL

Installing PIL in a (hand-compiled) python2.4 yielded "python "ZLIB (PNG/ZIP) support not available", although i did have zlib1g-dev installed.

Found the answer in Natty puts some libs in /usr/lib/x86_64-linux-gnu/, where PIL can't find them. Symlinking helps.