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:
context.context, search the "first" slot in the registry for further
lookup information.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".
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