Exploring Software: Plone with Schemas

1
4737
Time to Plone

Time to Plone

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 we will explore how to add code for two related schemas, and create a simple custom view to display 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 plone.org 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 development activities. Once Plone 4 is installed, your next step is to modify the buildout configuration files, to add needed components.

Zopeskel is defined in base.cfg. You will need to add the egg for zopeskel.dexterity and the required dependencies. Your zopeskel section in base.cfg should now look like what is shown below:

[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 what follows:

extends =
     base.cfg
     versions.cfg
     http://good-py.appspot.com/release/dexterity/2.0-next

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

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 it did before.

Creating schemas

Move to the src subdirectory and create the skeleton as follows. You will be prompted to specify a package name, e.g., lfy.demo. A subdirectory by the same name will be created by zopeskel.

$ ../bin/zopeskel dexterity
$ cd lfy.demo

You may want to create two related schemas. For example, ‘Department’ and ‘Writer’; a writer belongs to a department. You will need to run the paster command for each content type and you will be prompted for the name of the content type (that is, the schema).

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

Directory structure and skeleton files, including Python code, will be created, which you can expand.
Now, you will want the department to have a name and description of its responsibilities. In addition, you may want to have the photo of the head of the department. Regarding the writer, you would require his name, blog site, photo, a brief profile and the department to which he belongs. You can define the models in the Python files in the subdirectory lfy.demo. The generated skeleton files are well documented. The IDepartment class in department.py will then become what’s given below:

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 Dexterity documentation on plone.org.
The IWriter class in writer.py will become what follows:

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 the lfy.demo.department.xml and lfy.demo.writer.xml files in the profiles/default/types subdirectory.

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

You are now ready to build the Plone instance with the custom fields. Edit the buildout.cfg file to add the 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. The 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 uncomment the following line:

    grok.name('view')

Now, SampleView becomes the default view. You will need to modify the corresponding template, sampleview.pt, in the subdirectory 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 URL are conditionally shown if the respective objects exist.

As profile is a rich text object, the “output” method converts it into HTML text. The “structure” option tells the system to use it as is, without escaping any characters. You can learn more about TAL and METAL here.

It’s not just the templates, the code can also be as complex as you require. 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.

1 COMMENT

LEAVE A REPLY

Please enter your comment!
Please enter your name here