Changing DX content type base class from Item to Container

Hi guys,
Back to the mother of all problems (again), how to change a Dexterity item base class from Item to Container... It's not the first time I've been here, but it seems that old recipes doesn't work anymore as they used to in Plone 5.1.x :frowning:

I followed:
http://blog.redturtle.it/2013/02/25/migrating-dexterity-items-to-dexterity-containers

I even had code that used to work in older projects.

The issue is that it seems all fine, but the migrated objects can't respond to the /folder_contents and /++add++ views, like they don't have the correct interfaces applied...

Anyone has faced this issue lately? Any change in latest Plone versions that could make that an issue?

Cheers,
V.

/cc @alert

Not directly related with your problem, but an idea I was wondering.. is it possible to create a "folderish" behavior transform an item into a container?

I have done this in Plone 5.1 to make news and events folderish. I'd gotten the code from a blog post somewhere, I'll see if I can find the original post. Here's the code for changing News Items:

content/news.py:

from plone.dexterity.content import Container
from zope.interface import implementer
from ..interfaces import INewsItem


@implementer(INewsItem)
class NewsItem(Container):
    """Convenience subclass for ``News Item`` portal type
    """

interfaces.py:

from plone.app.contenttypes.interfaces import INewsItem as INewsItemOriginal

class INewsItem(INewsItemOriginal):
    """ Folderish News """

Then also update the News_Item.xml to point the klass and schema at your custom versions.

Note: I got this from Extend Default Content Types

@cdw9 thanks, the basic Plone code is fine. The problem is with the migration of the existing types. This is my upgrade step code:

def convert_news_types_to_folderish(context):
for news_brain in api.content.find(
     portal_type=["News Item", "Pressemitteilung", "Wichtiges"]):
    obj = news_brain.getObject()
    parent = obj.aq_parent
    parent._delOb(obj.getId())
    obj.__class__ = NewsItem
    parent._setOb(obj.getId(), obj)
    from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base
    BTreeFolder2Base._initBTrees(parent[obj.getId()])
    parent[obj.getId()].reindexObject(idxs=['is_folderish', 'object_provides', ]) # noqa

    parent[obj.getId()].reindexObject()

transaction.commit()

Which is a mix of both posts I mentioned. I changed to the ._setOb code one instead of the suggested by Redturtle one because it triggers an error now (I guess that with earlier Plone versions it doesn't) on item assignment back into the tree:

  Module plone.folder.ordered, line 221, in __setitem__
  Module Products.BTreeFolder2.BTreeFolder2, line 449, in _setObject
  Module AccessControl.owner, line 219, in manage_fixupOwnershipAfterAdd
  Module plone.folder.ordered, line 102, in objectValues
  Module plone.folder.ordered, line 88, in objectIds
  Module plone.folder.ordered, line 54, in getOrdering
  Module zope.component._api, line 99, in getAdapter
  ComponentLookupError: (<Container at /Plone/beispiele/pressemitteilung-deutsche-physikerinnentagung-2018>, <InterfaceClass plone.folder.interfaces.IOrdering>, u'')

Not sure what the problem is by this description.
Can you double check that the __class__ is persisting to the new one?
Also there is an issue that @reinhardt found with relations that can bring back the __class__ to the original state and at the end he was able to solve using https://pypi.org/project/zodbupdate/.

Another option might be aliasing your original class (if it is not something really basic as plone.dexterity.content.Item) or providing the missing interfaces via some zcml registration.

<class class="dotted.path.to.class">
  <implements interface="my.magic.interface" />
</class>

EDITED: Discard this, from the info about your latest reply it seems that the __class__ is not persiting.

I see now:

ComponentLookupError: (<Container at /Plone/beispiele/pressemitteilung-deutsche-physikerinnentagung-2018>, <InterfaceClass plone.folder.interfaces.IOrdering>, u'')

As you can see your object is still a container.
Try to perform the migration with zodbupdate or to clear and rebuild the relation catalog.

Sorry, I pasted the code from early tests, the class is persisted (NewsItem) which is:

@implementer(INewsItem)
class NewsItem(Container):
"""Convenience subclass for ``News Item`` portal type
"""

the later error is the same but with the new class, sorry for the confusion. I'm trying to migrate from DX Item to the new folderish NewsItem.

I guess the IOrdering adapter is found for the newly created items (not migrated ones).
If yes going to manage_interfaces on a newly created one and on a migrated one might show you some missing interfaces you are missing.

Oddly enough, they are exactly the same! :frowning:

I even removed the filtering in the error log, just in case something was causing the 404 on folder_contents and ++add++ views, but no luck either.

Also the adapter is not registered for the migrated items (and it is for the newly created ones)... as said, as if some interfaces were not effectively being applied on the migrated objects.

I ran into a similar error and tried to fix it.

Forgot about that! Thanks for pointing it out again :slight_smile:
I triggered a test build,
@sneridagh can you confirm @jianaijun patch fixes your issue (i.e. apply alsoProvides(obj, IOrdering)) to your migrated objects.

@alert Indeed this was the missing step! I can confirm that works like a charm.
Thanks @jianaijun !!

and thanks a lot Ale for your help!!!

2 Likes