Making the view page of a content type use your schemata declarations

by Plone Documentation Team last modified Mar 19, 2010 12:00 PM
Contributors: Mikko Ohtamma, Martin Aspeli, Kamon Ayeva, Israel Saeta Pérez
How to make the schemata declarations in a Archetypes schema be used in the view page of a content type.

Introduction

Declaring schematas in your Archetype schema has the nice effect of displaying the fields of the different schemas on different edit pages (very much like a "wizard" for adding a new content type instance). Often times you might like to also have the view page be divided according to the different schemas you have declared. This is not done automatically by Archetypes so in this document I'll show you how to do it yourself. Don't worry! It's really quite easy.

Python class and schema

I'll be using a simple article content type I have constructed for this how-to to show you how the schematas can be used on your content type's view page. The example type is really not very usable, but just complex enough to show you how to do this. It has a schema of four fields in addition to the default id and title fields: abstract, body, firstname, lastname. The abstract and body fields are in a schemata named article and the firstname and lastname field in a schemata named author.

I have also defined the title and id fields to be in schemata article. This was done so I won't have an extra schemata called default and so I can use the title field for the title of the article. (Remember to use BaseSchema.copy()!)

The class itself has just the schema declaration and a new view action definition. I have defined the view action to use a template called article_view that we'll be getting to shortly.

Here is the file in it's entirety:

 

from Products.Archetypes.public import *

from Products.CMFCore import CMFCorePermissions



from config import PKG_NAME



schema = BaseSchema.copy() + Schema((

        TextField('abstract',

                required=1,

                searchable=1,

                widget = TextAreaWidget(description="Abstract", label="Abstract"),

                schemata = 'article'),

        TextField('body',

                required=1,

                searchable=1,

                widget = TextAreaWidget(description="Body", label="Body"),

                schemata = 'article'),

        TextField('firstname',

                required=1,

                searchable=1,

                widget = StringWidget(description="First name", label="First name"),

                schemata = 'author'),

        TextField('lastname',

                required=1,

                searchable=1,

                widget = StringWidget(description="Last name", label="Last name"),

                schemata = 'author'),

        ))



schema['title'].schemata = 'article'

schema['id'].schemata = 'article'



class Article(BaseContent):

        schema = schema



        actions = (

                        {'id': 'view',

                        'name': 'View',

                        'action': 'string:${object_url}/article_view',

                        'permissions': (CMFCorePermissions.View,)

                        },

        )



registerType(Article, PKG_NAME)

View template

The view template article_view is the main part of this how-to. It has the page template code to generate the different pages for the different schematas.

First you should copy the base.pt file from the Archetypes skins folder (on my Debian GNU/Linux unstable it's in /usr/share/zope/Products/Archetypes:1.3/skins/archetypes) to your product's skins folder. It has most of the template code you'll need ready, so you'll only need to make some minor changes to make this work. Also it uses all the default macros and such, so you'll view page will look like a real plone page.

The base.pt template just goes through all the fields of your content type and shows their widgets. What we want to do is to have it only go through the fields of one schemata at a time and give us links to see the others. This will be done using REQUEST parameters to the scripts.

I'll go though the changes from the top of the file downwards so you'll have a easier time keeping up and making the changes to your own template.

Links to the different schematas

We'll want the list of different schematas to be at the top of the page, so that'll go in first. Find the line that says '<metal:main_macro define-macro="main">'. This is where the body of the template starts. After this line is the header with the title and the little icons for edit, print and such, and I want to have my links to show up above that. So after the beginning of the body and above the header add the following code:

 

        <div style="margin-bottom: 1em">

                <span tal:repeat="schemata python: here.Schemata().keys()">

                        <b tal:condition="python: schemata != 'metadata'">[<a tal:attributes="href string:?page=${schemata}"><span tal:replace="schemata" /></a>]</b>

                </span>

        </div>

This just repeats over our schematas' names (we get them with here.Schemata().keys()) and prints all of them on one line as links, each one in square brackets. The links are to the same view page, but they all set a parameter in REQUEST called page that points to the schemata we are linking to. This isn't very pretty so you'll probably want to make them look nicer if you like. The 'schemata != 'metadata'' part is because there's a schemata called metadata created automatically for your content type to support default standard metadata which can be set via the properties tab and that we do not want to include here.

Showing only the schemata we want

In the next part we'll be diving deeper into the code. You're looking for a part that says 'tal:repeat="field python:here.Schema().filterFields(isMetadata=0)"'. This repeats through the fields of your content type and the following parts show their widgets. What we want to do here is to have it repeat through the fields of the schemata we want instead of all of them. In the previous part we set a parameter in REQUEST called page that points to the schemata we want to see, and here we want to use that to pick which schemata's fields to loop over. So just go ahead and replace the part with 'tal:repeat="field python:here.Schemata()[here.REQUEST.get('page', here.Schemata().keys()[0])].filterFields(isMetadata=0)"'. This just gets the page parameter from REQUEST (if page is not found, ie. the template is called with no parameters, then first schemata, in this case article, is used) and loops through the fields of the schemata with that name.

The completed article_view.pt looks like this:

 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"

      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"

      metal:use-macro="here/main_template/macros/master">



  <head><title></title></head>



  <body>



    <div metal:fill-slot="main">



      <metal:main_macro define-macro="main">



        <div style="margin-bottom: 1em">

                <span tal:repeat="schemata python: here.Schemata().keys()">

                        <b tal:condition="python: schemata != 'metadata'">[<a tal:attributes="href string:?page=${schemata}"><span tal:replace="schemata" /></a>]</b>

                </span>

        </div>



        <metal:header_macro define-macro="header">

          <div metal:use-macro="here/document_actions/macros/document_actions">

            Document actions (print, rss, etc)

          </div>

          <h1 tal:content="title_string | here/title_or_id" />

          <tal:has_document_byline tal:condition="exists:here/document_byline">

            <div metal:use-macro="here/document_byline/macros/byline">

              Get the byline - contains details about author and modification date.

            </div>

          </tal:has_document_byline>

        </metal:header_macro>



        <metal:body_macro metal:define-macro="body"

                          tal:define="field_macro field_macro | here/widgets/field/macros/view;"

                          tal:repeat="field  python:here.Schemata()[here.REQUEST.get('page', here.Schemata().keys()[0])].filterFields(isMetadata=0)">

          <tal:if_visible define="mode string:view;

                                  visState python:field.widget.isVisible(here, mode);

                                  visCondition python:field.widget.testCondition(here, portal, template);"

                          condition="python:visState == 'visible' and visCondition">

            <metal:use_field use-macro="field_macro" />

          </tal:if_visible>

        </metal:body_macro>



        <metal:folderlisting_macro metal:define-macro="folderlisting"

                                   tal:define="fl_macro here/folder_listing/macros/listing | nothing;

                                               folderish here/isPrincipiaFolderish | nothing;">

            <tal:if_folderlisting condition="python:folderish and fl_macro">

                <metal:use_macro use-macro="fl_macro" />

            </tal:if_folderlisting>

        </metal:folderlisting_macro>



        <metal:footer_macro define-macro="footer">

        </metal:footer_macro>



      </metal:main_macro>



    </div>



  </body>



</html>

Conclusion

So that was it. I told you it was going to be easy!

Happy hacking!