[SOLVED] Displaying "related" leadimage in collection album_view

I have created a behavior that provides a "related" image field to dexterity content types.

This uses a ComputedAttribute:

    @ComputedAttribute
    def image(self):
        image_link = getattr(self.context.aq_explicit, 'related_image', False)
        linked_image = image_link and self.context.related_image.to_object.image or ''
        return linked_image

Content types for which this behavior has been enabled can now show this image in a viewlet, the same way as an ordinary leadimage.

My next step would be to allow this content to become available as a collection result and to display in a collection's album view. The relevant code which does this is in plone.app.contenttypes.browser.collection _album_results where there is a check if the object provides an IImage or ILeadImage interface.

Rather than patching this and adding my IRelatedTeaserImageMarker interface to this check (which in testing got me quite far), I would like to add the IImage interface to my objects.

In the ZMI, I can add IImage as a dynamic marker interface, but I seem to be unable to do so in code.

I tried to add it to the behavior itself:

@implementer(IRelatedTeaserImageBehavior)
@adapter(IRelatedTeaserImageMarker, IImage)
class RelatedTeaserImage(object):

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

which results in the error ' The factory can not be declared as multi-adapter.'

Where am I going wrong here?

1 Like

Can you post the full error information (--> error_log object), especially the traceback?

Hi @dieter this happens at startup:

Traceback (most recent call last):
  File "/usr/local/Plone52/zeocluster/parts/instance/bin/interpreter", line 297, in <module>
    exec(compile(__file__f.read(), __file__, "exec"))
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/serve.py", line 255, in <module>
    sys.exit(main() or 0)
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/serve.py", line 251, in main
    return command.run()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/serve.py", line 190, in run
    global_conf=vars)
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/serve.py", line 220, in loadapp
    return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/loadwsgi.py", line 253, in loadapp
    return loadobj(APP, uri, name=name, **kw)
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/loadwsgi.py", line 278, in loadobj
    return context.create()
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/loadwsgi.py", line 715, in create
    return self.object_type.invoke(self)
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/loadwsgi.py", line 209, in invoke
    app = context.app_context.create()
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/loadwsgi.py", line 715, in create
    return self.object_type.invoke(self)
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/loadwsgi.py", line 152, in invoke
    return fix_call(context.object, context.global_conf, **context.local_conf)
  File "/usr/local/Plone52/buildout-cache/eggs/PasteDeploy-2.0.1-py3.7.egg/paste/deploy/util.py", line 55, in fix_call
    val = callable(*args, **kw)
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/run.py", line 71, in make_wsgi_app
    starter.prepare()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/starter.py", line 41, in prepare
    self.startZope()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/Startup/starter.py", line 98, in startZope
    Zope2.startup_wsgi()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/__init__.py", line 50, in startup_wsgi
    _startup()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/App/startup.py", line 143, in startup
    load_zcml()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/App/startup.py", line 58, in load_zcml
    load_site()
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/Zope2/App/zcml.py", line 45, in load_site
    _context = xmlconfig.file(site_zcml)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 657, in file
    include(context, name, package)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 557, in include
    processxmlfile(f, context)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 407, in processxmlfile
    parser.parse(src)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 111, in parse
    xmlreader.IncrementalParser.parse(self, source)
  File "/usr/lib/python3.7/xml/sax/xmlreader.py", line 125, in parse
    self.feed(buffer)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 217, in feed
    self._parser.Parse(data, isFinal)
  File "../Modules/pyexpat.c", line 471, in EndElement
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 381, in end_element_ns
    self._cont_handler.endElementNS(pair, None)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 393, in endElementNS
    self._handle_exception(ex, info)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 391, in endElementNS
    self.context.end()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 703, in end
    self.stack.pop().finish()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 868, in finish
    actions = self.handler(context, **args)
  File "/usr/local/Plone52/buildout-cache/eggs/Zope-4.1.3-py3.7.egg/OFS/metaconfigure.py", line 47, in loadProducts
    xmlconfig.include(_context, zcml, package=product)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 557, in include
    processxmlfile(f, context)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 407, in processxmlfile
    parser.parse(src)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 111, in parse
    xmlreader.IncrementalParser.parse(self, source)
  File "/usr/lib/python3.7/xml/sax/xmlreader.py", line 125, in parse
    self.feed(buffer)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 217, in feed
    self._parser.Parse(data, isFinal)
  File "../Modules/pyexpat.c", line 471, in EndElement
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 381, in end_element_ns
    self._cont_handler.endElementNS(pair, None)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 393, in endElementNS
    self._handle_exception(ex, info)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 391, in endElementNS
    self.context.end()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 703, in end
    self.stack.pop().finish()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 868, in finish
    actions = self.handler(context, **args)
  File "/usr/local/Plone52/buildout-cache/eggs/z3c.autoinclude-0.3.9-py3.7.egg/z3c/autoinclude/zcml.py", line 104, in includePluginsDirective
    includeZCMLGroup(_context, info, filename)
  File "/usr/local/Plone52/buildout-cache/eggs/z3c.autoinclude-0.3.9-py3.7.egg/z3c/autoinclude/zcml.py", line 30, in includeZCMLGroup
    include(_context, filename, includable_package)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 557, in include
    processxmlfile(f, context)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 407, in processxmlfile
    parser.parse(src)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 111, in parse
    xmlreader.IncrementalParser.parse(self, source)
  File "/usr/lib/python3.7/xml/sax/xmlreader.py", line 125, in parse
    self.feed(buffer)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 217, in feed
    self._parser.Parse(data, isFinal)
  File "../Modules/pyexpat.c", line 471, in EndElement
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 381, in end_element_ns
    self._cont_handler.endElementNS(pair, None)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 393, in endElementNS
    self._handle_exception(ex, info)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 391, in endElementNS
    self.context.end()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 703, in end
    self.stack.pop().finish()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 868, in finish
    actions = self.handler(context, **args)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 557, in include
    processxmlfile(f, context)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 407, in processxmlfile
    parser.parse(src)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 111, in parse
    xmlreader.IncrementalParser.parse(self, source)
  File "/usr/lib/python3.7/xml/sax/xmlreader.py", line 125, in parse
    self.feed(buffer)
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 217, in feed
    self._parser.Parse(data, isFinal)
  File "../Modules/pyexpat.c", line 471, in EndElement
  File "/usr/lib/python3.7/xml/sax/expatreader.py", line 381, in end_element_ns
    self._cont_handler.endElementNS(pair, None)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 393, in endElementNS
    self._handle_exception(ex, info)
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/xmlconfig.py", line 391, in endElementNS
    self.context.end()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 703, in end
    self.stack.pop().finish()
  File "/usr/local/Plone52/buildout-cache/eggs/zope.configuration-4.3.1-py3.7.egg/zope/configuration/config.py", line 868, in finish
    actions = self.handler(context, **args)
  File "/usr/local/Plone52/buildout-cache/eggs/plone.behavior-1.3.0-py3.7.egg/plone/behavior/metaconfigure.py", line 177, in behaviorDirective
    for_ = _detect_for(factory, marker)
  File "/usr/local/Plone52/buildout-cache/eggs/plone.behavior-1.3.0-py3.7.egg/plone/behavior/metaconfigure.py", line 100, in _detect_for
    u'The factory can not be declared as multi-adapter.')
zope.configuration.exceptions.ConfigurationError: The factory can not be declared as multi-adapter.
    File "/usr/local/Plone52/zeocluster/parts/instance/etc/site.zcml", line 16.2-16.23
    File "/usr/local/Plone52/buildout-cache/eggs/Products.CMFPlone-5.2.1-py3.7.egg/Products/CMFPlone/configure.zcml", line 110.2-114.8
    File "/usr/local/Plone52/zeocluster/src/collective.behavior.relatedteaserimage/src/collective/behavior/relatedteaserimage/configure.zcml", line 47.4-47.36
    File "/usr/local/Plone52/zeocluster/src/collective.behavior.relatedteaserimage/src/collective/behavior/relatedteaserimage/behaviors/configure.zcml", line 12.4-19.10

These are the relevant lines in /usr/local/Plone52/zeocluster/src/collective.behavior.relatedteaserimage/src/collective/behavior/relatedteaserimage/behaviors/configure.zcml

    <plone:behavior
        name="collective.behavior.relatedteaserimage"
        title="Related Teaser Image Behavior"
        description="A related image field which displays like a leadimage"
        provides=".relatedteaserimage.IRelatedTeaserImageBehavior"
        factory=".relatedteaserimage.RelatedTeaserImage"
        marker=".relatedteaserimage.IRelatedTeaserImageMarker"
        />

The problem is apparently, that factory has a multi-element __component_adapts__. To analyse this case, I would put a (temporary) programmatic breakpoint (i.e. import pdb; pdb.set_trace()) before the raise ConfigurationError in plone.behavior.metaconfigure._detect_for and analyse factory in the debugger. Locking at its __component_adapts__ should allow to determine where the multi-element comes from and how to fix this situation.

This line was missing from the zcml behavior configuration:

        for="plone.dexterity.interfaces.IDexterityContent"

When no for element exists, plone.behavior.metaconfigure _detect_for attempts to detect it and deliberately fails if more than one adapter is listed.

I created the package and its behavior with mrbob as suggested here: https://docs.plone.org/develop/addons/bobtemplates.plone/bobtemplates.plone/docs/templates/addon/behavior/index.html

@mrtango Since the zcml file is auto-generated, I wonder if it would have been wise to also generate a generic for element there. I created an issue on github.

Would it not be better if every content type that had lead image behavior enabled showed in the album view (by default) ?

(instead of showing just images)

The album view of a collection already does this, hence:

I would almost call it a bug if a collection and a folder show differently for the same view. ( If I put album view on a folder with for example Content type A and B, I would expect to see the same result as of a Collection that shows Content type A and B

I don't know what a folder's album view would display in this case. Agree that they should be the same.

My main issue was to have a field "image" which returns an image (from my behavior) be displayed in the same way a leadimage from a behavior, or an Image type's "image" field are displayed..

Apparently there is an open issue which attempts to corral a herd of cats: regression: album view shows folders with lead image two times · Issue #537 · plone/plone.app.contenttypes · GitHub

Marked as solved: what I wanted to achieve: to provide an IImage marker interface to the affected instances (which can be done through ZMI) is not directly supported in code.

This package also does not directly support the adding of marker interfaces to instances. To do that, you can either use an event handler to mark an object when it is created, or a dynamic providedBy descriptor that does the lookup on the fly (but you probably want some caching). A sample event handler is provided with this package, but is not registered by default.

Edit 04-01-2020: I ended up creating subscribers for IObjectAddedEvent and IObjectModifiedEvent to trigger the addition/removal of the additional interface.

For schema only behaviors for is not needed and a warning would be issued if given.