6.1 migration - ModuleNotFoundError: No module named 'Products.CMFPlone.PropertiesTool'

I have a site (several, actually) in 6.0.14 that is failing to upgrade properly. The @@plone-upgrade page gives a warning about packages that have been installed at one point but are no longer present. I believe this is a false positive, those were installed and then uninstalled awhile ago. I am including it here for completeness. The upgrade otherwise performs without error.

However when I load a page I get a 500 on every resource request and the WSGI client outputs this many times:

ModuleNotFoundError: No module named 'Products.CMFPlone.PropertiesTool'
2025-05-14 10:57:45,647 ERROR   [ZODB.Connection:801][waitress-1] Couldn't load state for zope.component.persistentregistry.PersistentAdapterRegistry 0x023e57
Traceback (most recent call last):
  File "/plone/lib64/python3.11/site-packages/ZPublisher/WSGIPublisher.py", line 181, in transaction_pubevents
    yield
  File "/plone/lib64/python3.11/site-packages/ZPublisher/WSGIPublisher.py", line 390, in publish_module
    response = _publish(request, new_mod_info)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/ZPublisher/WSGIPublisher.py", line 268, in publish
    obj = request.traverse(path, validated_hook=validate_user)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/ZPublisher/BaseRequest.py", line 436, in traverse
    bpth(object, self)
  File "/plone/lib64/python3.11/site-packages/ZPublisher/BeforeTraverse.py", line 105, in __call__
    getattr(container.__class__, self._hookname)(container,
  File "/plone/lib64/python3.11/site-packages/Products/CMFPlone/Portal.py", line 132, in __before_publishing_traverse__
    notify(BeforeTraverseEvent(self, REQUEST))
  File "/plone/lib64/python3.11/site-packages/zope/event/__init__.py", line 33, in notify
    subscriber(event)
  File "/plone/lib64/python3.11/site-packages/zope/component/event.py", line 27, in dispatch
    component_subscribers(event, None)
  File "/plone/lib64/python3.11/site-packages/zope/component/_api.py", line 146, in subscribers
    return sitemanager.subscribers(objects, interface)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/zope/interface/registry.py", line 458, in subscribers
    return self.adapters.subscribers(objects, provided)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/zope/interface/adapter.py", line 918, in subscribers
    subscription(*objects)
  File "/plone/lib64/python3.11/site-packages/zope/component/event.py", line 37, in objectEventNotify
    component_subscribers((event.object, event), None)
  File "/plone/lib64/python3.11/site-packages/zope/component/_api.py", line 146, in subscribers
    return sitemanager.subscribers(objects, interface)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/zope/interface/registry.py", line 458, in subscribers
    return self.adapters.subscribers(objects, provided)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/zope/interface/adapter.py", line 918, in subscribers
    subscription(*objects)
  File "/plone/lib64/python3.11/site-packages/plone/browserlayer/layer.py", line 15, in mark_layer
    layers = getAllUtilitiesRegisteredFor(ILocalBrowserLayerType)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/zope/component/_api.py", line 195, in getAllUtilitiesRegisteredFor
    return getSiteManager(context).getAllUtilitiesRegisteredFor(interface)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/zope/interface/registry.py", line 310, in getAllUtilitiesRegisteredFor
    return self.utilities.subscriptions((), interface)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/ZODB/Connection.py", line 788, in setstate
    self._reader.setGhostState(obj, p)
  File "/plone/lib64/python3.11/site-packages/ZODB/serialize.py", line 638, in setGhostState
    state = self.getState(pickle)
            ^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/ZODB/serialize.py", line 631, in getState
    return unpickler.load()
           ^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/ZODB/serialize.py", line 488, in find_global
    return factory(conn, modulename, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/Zope2/Startup/datatypes.py", line 265, in simpleClassFactory
    m = __import__(module, _globals, _globals, _silly)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I know this upgrade removes the portal_properties object and I can see it's gone. But some object in the ZODB is still referencing it?

edit: running zodbverify (can/should I delete these objects?)

ModuleNotFoundError: No module named 'Products.CMFPlone.PropertiesTool': 5
0x0236e5 0x023e56 0x023e57 0x024c37 0x024c38

edit2: I forgot I can inspect with zodbverify

INFO:zodbverify:
Object as dict:
{'__Broken_newargs__': ()}
INFO:zodbverify:
The object is 'obj'
> /plone/lib64/python3.11/site-packages/zodbverify/verify_oid.py(230)verify_oid()
-> pickle, state = storage.load(oid)
(Pdb) obj
<persistent broken Products.CMFPlone.PropertiesTool.PropertiesTool instance b'\x00\x00\x00\x00\x00\x026\xe5'>
(Pdb) c
INFO:zodbverify:
Could not process unknown record 0x0236e5 (b'\x00\x00\x00\x00\x00\x026\xe5'):
INFO:zodbverify:b'\x80\x03cProducts.CMFPlone.PropertiesTool\nPropertiesTool\nq\x00.\x80\x03}q\x01(X\x05\x00\x00\x00titleq\x02X\x19\x00\x00\x00General settings registryq\x03X\x0f\x00\x00\x00site_propertiesq\x04C\x08\x00\x00\x00\x00\x00\x02L8q\x05cProducts.CMFPlone.PropertiesTool\nSimpleItemWithProperties\nq\x06\x86q\x07QX\x08\x00\x00\x00_objectsq\x08}q\t(X\t\x00\x00\x00meta_typeq\nX\x14\x00\x00\x00Plone Property Sheetq\x0bX\x02\x00\x00\x00idq\x0cX\x12\x00\x00\x00navtree_propertiesq\ru}q\x0e(h\nh\x0bh\x0cX\x0f\x00\x00\x00site_propertiesq\x0fu\x86q\x10X\x12\x00\x00\x00navtree_propertiesq\x11C\x08\x00\x00\x00\x00\x00\x02L7q\x12h\x06\x86q\x13QX\x12\x00\x00\x00__ac_local_roles__q\x14}q\x15X\n\x00\x00\x00admin_cronq\x16]q\x17X\x05\x00\x00\x00Ownerq\x18ash\x0cX\x11\x00\x00\x00portal_propertiesq\x19u.'
INFO:zodbverify:Traceback (most recent call last):
  File "/plone/lib64/python3.11/site-packages/zodbverify/verify.py", line 62, in verify_record
    class_info = unpickler.load()
                 ^^^^^^^^^^^^^^^^
  File "/plone/lib64/python3.11/site-packages/ZODB/_compat.py", line 39, in find_class
    return super().find_class(modulename, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ModuleNotFoundError: No module named 'Products.CMFPlone.PropertiesTool'
1 Like

I remembered the full zodbverify guide and was able to identify the problem with it Best practice documentation on ZODB Debugging. Object databases are scary to work with when classes are removed, but this is a powerful light in the dark.

The problem was a registered component and I was able to unregister it in the debugger. Here is the relevant part in my testing (followed by transaction.commit())

>>> site.getSiteManager()
<PersistentComponents plone_demo>
>>> sm=site.getSiteManager()
>>> from Products.CMFCore.interfaces import IPropertiesTool
>>> sm._utility_registrations.get((IPropertiesTool, ''))
(<persistent broken Products.CMFPlone.PropertiesTool.PropertiesTool instance b'\x00\x00\x00\x00\x00\x026\xe5'>, '', None)
>>> component=sm._utility_registrations.get((IPropertiesTool, ''))[0]
>>> sm.unregisterUtility(component, IPropertiesTool, '')
True

And if I look at /manage_components on the source site (6.0) I do see it registered there.

I'm still confused why would this not be a problem for others? I also tested on a local dev site where I did not have a problem, migrating a site created in 6.0.14 to 6.1 I see this registered component, though again, I do not get any errors on that site.

<utility factory="Products.CMFPlone.PropertiesTool.PropertiesTool"
     id="broken object"
     interface="Products.CMFCore.interfaces.IPropertiesTool"/>

But maybe this should still be removed? I'm curious if those with a successful migration also have that as a broken object in their components.

2 Likes