Description
Rendering HTML pages in Plone using Zope 3 view pattern.
Plone/Zope uses a view pattern to output dynamically generated HTML pages.
Views are the basic elements of modern Python web frameworks. A view runs code to set-up Python variables for a template rendering. Output may not limited to HTML pages and snippets, but may contain JSON, file download payloads and other.
Views are usually combination of
By keeping as much of the logic code in a separate Python class and making page template as dumb 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.
ノート
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):
"""
This will initialize context and request object as they are given as view multiadaption parameters.
Note that BrowserView constructor does this for you and this step here is just to show
how view receives its context and request parameter. You do not need to write
__init__() for your views.
"""
self.context = context
self.request = request
# by default call will call self.index() method which is mapped
# to ViewPageTemplateFile specified in ZCML
#def __call__():
#
警告
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.
Zope 3 views are registered in ZCML, XML-based configuration language. Usually the configuration file, where the registration done is yourapp.package/yourapp/package/browser/configure.zcml.
The following example registers a new view
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
>
<browser:page
for="*"
name="test"
permission="zope2.Public"
class=".views.MyView"
/>
</configure>
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
If you need to produce other output than (X)HTML here are some resources
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
"""
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 using helper views is that you can have reusable functionality which can be plugged-in as one-line code around the system. Helper views also get around the following limitations:
ノート
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.
To use the same template code several times you can either
ノート
Plone 2.x way, TAL template language macros, are discouraged to provide reusable functionality in your add-on product. This is because macros are hardwired to TAL template language and referring them outside templates is difficult. Also, if you ever need to mix or change the template language, you can do it easily when templates are a feature of a pure Python based view and not vice versa.
Here is an example how to have a view snippet which can be used subclasses of a base view class. Subclasses can refer to this template in any point of the view rendering, making it possible for subclasses to have fine tuned control over how the template snippet is represented.
Related Python code:
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
class ProductCardView(BrowserView):
"""
End user visible product card presentation.
"""
implements(IProductCardView)
# Nested template which renders address box + buy button
summary_template = ViewPageTemplateFile("summarybox.pt")
def renderSummary(self):
""" Render summary box
@return: Resulting HTML code as Python string
"""
return self.summary_template()
Then you can render summary template in the main template associated with ProductCardView, by calling renderSummary() method and TAL non-escaping HTML embedding.
<h1 tal:content="context/Title" />
<div tal:replace="structure view/renderSummary" />
<div class="description">
<div tal:condition="python:context.Description().decode('utf-8') != u'None'" tal:replace="structure context/Description" />
</div>
And the summarybox.pt itself is just HTML code without Plone decoration frame (main_template/master etc. macros). Make sure that you declare i18n:domain again or the strings in this template are not translated.
<div class="summary-box" i18n:domain="your.package">
...
</div>
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 may 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>
Use aq_acquire(object, template_name)
Example: Get an object by its path and render it using its default template in the current context.
from Acquisition import aq_base, aq_acquire
from Products.Five.browser import BrowserView
class TelescopeView(BrowserView):
"""
Renders an object in a different location of the site when passed the
path to it in the querystring.
"""
def __call__(self):
path = self.request["path"]
target_obj = self.context.restrictedTraverse(path)
# Strip the target_obj of context with aq_base.
# Put the target in the context of self.context.
# getDefaultLayout returns the name of the default
# view method from the factory type information
return aq_acquire(aq_base(target_obj).__of__(self.context),
target_obj.getDefaultLayout())()
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) ]