<browser:page>: configured template is not used

I have a quite old product which contains several Archetypes-based types. I'd like to have special views for those types for use with my ajaxnavigation product; the idea is,

  • an ajax-nav view to return the whole information about the object in JSON format, and
  • an embed view, using a template, which is used internally to create the content information.

I have a subpackage ajax_nav which is activated if my ajaxnavigation package is installed.
In its configure.zcml, I have entries like this one:

<browser:page
    name="embed"
    for="Products.theoldone.content.interfaces.IMyVideo"
    class=".video.EmbedView"
    template="video-embed.pt"
    permission="zope2.View"
    />

This looks perfectly reasonable to me, but it won't work. I expected the effect to be:

  • For every context object which implements the interface given in the for attribute,
  • when called via .../@@embed,
  • use the EmbedView class of the .video module to provide the view data
  • which is used for the video-embed.pt template.

That ZCML directive should create the connection to the template, right?
However, the template is not used; instead, I get the raw data from the EmbedView.__call__ result.

My Python modules look like this:

# _base.py
from Products.Five.browser import BrowserView
from plone.uuid.interfaces import IUUID

class BaseEmbedView(BrowserView):
    def __call__(self):
        context = self.context
        res = {
            'UUID': IUUID(context, None),
            }
         # (e.g., read the data from the schema ...)
         return res

# video.py
from ._base import BaseEmbedView

class EmbedView(BaseEmbedView):
    # ./video-embed.pt

    def __call__(self):
        res = BaseEmbedView.__call__(self)
        # (several additions for videos)
        return res

I tried as well to change the entries to a for="*" attribute and put the interface in an allowed_interfaces attribute instead, but this didn't work -- not very surprisingly, since there are many "embed" pages for the same for value.

How is this supposed to be done, please? Thank you!

The browser:page directive will use its template attribute to define an attribute on the view class (I think the name of this attribute is index). The base __call__ will use this attribute to "render" the view. If you override __call__ in your class, the your __call__ will render the view (and apparently ignores the template attribute).

Background: A browser:page registers a view. Technically, a view is a (multi) adapter for an object and a request. During the adaptation, the adapter is (always) called on the objects (for a class instance, this means that its __call__ method is called).

Ah, thank you. I somehow had the imagination of __call__ being the method to provide the data.
That is obviously incorrect.
For the @@embed views, which make use of a template each, I changed the names of the __call__ methods to data and use view/data now in the templates instead of view.

Thus, the general rule seems to be:

  • If not using a template, override the __call__ method of the used BrowserView subclass
    (I have created a @returns_json decorator for my @@ajax-nav views; perhaps there is such a beast elsewhere already, but it was easy enough);
  • Otherwise, take care not to override the __call__ method but create a method with another arbitrary name, e.g. data, and call this in the template to get the data (e.g., tal:define="data view/data").

Correct?
Is there a nice documentation link we could add here?

You can also override the call to set variables and return the super of call like this:

class CustomView(BrowserView):

    def __call__(self):
        # set variables
        # super().__call__() python-3
        return super(CustomView, self).__call__() 

Yes. But why should I?
Reasons not to do so:

  • The syntax is different for Python 2 and Python 3.
  • I'd like to avoid the use of super as long as I don't really need it (see “Python's Super is nifty, but you can't use it”, previously known as “Python's Super Considered Harmful”)
  • If using arbitray method names, I can simply have a family of views which share the same CustomView class but use different data-providing methods.

The typical interaction between view and template it that the view provides preprocessed data via appropriate attributes and/or methods - ready to use for the template with the aim to separate logic from presentation: the class is responsible for the logic, the template for presentation. Thus, usually, you would not have a single method which provides all data but several individual attributes/methods for individual values (to keep the template as simple as possible).

Well, this depends, right?

E.g., I have an optval attribute in my objects; if it is non-empty, I'd like to use a pre-processed optval_pretty value in the template.
As for the logic, I suppose a simple

tal:condition="optval_pretty"
tal:content="optval_pretty"

to be much better than to use a call to another method, or (even worse) use another template.

For easy cases, that "single data method" looks to me like a very good place for logic like, "set optval_pretty to None or to some formatted string".