Sometimes we have some folders or some documents which should have not been deleted.

But our customers/users have deleted them and it occurs some errors on application.

So we decided to create a new package to prevent delete or/and move actions.


How it works

We add marker interface to objects which can not be deleted or renamed (= moved).

We subscribe all IItem objects to OFS.interfaces.IObjectWillBeRemovedEvent and OFS.interfaces.IObjectWillBeMovedEvent

When one of these events is received, and object is marked as not deleted or not renamed, we raised an exception and object is not deleted or moved.


In the futur, we expect to add a dashboard to have a view of all contents with these markers interfaces to easily use it.


You can also set some contents not deleteable (for example) as this in your setuphandler :

from collective.preventactions.interfaces import IPreventDelete
from plone import api
from zope.interface import alsoProvides

def post_install(context):
obj = api.content.get('/Plone/content-not-deleteable')
alsoProvides(obj, IPreventDelete)


Now you can have a look at source code of package and try it.



New package: collective.geo.faceted

Why did we create collective.geo.faceted ?

We use collective.geo suite for geolocatisation for some of our projects. We also use eea.facetednavigation to easily find content into our website or application.

So we decided to add a map view for eea.facetednavigation and we created collective.geo.faceted.

How it works


We prefer to use Leaflet than OpenLayers, because it seems easier to use for us.

So we decided to use collective.geo.leaflet machinery for map creation.


We use geojson standard to add points on map. It is a famous standard used to geo content.

The view created for "faceted" will simply update the geojson and this geojson will also update the map. For the generation of geojson, we extended collective.geo.json view.


Map is added into a viewlet dedicated to the faceted view. We choose to use a viewlet out of 'content-core slot'  (content-core slot is used by faceted to update automatically the  contents). Indeed each technology (faceted and map) uses singular  javascript , it seems better to not mix both technology.

Plone objects are on map  and they are updated thanks to these lines of code:

jQuery(document).ready(function() {

This code fetches "faceted" events when "faceted" has modifed its criterias and used update_map javascript function to update new geojson on map.

Image is better than words:


This package is tested for Plone 4 with plone.app.contenttypes used as default Plone content types.

Maybe in future we should add a profile like plone5  e.g. example.p4p5 or create a branch plone4 on github and make master branch used to plone5.


Migrate Archetypes content types to Dexterity

By this migartion, I had 2 goals:

  • make my first step for migration to Plone 5
  • use multiligual site with Plone 4 and plone.app.multilingual
  • use plone.app.event (for recurence, no end event, ...).

I also don't want to make big visual changes for my clients. So I decided to not use plone.app.widgets at this moment. I prefere use it with Plone 5. So I will pin plone.app.event 1.1.x. Indeed newer versions of plone.app.event use plone.app.widgets.

For this migration I have to done 2 'steps'. I made a 'custom migration' and I had a profile with an upgrade step. I will explain that below.

For migration, I first have install and pin some packages in my buildout:

  • Add plone.app.contenttypes to my buildout (with 1.1.x version/branch)
  • Pin plone.app.event to 1.1.x version/branch
  • Pin plone.outputfilters 2.1.2 for this problem

Creation of a new profile

I choose to add new profile called 'migratetodx' for making migration. I prefere use a new profile instead of a upgrade step but all migration can be used into a upgrade step.

So I started with creating new profile like this :

        title="cpskin.migration: migrate at to dx"
        description="Updates CPSkin to dexterity"

Created folder profiles/migratetodx and added a metadata.xml file like this :

<?xml version="1.0"?>


Add behaviors

Lead image

For lead image, all job is already done in plone.app.contenttypes. I just had to add behavior for types you would like to migrate.

So in my profile, I added files like profiles/migrate/types/Folder.xml

<?xml version="1.0"?>
<object name="Folder">
  <property name="behaviors" purge="false">
    <element value="plone.app.contenttypes.behaviors.leadimage.ILeadImage"/>

Other collective packages

I added others packages from collective: Collective.geo.*, collective.plonetruegallery and eea.facetednavigation.

So I added other behaviors for my folder type into Folder.xml:

<?xml version="1.0"?>
<object name="Folder">
<property name="behaviors" purge="false">
<element value="plone.app.contenttypes.behaviors.leadimage.ILeadImage"/>
<element value="collective.geo.behaviour.interfaces.ICoordinates" />
<element value="eea.facetednavigation.subtypes.interfaces.IPossibleFacetedNavigable"/>
<element value="collective.plonetruegallery.interfaces.IGallery"/>

Add import step

I created a profiles/migratetodx/import_steps.xml

<?xml version="1.0"?>
  <import-step id="cpskin.migration.migratetodx"
               title="cpskin.migration: import step for migration">
    <dependency step="typeinfo" />

And I use migrate.py for preparing migration, starting migration (with migration view) and fixing image scales.

Problems with memoize

We use caching for our sites and applications. And during migration, I saw than I had some problems with the cache and with plone.memoize. We decide to use an empty plone.memoize cache and keep this cache empty with this code into import step.

In my migrate.py file, I used this code:

from plone import api
from zope.annotation.interfaces import IAnnotations

def migratetodx(context):
    if context.readDataFile('cpskin.migration-migratetodx.txt') is None:
portal = api.portal.get()
request = getattr(portal, 'REQUEST', None)
class EmptyMemoize(dict):

    def __setitem__(self, key, value):

annotations = IAnnotations(request)
annotations['plone.memoize'] = EmptyMemoize()

Fix image scale

We also have to fix new way to get image scale, I was inspared by  this code. It get all richtext content and check if it needs to change image_[preview] to @@images/image/[preview].

I also use this code for getting all portlets static and update it.

from zope.component import getMultiAdapter
from zope.component import getUtility
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import IPortletAssignmentMapping

def image_scale_fixer(text):
    if text:
        for old, new in IMAGE_SCALE_MAP.items():
            # replace plone.app.imaging old scale names with new ones
            text = text.replace(
            # replace AT traversing scales
            text = text.replace(
    return text

def fix_portlets_image_scales(obj):
    managers = [u'plone.leftcolumn', u'plone.rightcolumn']
    for manager in managers:
        column = getUtility(IPortletManager, manager)
        mappings = getMultiAdapter((obj, column), IPortletAssignmentMapping)
        for key, assignment in mappings.items():
            # skip possibly broken portlets here
            if not hasattr(assignment, '__Broken_state__'):
                if getattr(assignment, 'text', None):
                    clean_text = image_scale_fixer(assignment.text)
                    assignment.text = clean_text
                logger.warn(u'skipping broken portlet assignment {0} '
                            'for manager {1}'.format(key, manager))


Custom migration

Migrate extended archetypes field

I migrated an extend archetype filed named 'hiddentags'. For that is use ICustomMigrator adapter. I added this line on my configure.zcml :

<adapter name="mymigrator" factory=".migrate.MyMigrator" />

And I created a class MyMigrator with a "migrate"method in my file migrate.py

from plone.app.contenttypes.migration.migration import ICustomMigrator
from zope.component import adapter
from zope.interface import implementer
from zope.interface import Interface

class MyMigrator(object):

    def __init__(self, context):
        self.context = context

    def migrate(self, old, new):
        # hiddenTags
        if getattr(old, 'hiddenTags', None):
            new.hiddenTags = old.hiddenTags

Migrate marker interfaces

I had some marker interfaces on our content, in this snippet, I will show how I migrated eea.facetednavigation marker :

from eea.facetednavigation.settings.interfaces import IDisableSmartFacets
from eea.facetednavigation.settings.interfaces import IHidePloneLeftColumn
from eea.facetednavigation.settings.interfaces import IHidePloneRightColumn
from eea.facetednavigation.subtypes.interfaces import IFacetedNavigable
from eea.facetednavigation.subtypes.interfaces import IFacetedWrapper
interfaces = [
for interface in interfaces:
    if interface.providedBy(old):
        alsoProvides(new, interface)

Migrate faceted criteria

In our site, we use faceted navigation. and we have to migrate criteria for all our faceted view. I have done into custom migration with that code :

if IFacetedNavigable.providedBy(old):
criteria = Criteria(new)

Migrate object with coordinates

Again in migrate method, I had this snippet

from collective.geo.behaviour.behaviour import Coordinates

old_coord = Coordinates(old).coordinates
new_coord = Coordinates(new)
new_coord.coordinates = old_coord

Setting time-zone to new event

When I wrote this code, there is still a bug into plone.app.event 1.1.0 and plone.app.contenttypes 1.1.0. Time-zone is not set on each event during migration, I forced id:

from plone.app.event.dx.interfaces import IDXEvent

if IDXEvent.providedBy(new):
new.timezone = timezone


I learnt a lot of Plone migration all along my work.Each migration depend on plugins you use,

And this is link for my 'import step' script. I hope you can use some piece of code of it.

Document Actions