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.
Thank you so much! This is the best written article on this subject I have seen. Far better than the dexterity docs.