Contents
Plone/Zope uses view pattern to output dynamically generated HTML pages.
Views are usually combination of
By keeping as much of the logic code in a separate Python class and making page template as dump as possible better component readability and reuse is achieved. You can override either Python logic or the template file separately.
When you are working with Plone, the most usual view type is BrowserView from Products.Five package, but there are others.
Each BrowserView class is a Python callable. BrowserView.__call__() method acts as an entry point of executing the view code. From Zope point of view, even a function would be enough as it is callable.
Views were introduced in Zope 3 and made available in Plone by Products.Five package (which provides some Plone/Zope 2 specific adaption hooks). However, Zope 3’s way of XML based configuration languae ZCML and separating things to three different files (Python module, ZCML configuration, TAL template) was later seen as cumbersome to maintain.
Later a project called Grok was started to introduce easy API to Zope, including how to set up and maintain views. For more information how to use Grok (five.grok package) with Plone, please read Plone and Grok tutorial.
Note
When writing this (Q1/2010), all project templates in Paster still use old-style Zope views.
Views are Zope component architecture multi-adapter registrations. If you are doing manual view look-ups then this information concerns you.
Views are looked up by name. Zope publisher does forced view look-up, instead of traversing, if the traversing name is prefixed with @@.
Views are resolved against different interfaces
See also related source code.
This shows how to create and register view in Zope 3 manner.
Example:
# We must use BrowserView from view, not from zope.browser
# Zope version does not
from Products.Five.browser import BrowserView
class MyView(BrowserView):
def __init__(self, context, request):
self.context = context
self.request = request
# by default call will call self.index() method which is mapped
# to ViewPageTemplateFile specified in ZCML
#def __call__():
#
Warning
Do not attempt to run any code in __init__() method of a view. If this code fails and exception is caused, zope.component machinery remaps this as “View not found” exception or traversing error. Instead, use a pattern where you have setup() or similar method which __call__() or view users explicitly call.
The following example registers a new view
<browser:page
for="*"
name="test"
permission="zope2.Public"
class=".views.MyView"
/>
ZCML <browser:view template=”“> will set index class attribute.
The default view __call__() method will return the value returned by self.index() call.
Example:
<browser:page
for="*"
name="test"
permission="zope2.Public"
class=".views.MyView"
/>
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
class MyView(BrowserView):
index = ViewPageTemplateFile("my-template.pt")
is equal to:
<browser:page
for="*"
name="test"
permission="zope2.Public"
class=".views.MyView"
template="my-template.pt"
/>
class MyView(BrowserView):
pass
Rendering of the view is done by following:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
class MyView(BrowserView):
# This may be overridden in ZCML
index = ViewPageTemplateFile("my-template.pt")
def render(self):
return self.index()
def __call__(self):
return self.render()
Views can be registered against a specific layer interface. This means that views are only looked up if the specific layer is effective. Since one Zope application server can contain multiple Plone sites, layers are used to determine which Python code is effective for which Plone site.
Layer can be be effective when
You should generally always register your views against a certain layer in your own code.
For more information, see
::doc::browser layers </serving/layers>.
If you need to produce other output than (X)HTML here are some resources
You need to get access to view in your code if you are
Below are two different approach.
This is the most efficient way in Python.
Example
from Acquisition import aq_inner
from zope.component import getMultiAdapter
def getView(context, request, name):
# Remove acquisition wrapper which made cause false context assumptions
context = aq_inner(context)
# Will raise ComponentLookUpError
view = getMultiAdapter((context, request), name=name)
# Put view to acquisition chain
view = view.__of__(context)
return view
Traversing is slower than direct getMultiAdapter() call. However, traversing is available in templates and RestrictedPython modules easily.
Example
def getView(context, name):
""" Return a view which is associated with context object and current HTTP request.
@param context: Any Plone content object
@param name: Attribute name holding the view name
"""
try:
view = context.unrestrictedTraverse("@@" + name)
except AttributeError:
raise RuntimeError("Instance %s did not have view %s" % (str(context), name))
view = view.__of__(context)
return view
You can also do direct view looks up and method calls in template by using @@ notation in traversing.
<div tal:attributes="lang context/@@plone_portal_state/current_language">
We look up lang attribute by using BrowserView which name is "plone_portal_state"
</div>
Difference in code:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
vs.
from zope.app.pagetemplate import ViewPageTemplateFile
Difference is that Five version supports
Most of the code in this section is copied from a tutorial by Martin Aspeli (on slideshare.net). The main change is that, at least for Plone 4, the interface should subclass plone.theme.interfaces.IDefaultPloneLayer instead of zope.interface.Interface.
In this example we override the “@@register” form from the plone.app.users package, creating a custom form which subclasses the original.
from plone.theme.interfaces import IDefaultPloneLayer
class IExamplePolicy(IDefaultPloneLayer):
""" A marker interface for the theme layer
"""
<layers>
<layer
name="example.policy.layer"
interface="example.policy.interfaces.IExamplePolicy"
/>
</layers>
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="example.policy">
<browser:page
name="register"
class=".customregistration.CustomRegistrationForm"
permission="zope2.View"
layer="..interfaces.IExamplePolicy"
/>
</configure>
from plone.app.users.browser.register import RegistrationForm
class CustomRegistrationForm(RegistrationForm):
""" Subclass the standard registration form
"""
This is useful for debugging purposes:
from plone.app.customerize import registration
from zope.publisher.interfaces.browser import IBrowserRequest
# views is generator of zope.component.registry.AdapterRegistration objects
views = registration.getViews(IBrowserRequest)
How to filter out views which provide certain interface.
from plone.app.customerize import registration from zope.publisher.interfaces.browser import IBrowserRequest
# views is generator of zope.component.registry.AdapterRegistration objects views = registration.getViews(IBrowserRequest)
# Filter out all classes which do not filter a certain interface views = [ view.factory for view in views if IBlocksView.implementedBy(view.factory) ]
Not all views need to return HTML output, or output at all. Views can be used as a helpers around in the code to provide APIs to objects. Since views can be overridden using layers, view is a natural plug-in point which an add-on product can customize or override in a conflict-free manner.
View methods are exposed to page templates and such, so you can also call view methods directly from a page template, besides Python code.
Often the point of helper views is that you can have reusable functionality which can be plugged-in as one-line code around the system. Helper views also go around the limitations
Note
Using RestrictedPython scripts (creating Python through Zope Management Interface) and Zope 2 Extension modules is discouraged. The same functionality can be achieved with helper views, with less potential pitfalls.