Products.cmfplone: missing root_uuid of navigation-portlet throws error in DB migrated to py3

Created on 4 Jun 2019  路  2Comments  路  Source: plone/Products.CMFPlone

The relevant DB was migrated from 4.3 to 5.2 and then to Python 3. After migrating the DB from py2 to py3 I get a error in the console:

UUIDIndex: query_index tried to look up key <ComputedAttribute object at 0x7fdce6c5c450> from index 'UID' but key was of the wrong type.

The reason seems to be:

Traceback (most recent call last):
  File "/opt/plone/u480phb/.cache/eggs/Products.ZCatalog-4.4-py3.6.egg/Products/PluginIndexes/unindex.py", line 567, in query_index
    s = index.get(k, None)
TypeError: '<' not supported between instances of 'str' and 'ComputedAttribute'

When portlets moved to use z3c.form instead of formlib the navigation also started to store the root of the navigation as a UID rather than a path (root_uid instead of root). See https://github.com/plone/plone.app.portlets/commit/fc1581421#diff-728de4de835790e6e7b32769b29f1747R124

The fallback used when the attribute root_uid is not set at allbreaks in Python 3 - maybe because the implementation of ComputedAttribute changed.

To work around: Edit the offending portlet and set a new-navigation-root. I have no globally useful code to fix this at the moment.

In my project I wrote a upgrade-step like this :

def聽fix_portlet(setup):
    #聽fix聽navigation_portlet聽on portal-root (has聽ComputedValue聽for聽portal聽instead聽of聽a聽UUID)
    from聽plone.portlets.interfaces聽import聽IPortletAssignmentMapping
    from聽plone.portlets.interfaces聽import聽IPortletManager
    from聽zope.component聽import聽getMultiAdapter
    from聽zope.component聽import聽getUtility
    from聽ComputedAttribute聽import聽ComputedAttribute
    column聽=聽getUtility(IPortletManager,聽u'plone.leftcolumn')
    mappings聽=聽getMultiAdapter((portal,聽column),聽IPortletAssignmentMapping)
    for聽key,聽assignment聽in聽mappings.items():
    if聽assignment.__name__聽==聽'navigation'聽and聽isinstance(assignment.root_uid,聽ComputedAttribute):聽聽#聽noqa:聽E501
        # we have no root set anyway.
        assignment.root_uid聽=聽None

Most helpful comment

I had to update my fix for locally assigned portlets and navigation-portlets that were not called 'navigation':

from ComputedAttribute import ComputedAttribute
from plone import api
from plone.app.portlets.portlets.navigation import Renderer
from plone.portlets.interfaces import IPortletAssignmentMapping
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import IPortletRenderer
from zope.component import queryMultiAdapter
from zope.component import queryUtility
from zope.globalrequest import getRequest


def fix_portlets():
    # fix navigation_portlet (has ComputedValue for portal instead of a UUID)
    catalog = api.portal.get_tool('portal_catalog')
    portal = api.portal.get()
    for brain in catalog.getAllBrains():
        try:
            obj = brain.getObject()
        except KeyError:
            log.info('Broken brain for {}'.format(brain.getPath()))
        fix_navigation_portlet_for(obj)
    fix_navigation_portlet_for(portal)


def fix_navigation_portlet_for(obj):
    request = getRequest()
    view = obj.restrictedTraverse('@@view')
    for manager_name in ['plone.leftcolumn', 'plone.rightcolumn']:
        manager = queryUtility(IPortletManager, name=manager_name, context=obj)
        if not manager:
            continue
        mappings = queryMultiAdapter((obj, manager), IPortletAssignmentMapping)
        if not mappings:
            continue
        for key, assignment in mappings.items():
            renderer = queryMultiAdapter(
                (obj, request, view, manager, assignment), IPortletRenderer)
            if not renderer:
                continue
            if isinstance(renderer, Renderer) and isinstance(assignment.root_uid, ComputedAttribute):  # noqa: E501:
                # We have a navigation-portlet!
                assignment.root_uid = None
                log.info('Reset root of navigation-portlet assigned at {} in {}'.format(obj.absolute_url(), manager_name))  # noqa: E501
                log.info('You may need to configure it manually at {}/manage-portlets'.format(obj.absolute_url()))  # noqa: E501

All 2 comments

I had to update my fix for locally assigned portlets and navigation-portlets that were not called 'navigation':

from ComputedAttribute import ComputedAttribute
from plone import api
from plone.app.portlets.portlets.navigation import Renderer
from plone.portlets.interfaces import IPortletAssignmentMapping
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import IPortletRenderer
from zope.component import queryMultiAdapter
from zope.component import queryUtility
from zope.globalrequest import getRequest


def fix_portlets():
    # fix navigation_portlet (has ComputedValue for portal instead of a UUID)
    catalog = api.portal.get_tool('portal_catalog')
    portal = api.portal.get()
    for brain in catalog.getAllBrains():
        try:
            obj = brain.getObject()
        except KeyError:
            log.info('Broken brain for {}'.format(brain.getPath()))
        fix_navigation_portlet_for(obj)
    fix_navigation_portlet_for(portal)


def fix_navigation_portlet_for(obj):
    request = getRequest()
    view = obj.restrictedTraverse('@@view')
    for manager_name in ['plone.leftcolumn', 'plone.rightcolumn']:
        manager = queryUtility(IPortletManager, name=manager_name, context=obj)
        if not manager:
            continue
        mappings = queryMultiAdapter((obj, manager), IPortletAssignmentMapping)
        if not mappings:
            continue
        for key, assignment in mappings.items():
            renderer = queryMultiAdapter(
                (obj, request, view, manager, assignment), IPortletRenderer)
            if not renderer:
                continue
            if isinstance(renderer, Renderer) and isinstance(assignment.root_uid, ComputedAttribute):  # noqa: E501:
                # We have a navigation-portlet!
                assignment.root_uid = None
                log.info('Reset root of navigation-portlet assigned at {} in {}'.format(obj.absolute_url(), manager_name))  # noqa: E501
                log.info('You may need to configure it manually at {}/manage-portlets'.format(obj.absolute_url()))  # noqa: E501

@pbauer Thanks for the snippet !

I used it to migrate one of our site, and added this code in fix_navigation_portlet_for to fix portlets roots automatically :

root_uid = None
try:
    root_path = assignment.root.lstrip("/")
    root = portal.restrictedTraverse(root_path)
    root_uid = root.UID()
    logger.info('Fixed navigation portlet in {} : {}'.format(obj.absolute_url(), root_path))
except KeyError:
    logger.warn('Unable to fix navigation portlet in {}'.format(obj.absolute_url()))
assignment.root_uid = root_uid
del assignment.root
Was this page helpful?
0 / 5 - 0 ratings

Related issues

pbauer picture pbauer  路  5Comments

skleinfeldt picture skleinfeldt  路  5Comments

hvelarde picture hvelarde  路  4Comments

erral picture erral  路  3Comments

davisagli picture davisagli  路  4Comments