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!