Alberto Valverde wrote:
> I'm trying to make use of the new-in-1.1 "accept" view predicate to
> register several views with the same name for the same context object to
> render a response in the content-type requested by the user but the
> result is not quite what I expect. The zcml looks like this:
> Everything works fine when I request the application/json and
> application/json+xconfig mimetypes with a xhr since I can control the
> "Accept" header, however, when a browser makes a "normal" request the
> result is unpredictable (in practice) since '*/* is sent in the accept
> header so all of the view predicates evaluate positively.
> I made a quick POC patch so the best match is taken into account
> (attached). It modifies MultiView.__call__() so it narrows down the
> predicates that it will evaluate using the request.accept.best_match()
> method and it seems to work (all bfg tests still pass and the result is
> as expected when I manually test my bfg app). The patch is against the
> 1.1 branch in SVN.
> Is this the right approach? If so I would like to finish it up with
> tests to reach full coverage and contribute it :)
The more I think about it Alberto, I think the patch you sent over is pretty
much correct (definitely more correct than the current behavior; it must have
taken a lot of thought, thank you).
I think we might want to change view registration so it operates like this:
- If there's not already a view callable registered for this
context/request/viewname (this is the first view being registered for
this particular triad), register a view callable with
the accept value as a *predicate*, except also set __accept__ on the view
callable like your patch does (in case another view gets registered for
the same triad, resulting in a MultiView; we need to keep the value handy).
Derived views without an "accepts=" in the set of registration arguments
will be registered with "__accepts__ = None" attribute, while derived
views with an "accepts=" in the set of registration arguments will be
registered with an "__accepts__ = 'text/html'" (or whatever).
- If there is already a *non-MultiView* view callable registered for this
triad, unregister the old view, and register a MultiView with both the old
view and the new view.
- If there is already a *MultiView* view callable registered for this triad,
add the new view to the multiview.
A MultiView will keep all views it represents that have the same "accepts"
value in a "bucket" representing that accepts value. Each "bucket" will be a
sequence of (score, view_callable) ordered by score. The buckets will be kept
as the values of a dictionary attribute of the multiview. The dictionary will
be keyed by accept value.
The __call__ of a MultiView will do this:
- Find the "best" Accepts value for the currently registered set of accepts
values for this multiview (the keys of the dictionary) by asking
- Retrieve the subset of views in this multiview that match the best accept
value, and iterate over each of those, testing the predicates of each.
If a view callable matches, call it and return the response. If a no
view callable matches, however, retest "best_match" *without* the previously
tested "accepts" value to find the "next best" match, ad infinitum until
we either find a view that matches or we run out of subsets to test. If
"best_match" does its job right, the very last subset we test will be the
set of views that do not have *any* "accepts" value (the subset of views
keyed on the None accepts value).
Obviously we'll put some shortcut logic in here so it will "go fast" when all
views in the multiview are registered with "accepts=None".
Repoze-dev mailing list