Plone with Schemas

“Using Dexterity to extend and customise Plone”

Last month, you tried Plone 4 and could create a schema interactively. However, at present, it does not seem to be possible to convert the interactive definitions into code. So, this month you will explore how to add code for two schemas which are related and create a simple custom view for displaying them.

Just as with Grok, the framework will create the skeleton package so that the routine drudgery is removed. This is done by zopeskel.dexterity – see http://plone.org/products/zopeskel.dexterity for details.

You may wish to install Plone as a non-root user. In which case, the installation is in $HOME/Plone. The advantage, of course, is that you will not need to be a superuser for the development activities.

Once Plone 4 is installed, your next step is to modify the buildout configuration files to add the needed components.

Zopeskel is defined is base.cfg. You will need to add the egg for zopeskel.dexterity and needed dependencies. Your zopeskel section on base.cfg should now look like:

[zopeskel]
# installs paster and Zopeskel
recipe = zc.recipe.egg
eggs =
PasteScript
ZopeSkel
Paste
PasteDeploy
zopeskel.dexterity
${buildout:eggs}

You should change the extends section of buildout.cfg to look like:

You will also need to include the egg for Dexterity in your Plone instance. The eggs section in your buildout.cfg will look like:

eggs =
Plone
Pillow
lxml
plone.app.dexterity

Now, you may run buildout to update the installation.

$ bin/buildout

You can verify that the site works as before.

Creating Schema

Move to src sub-directory and create the skeleton as follows. You will need to specify a package name, e.g. lfy.demo.

$ ../bin/zopeskel dexterity

$ cd lfy.demo

You will want to create two schema which will be related. As an example, let one be 'Department' and the other be 'Writer'. A writer will belong to a department.

$ ../../bin/paster addcontent dexterity_content

$ ../../bin/paster addcontent dexterity_content

You want the department to have a name and description of its responsibilities. In addition, you want to have the photo of the head of the department.

For the writer, you want his name, blog site, the photo, a brief profile and the department to which he belongs.

You can define the models in the python files in the sub-directory lfy/demo. The generated files are well documented. The IDepartment class in department.py will become:

from zope import schema

from plone.app.textfield import RichText

from plone.namedfile.field import NamedImage

class IDepartment(form.Schema):

"""

Departments

"""

title = schema.TextLine(

title=_(u"Name"),

)


description = RichText(

title=_(u"A short desciption"),

)

image = NamedImage(

title=_(u"Head of Department"),

description=_(u"Please upload an image"),

required=False,

)

You can find information about various data types in http://plone.org/products/dexterity/documentation/manual/developer-manual/reference.

The IWriter class in writer.py will become:

from zope import schema

from plone.app.textfield import RichText

from plone.namedfile.field import NamedImage

from z3c.relationfield.schema import RelationChoice

from plone.formwidget.contenttree import ObjPathSourceBinder

from lfy.demo.department import IDepartment

class IWriter(form.Schema):

"""

Writers

"""

title = schema.TextLine(

title=_(u"Name"),

)


department = RelationChoice(

title=_(u"Deparment"),

source=ObjPathSourceBinder(object_provides=IDepartment.__identifier__),

)

profile = RichText(

title=_(u"Profile"),

)

blog = schema.URI(

title=_(u"Blog Site"),

required=False,

)

picture = NamedImage(

title=_(u"Picture"),

description=_(u"Please upload an image"),

required=False,

)

You may want to change the default behaviour so that the system does not add metadata fields like title. This is achieved by deleting the line below from lfy.demo.department.xml and lfy.demo.writer.xml in profiles/default/types sub-directory.

<element value="plone.app.dexterity.behaviors.metadata.IBasic"/>

You are now ready to build the Plone instance with the custom fields. Edit buildout.cfg file to add lfy.demo egg:

eggs =

...

lfy.demo

develop =

src/lfy.demo

Stop plone, build the new instance and start it again.

$ bin/plonectl stop

$ bin/buildout

$ bin/plonectl start

As an admin user, enable Dexterity types and the demo application you have just created in the 'Add-ons' from 'Site Preferences'. 'Add new' option will now have Department and Writer as additional types.

Changing the View

The default view is reasonable but each field is stacked below the other. A URL is shown as text and not as a link. You can replace the default view of the writer by your own custom view.

In writer.py, in the class 'SampleView' un-comment the following line:

grok.name('view')

Now, SampleView becomes the default view. You will need to modify the corresponding template, sampleview.pt, in the sub-directory writer_templates. The template's logic is in the metal:main block, which you may replace as follows so that the profile wraps around the image:

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

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

<h1 class="documentFirstHeading" tal:content="context/title" />

<!-- illustrates creating a link from a relation object -->

<div>

<p><label>Department</label>

<a tal:attributes="href string:${context/department/to_path}"

tal:content="context/department/to_object/title">Link</a>

</p>

</div>

<!-- illustrates creating a link using a URI field -->

<div tal:condition="nocall:context/blog">

<p><label>blog</label>

<a tal:attributes="href string:${context/blog}"

tal:content="string:${context/blog}">Link</a>

</p>

</div>

<!-- Illustrates creating a URL from image object -->

<div tal:define="picture nocall:context/picture"

tal:condition="nocall:picture">

<img tal:attributes=

"src string:${context/absolute_url}/@@download/picture/${picture/filename};"

style="float:left;" />

</div>


<!-- illustrates displaying a rich text field -->

<div>

<label>Profile</label>

<p tal:content="structure context/profile/output" />

</div>

</tal:main-macro>

</metal:main>

The hardest part in the template is to display the image. Since the image is stored in the database, it has to be constructed into a URL and the img tag with src attribute created. Context refers to the current object, which will normally be followed by an attribute. The attribute may be followed by an operation of the attribute. The image and the uri are conditionally shown if the respective objects exist.

As profile is a rich text object, “output” method converts it into html text. The “structure” option tells the system to use it as it is without escaping any characters. You can learn more about TAL and METAL at http://docs.zope.org/zope2/zope2book/AppendixC.html.

It's not just the templates, the code can also be as complex as you need. Virtually anything you can do with grok, you should be able to do in Plone. So, you have a powerful tool to create a site whose primary but not exclusive focus is content management.

Comments