Simple views

by Martin Aspeli last modified Apr 26, 2010 10:09 PM
Creating basic views

So far, our types have used the default views, which use the display widgets from z3c.form, much like the add and edit forms use the edit widgets. This is functional, but not very attractive. Most types will need one or more custom view templates.

Dexterity types are no different to any other content type in Plone. You can register a view for your schema interface, and it will be available on your type. If the view is named view, it will be the default view, at least if you use the standard FTI configuration. This is because the FTI's default_view property is set to view, and view is in the list of view_methods.

When working with Dexterity, we will typically configure our views using the five.grok configuration system, eschewing ZCML configuration. Below, we will show how to add simple views for the Program and Speaker types. Next, we will show how to use display forms to take advantage of the standard widgets if required.

The five.grok view approach uses a class in the content type's module, which is automatically associated with a template in an accompanying directory. These directories should be created next to the module files, so we will have program_templates, presenter_templates and session_templates.

In program.py, the view is registered as follows:

class View(grok.View):
    grok.context(IProgram)
    grok.require('zope2.View')
    
    def sessions(self):
        """Return a catalog search result of sessions to show
        """
        
        context = aq_inner(self.context)
        catalog = getToolByName(context, 'portal_catalog')
        
        return catalog(object_provides=ISession.__identifier__,
                       path='/'.join(context.getPhysicalPath()),
                       sort_on='sortable_title')

This creates a view registration similar to what you may do with a <browser:page /> ZCML directive. We have also added a helper method which will be used in the view. Note that this requires some imports at the top of the file:

from Acquisition import aq_inner
from Products.CMFCore.utils import getToolByName

from example.conference.session import ISession

The view registration works as follows:

  • The view name will be @@view, taken from the class name in lowercase. You can specify an alternative name with grok.name('some-name') if required.
  • The grok.context() directive specifies that this view is used for objects providing IProgram.
  • You can add a grok.layer() directive if you want to specify a browser layer.
  • The grok.require() directive specifies the required permission for this view. It uses the Zope 3 permission name. zope2.View and zope.Public are the most commonly used permissions (in fact, zope.Public is not actually a permission, it just means "no permission required"). For a list of other standard permissions, see parts/omelette/Products/Five/permissions.zcml. We will cover creating custom permissions later in this manual.
  • Any methods added to the view will be available to the template via the view variable. The content object is available via context, as usual.

This is associated with a file in program_templates/view.pt. The file name matches the class name (even if a different view name was specified). This contains:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      lang="en"
      metal:use-macro="context/main_template/macros/master"
      i18n:domain="example.conference">
<body>

<metal:main fill-slot="main">
    <tal:main-macro metal:define-macro="main"
        tal:define="toLocalizedTime nocall:context/@@plone/toLocalizedTime">

        <div tal:replace="structure provider:plone.abovecontenttitle" />

        <h1 class="documentFirstHeading" tal:content="context/title" />
        
        <div class="discreet">
            <tal:block condition="context/start">
                <span i18n:translate="label_from">From:</span>
                <span tal:content="python:context.start.strftime('%x %X')" />
            </tal:block>
            <tal:block condition="context/end">
                <span i18n:translate="label_to">To:</span>
                <span tal:content="python:context.end.strftime('%x %X')" />
            </tal:block>
        </div>

        <div tal:replace="structure provider:plone.belowcontenttitle" />

        <p class="documentDescription" tal:content="context/description" />

        <div tal:replace="structure provider:plone.abovecontentbody" />

        <div tal:content="structure context/details/output" />
        
        <h2 i18n:translate="heading_sessions">Sessions</h2>
        <dl>
            <tal:block repeat="session view/sessions">
                <dt>
                    <a tal:attributes="href session/getURL"
                       tal:content="session/Title" />
                </dt>
                <dd tal:content="session/Description" />
            </tal:block>
        </dl>

        <div tal:replace="structure provider:plone.belowcontentbody" />

    </tal:main-macro>
</metal:main>

</body>
</html>

For the most part, this template outputs the values of the various fields, using the sessions() method on the view to obtain the sessions contained within the program.

Notice how the details RichText field is output as tal:content="structure context/details/output". The structure keyword ensure the rendered HTML is not escaped. The extra traversal to details/output is necessary because the RichText field actually stores a RichTextValue object that contains not only the raw text as entered by the user, but also a MIME type (e.g. text/html) and the rendered output text. RichText fields are covered in more detail later in this manual.

The view for Presenter, in presenter.py, is even simpler:

class View(grok.View):
    grok.context(IPresenter)
    grok.require('zope2.View')

Its template, in presenter_templates/view.pt, is similar to the previous template:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      lang="en"
      metal:use-macro="context/main_template/macros/master"
      i18n:domain="example.conference">
<body>

<metal:main fill-slot="main">
    <tal:main-macro metal:define-macro="main">

        <div tal:replace="structure provider:plone.abovecontenttitle" />

        <h1 class="documentFirstHeading" tal:content="context/title" />
        
        <div tal:replace="structure provider:plone.belowcontenttitle" />

        <p class="documentDescription" tal:content="context/description" />

        <div tal:replace="structure provider:plone.abovecontentbody" />

        <div tal:content="structure context/bio/output" />

        <div tal:replace="structure provider:plone.belowcontentbody" />

    </tal:main-macro>
</metal:main>

</body>
</html>

Obviously, these views are very basic. Much more interesting views could be created by putting a little more work into the templates.

You should also realise that you can create any type of view using this technique. Your view does not have to be related to a particular content type, even. You could set the context to Interface, for example, to make a view that's available on all types.