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

No comments: