Skip to content.

plope

Personal tools
You are here: Home » Members » chrism's Home » Zope Views: should have been "request, context"; not "context, request"
 
 

Zope Views: should have been "request, context"; not "context, request"

Zope views should have been defined as accepting (request, context) rather than (context, request).

Yawn. Boring. I'll post it anyway, though.

The Zope "multiadapter" machinery lookup algorithm isn't defined or documented in any sort of human-consumable way anywhere. So you'll have to trust me on the following. It's almost incomprehensible anyway, so if you have a low tolerance for details, don't bother trying to understand this.

Zope views are multiadapters on ``(context, request)``. When you say please adapt (context, request) to IView, it does more or less this:

  • Give me every interface provided by context.
  • From most specific to least specific interface provided by context, search the "first" slot in the registry for further lookup information.
  • If something is found in the first slot that is registered for any of the interfaces of context, continue on to look for an adapter based on the request object.

In terms of a registry represented by a dictionary and a pseudocode implementation of getMultiAdapter, it looks a bit like this:

  def getMultiAdapter(objects, target=Interface):
      num = len(objects)
      info = registry[num][target]
      for object in objects:
          items = providedBy(object)
          for item in items:
             rest = info.get(item)
             if rest is not None:
                break
          if rest is None:
              raise ComponentLookupError
          info = rest
      return info(*objects)

This isn't quite right, because some backtracking happens when a secondary lookup fails, and we're disregarding the adapter name, but for our purposes it's close enough.

Let's also take for granted we have some interfaces:

  from zope.interface import Interface

  class IRequest(Interface): pass
  class ISpecializedRequest(IRequest): pass
  class IContext(Interface): pass

In such a setup, when you then do this:

  registry = {2:{IView:{IContext:{IRequest:adapter}}}}

  directlyProvides(context, IContext)
  directlyProvides(request, IRequest)

  getMultiAdapter((context, request), IView)

You will get back the result of "adapter" in our faux registry. This is well and good. However, there is a corner case lurking here that is not well and good.

For view lookups, I actually think the historical Zope ordering of (context, request) is not correct. It should actually likely be (request, context). If you take this to its logical "worldview" conclusion, I think this means that Zope view class constructors should have been made to accept (request, context) rather than (context, request).

Why? Well, as a general rule, due to the lookup algorithm above, you almost always want to create multiadapters with arguments ordered in such a way that "provides" registration arguments more likely to be for specific interfaces come before "provides" registration arguments that are less likely to be for specific interfaces.

View registrations are always made for some very specific request (e.g. IRequest) interface, but often they are made for a very weakly binding context interface, sometimes just "Interface". "Interface" is implemented by everything.

For example, the (context, request) ordering breaks down a bit when you have a registry populated as follows:

  registry = {2:
                {IView:
                  {IContext:{IRequest:adapter}},
                  {Interface:{ISpecializedRequest:anotheradapter}},
                }
             }

And you do this:

  directlyProvides(context, IContext)
  directlyProvides(request, ISpecializedRequest)

  getMultiAdapter((context, request), IView)

You will still get back the result of "adapter" even though it's likely that you really wanted to get back the result of "anotheradapter".

Why would you expect to get back the result of "anotheradapter"? It's an extremely uncommon thing to do to put a specialized interface on the request and to make registrations for this specialized request type. You almost always want to find adapters registered for the request interface first, even if the registration context interface is "more binding".

Created by chrism
Last modified 2010-01-24 12:55 PM

Lookup

I think to a certain extent this reflects the "CMS-y" roots of Zope. For example, in Plone, we normally have a view called '@@view' that is available on every content item. By using the same naming convention for all types of content, we gain some predictability if nothing else.

The @@view view is always registered for the particular type of context, so the view of an IDocument is different to the view of an INewsItem. In this case had we registered them both for (IBrowserRequest, <type-specific interface>) it's probably easier to accidentally override the view for all content items. That's not to say it's better (in this example, you just have to be sure that you're registering any override for both the context and the request marker), but it probably does explain why it's like this.

Martin

hmm

I still think it's an objective mistake.

In the common case, without any layers involved, if the arguments were reversed, it would operate effectively the same way it does now. It would only operate differently when a request was decorated with a non-default layer.

As far as I know (and maybe this is the contention, and I am incorrect), a user most often registers a view inside a non-default layer for a very specific circumstance that includes a context as an override. It is true that they could inadvertently shoot themselves in the foot by registering that view without naming a specific context. But a sufficiently knowledgeable user wants to, he probably *should* be able to override all views for all types when the request matches a non-default layer. For example, you could make a layer view always represent an XML-RPC response rather than a browser response. Right now this cannot be done, at least without overriding each view in the default layer explicitly and individually.

Am I wrong?