On 12/15/05, Martijn Faassen <[EMAIL PROTECTED]> wrote:
> Jim Fulton wrote:
> > Martijn Faassen wrote:
> [snip]
> >  > Or am I wrong in even imagining this would be desirable?
> >
> > I think so.  If there are custom views for more specific interfaces,
> > it is likely those custom views provide features that your generic
> > view doesn't.  It would be a bit unkind for your view to override
> > those.
> I can see that...
> I have to consider what this means in practice -- with a skin you often
> want complete control over what users see. If suddenly bits of Zope 3
> pop up for your end user, that'll be very disconcerting; often I really
> don't want that to happen.
> Then again, in practice this might not happen, as your end user may not
> be capable of creating such objects anyway in an application; if they
> could you would've skinned them... Not sure...
> One troublesome scenario I can imagine is that if I make my total skin
> in Zope 3.n, it works, and then Zope 3.n + 1 is released and it has a
> more specific view registered for some content object that I'm using. I
> was relying on having skinned the more general interface, but suddenly
> in Zope 3.n + 1 I get the Zope 3 view. That'd be bad.
> So, I'm not sure whether or not this ability is compromised in practice
> with this bugfix...

If it is compromised, it may be bad design on your part? That's the
decision that I came to. I made a slightly modified
'EditMetaData.html' view for our CMS system that added a 'keywords'
field, made the form look nicer in our interface, and then mapped
'keywords' to IZopeDublinCore.subject (subjects? I can't recall right
now). I bound the view to one of our core interfaces,
'IManagedContent', used to distinguish CMS objects from other content
objects. Anyways, after installing Zope 3.2b1 I was getting the
default EditMetaData.html view instead of ours.

I wrote a little helper module for me to 'debug' this situation -
basically I took the internal
zope.interface.adapters.AdapterLookup.lookup code that did the
multi-adapter lookup and compiled a list of the multi-adapters it
chose from instead of having it choose the best. For this
'EditMetaData.html' situation, I got the following results for one of
my objects:

        [(((3, <InterfaceClass zope.app.annotation.interfaces.IAnnotatable>),
           (4, <InterfaceClass zope...IDefaultBrowserLayer>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>),
         (((5, <InterfaceClass ex.cms.interfaces.IManagedContent>),
           (2, <InterfaceClass ex.cms.skin.CMS>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>)]

The integers are the ranks used, mapping to (view for, layer). This
particular case shows me that the IAnnotatable, IDefaultBrowserLayer
combination is ranked (3,4) which wins out over my IManagedContent,
CMS view, ranked (5,2). The solution here was for me to re-declare
what my view was for and make it for IAnnotatable. I run my debug
function again and get the following results:

        [(((3, <InterfaceClass zope.app.annotation.interfaces.IAnnotatable>),
           (4, <InterfaceClass zope...IDefaultBrowserLayer>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>),
         (((3, <InterfaceClass zope.app.annotation.interfaces.IAnnotatable>),
           (2, <InterfaceClass ex.cms.skin.CMS>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>)]

Now my custom view is ranked (3,2) and wins out over the default one.

I'm still unsure about how I feel about this situation in my gut. The
Zope 3.1 way felt strangely intuitive, but I recognize why it was
incorrect and why the 3.2 implementation does what it does.

I decided to make a special branch of our CMS to deal with Zope 3.2
beta testing, and it's turned into a big refactoring / mild
restructuring branch. I'm re-evaluating a lot of the blind
'implements' declarations I was making. I'm re-evaluating some of my
core interfaces and decided what ones belong to concrete objects and
which ones are or can be abstract notions ('marker' interfaces). This
is a task that's been in the back of my head for a while. I have a
pair of core content objects and corresponding interfaces that we've
used interchangeably and I really needed to come down hard with a
policy. For us at Bottlerocket, these core objects have bitten us in
the past. So for me - and I'm not saying it applies for you or anyone
else - this change to the 'correct' behavior for multi-adapter lookup
has highlighted some design flaws in our code and way of thinking. If
this issue didn't show itself now, a different issue may have shown up
in the future.

I'm attaching the little 'debug' module I whipped up. Using it, along
with statements like pprint(providedBy(myfolder).__iro__) helped me
realize that I had some bad implements declarations in my base classes

So for me, this helps because it should make my code tighter. Where it
doesn't sit well with me is just that for me (and you too Martijn), it
seems like it's more intuitive for the skin layers to have precedence.
Whether that intuitive feel comes from the Zope 3.1 behavior, or
whether it may be the 'right' way people expect things to behave, I
don't know.

But now that I understand multi-adapters and how they're used to do
browser views, I have to admit that I'm very impressed with how the
very core component architecture is used for *everything*, especially
after the 'Simplify Component Architecture' proposal was implemented.
I'll gladly break with what Zope 3.1 let me get away with in favor of
a "works-as-advertised" multi-adapter lookup with a sensible and
predictable ranking algorithm.
Utility classes and functions for debugging and analyzing certain mysteries.
__docformat__ = 'restructuredtext'
def listAllMultiAdapters(registry, required, provided, name='', default=None):
    Returns a list of _all_ of the matching multi adapters for the required
    interfaces. The argument list is set up to be close to the lookup() method
    on the adapter registry. *Works only for Multi-Adapters*.

    This function works by mimicing the internals of AdapterLookup.lookup for
    multi adapters, but instead of returning the 'best' match it reveals the
    criteria used to select the best match.

    - ``registry``: The global adapters registry. Can be retrieved with
    - ``required``: A tuple / list of interfaces that the adapter is required
      to adapt. Can be gotten for any series of objects by using map() with
      zope.interface.providedBy, ala ``map(providedBy, (self.context,
    - ``provided``: The interface the adapter is expected to provide. For most
      views, this can be just ``zope.interface.Interface``.
    - ``default``: ignored
    Example usage::

        from pprint import pprint
        from zope.app import zapi
        from zope.interface import Interface, providedBy
        from zope.publisher.browser import TestRequest
        from zope.publisher.interfaces.browser import ISkin, IDefaultSkin
        from ex.debug import listAllMultiAdapters
        adapters = zapi.getGlobalSiteManager().adapters
        conman = zapi.getUtility(ISkin, name='ex.cms.skin.ContentManagement')
        test_site = debugger.root()['test_site']
        subfolder = test_site['subfolder']
        required = map(providedBy, (subfolder, TestRequest(skin=conman)))
        all = listAllMultiAdapters(adapters,required,Interface,'contents.html')

    The returned output will be a series of tuples containing tuples, in the
    form of::

        [(((10, <InterfaceClass ex.cms.interfaces.IContentContainer>),
           (2, <InterfaceClass ex.cms.skin.CMS>)),
          <class 'zope.app.publisher.browser.viewmeta.contents.html'>)]

    Inside the tuples, the integer shows the rank of that interface match, and
    the interface is what interface was matched. This shows a view registered
    for IContentContainer in the ``ec.cms.skin.CMS`` layer. If you take the
    integers from each match returned together and compare them, you'll see
    what the match might be.


        [(((3, <InterfaceClass zope.app.annotation.interfaces.IAnnotatable>),
           (4, <InterfaceClass zope...IDefaultBrowserLayer>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>),
         (((5, <InterfaceClass ex.cms.interfaces.IManagedContent>),
           (2, <InterfaceClass ex.cms.skin.CMS>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>)]

    The ranks here would be ``(3,4)`` compared against ``(5,2)``. This means
    that for this particular query, the object supports both IAnnotatable and
    the CMS IManagedContent interfaces. Both interfaces have a view,
    'EditMetaData.html'. For this object, 'IAnnotatable' ranks higher than
    'IManagedContent', and the DefaultBrowserLayer ranks lower. In this case,
    the view registered for IAnnotatable in the Default Browser Layer would be

    Now if the CMS 'EditMetaData.html' view were to be registered for
    IAnnotatable, the results would be different.


        [(((3, <InterfaceClass zope.app.annotation.interfaces.IAnnotatable>),
           (4, <InterfaceClass zope...IDefaultBrowserLayer>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>),
         (((3, <InterfaceClass zope.app.annotation.interfaces.IAnnotatable>),
           (2, <InterfaceClass ex.cms.skin.CMS>)),
          <class 'zope.app.pagetemplate.simpleviewclass...>)]

    Now in the rankings we have the default one with a rank (still) of
    ``(3,4)``, against the CMS one now ranked with ``(3,2)``. The matched
    interface holds the same rank, so now the rank of the layer interface in
    the request (the second item) is used, and the CMS layer ranks better. Now
    the CMS custom EditMetaData.html is used.
    order = len(required)

    with = required
    key = provided, order

    out = []
    for surrogate in registry.get(required[0]), registry._default:
        byname = surrogate.get(key)
        if not byname:

        bywith = byname.get(name)
        if not bywith:

        # Selecting multi-adapters is not just a matter of matching the
        # required interfaces of the adapter to the ones passed. Several
        # adapters might match, but we only want the best one. We use a
        # ranking algorithm to determine the best match.

        # `best` carries the rank and value of the best found adapter.
        # (not used in this implementation, since we show all of the found
        # adapters)
        best = None
        for rwith, value in bywith:
            # the `rank` describes how well the found adapter matches.
            rank = []
            for rspec, spec in zip(rwith, with):
                if not spec.isOrExtends(rspec):
                    break # This one is no good
                # Determine the rank of this particular specification.
                rank.append( (list(spec.__sro__).index(rspec), rspec) )
                rank = tuple(rank)
                out.append((rank, value))
    return out

Zope3-dev mailing list
Unsub: http://mail.zope.org/mailman/options/zope3-dev/archive%40mail-archive.com

Reply via email to