diff --git a/README.rst b/README.rst index dd49a9b..bf16372 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,8 @@ +.. warning:: THIS REPOSITORY IS OBSOLETE. + + Development of this manual is continuing in the docs folder of + the ``plone.app.dexterity`` package. + Introduction ============ diff --git a/buildout.cfg b/buildout.cfg index 5cddd4c..dc547c4 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,9 +1,6 @@ -# -# Buildout to create toplone command which uploads Sphinx documentation to Plone site -# [buildout] parts = - sphinx + sphinx sources = sources sources-dir = ${buildout:directory}/src @@ -23,6 +20,7 @@ eggs = collective.sphinx.includedoc collective.sphinx.autoatschema + [versions] roadrunner = 0.2.3.1 zc.recipe.egg=1.2.0 diff --git a/source/advanced/behaviours.txt b/source/advanced/behaviours.txt index f3ad373..ea5226c 100644 --- a/source/advanced/behaviours.txt +++ b/source/advanced/behaviours.txt @@ -3,14 +3,14 @@ Using behaviors **Finding and adding behaviors** -Dexterity introduces the concept of *behaviors –* re-usable bundles of +Dexterity introduces the concept of *behaviors* – re-usable bundles of functionality and/or form fields which can be turned on or off on a per-type basis. Each behavior has a unique interface. When a behavior is enabled on a type, you will be able to adapt that type to the behavior’s interface. If the behavior is disabled, the adaptation will fail. The behavior -interface can also be marked as an *IFormFieldsProvider*, in which case +interface can also be marked as an ``IFormFieldsProvider``, in which case it will add fields to the standard add and edit forms. Finally, a behavior may imply a sub-type: a marker interface which will be dynamically provided by instances of the type for which the behavior is @@ -28,15 +28,16 @@ and imported using GenericSetup: Other behaviors are added in the same way, by listing additional -behavior interfaces as elements of the *behaviors* property. +behavior interfaces as elements of the ``behaviors`` property. -Behaviors are normally registered with the ** ZCML +Behaviors are normally registered with the ```` ZCML directive. When registered, a behavior will create a global utility -providing *IBehavior*, which is used to provide some metadata, such as a +providing ``IBehavior``, which is used to provide some metadata, such as a title and description for the behavior. -You can find and apply behaviors via the *Dexterity Content Types* -control panel that is installed with *plone.app.dexterity*. For a list +You can find and apply behaviors via the :guilabel:`Dexterity Content Types` +control panel that is installed with `plone.app.dexterity`_. For a list of standard behaviors that ship with Dexterity, see the reference at the end of this manual. +.. _plone.app.dexterity: http://pypi.python.org/pypi/plone.app.dexterity diff --git a/source/advanced/catalog-indexing-strategies.txt b/source/advanced/catalog-indexing-strategies.txt index 30640f9..74ef6b5 100644 --- a/source/advanced/catalog-indexing-strategies.txt +++ b/source/advanced/catalog-indexing-strategies.txt @@ -4,68 +4,87 @@ Catalog indexing strategies **How to create custom catalog indexes** The ZODB is a hierarchical object store where objects of different -schemata and sizes can live side by side. This is great for managing -individual content items, but not optimal for searching across the -content repository. A naive search would need to walk the entire object -graph, loading each object into memory and comparing object metadata -with search criteria. On a large site, this would quickly become -prohibitive. - -Luckily, Zope comes with a technology called the *ZCatalog*, which is -basically a table structure optimised for searching. In Plone, there’s a -ZCatalog instance called *portal\_catalog*. Standard event handlers will -index content in the catalog when it is created or modified, and unindex -when the content is removed. - -The catalog manages *indexes*, which can be searched, and *metadata* -(also known as *columns*), which are object attributes for which the -value is copied into the catalog. When we perform a search, the result -is a lazily loaded list of objects known as *catalog brains*. Catalog -brains contain the value of metadata columns (but not indexes) as -attributes. The functions *getURL()*, *getPath()* and *getObject()* can -be used to get the URL and path of the indexed content item, and to load -the full item into memory. +schemata and sizes can live side by side. +This is great for managing individual content items, +but not optimal for searching across the content repository. +A naive search would need to walk the entire object graph, +loading each object into memory and comparing object metadata +with search criteria. +On a large site, this would quickly become prohibitive. + +Luckily, Zope comes with a technology called the *ZCatalog*, +which is basically a table structure optimised for searching. +In Plone, there’s a ZCatalog instance called ``portal_catalog``. +Standard event handlers will index content in the catalog +when it is created or modified, +and unindex when the content is removed. + +The catalog manages *indexes*, which can be searched, +and *metadata* (also known as *columns*), +which are object attributes for which the value is copied into the catalog. +When we perform a search, +the result is a lazily loaded list of objects known as *catalog brains*. +Catalog brains contain the value of metadata columns (but not indexes) as +attributes. +The functions ``getURL()``, ``getPath()`` and ``getObject()`` can +be used to get the URL and path of the indexed content item, +and to load the full item into memory. .. note:: - Dexterity objects are more lightweight than Archetypes objects. This - means that loading objects into memory is not quite as undesirable as is - sometimes assumed. If you’re working with references, parent objects, or - a small number of child objects, it is usually OK to load objects - directly to work with them. However, if you are working with a large or - unknown-but-potentially-large number of objects, you should consider - using catalog searches to find them and use catalog metadata to store - frequently used values. There is an important trade-off to be made - between limiting object access and bloating the catalog with unneeded - indexes and metadata, though. In particular, large strings (such as the - body text of a document) or binary data (such as the contents of image - or file fields) should not be stored as catalog metadata. + Dexterity objects are more lightweight than Archetypes objects. + This means that loading objects into memory is not quite as undesirable + as is sometimes assumed. + If you’re working with references, parent objects, + or a small number of child objects, + it is usually OK to load objects directly to work with them. + However, if you are working with a large or + unknown-but-potentially-large number of objects, + you should consider using catalog searches to find them and use catalog + metadata to store frequently used values. + There is an important trade-off to be made between limiting object + access and bloating the catalog with unneeded indexes and metadata, + though. + In particular, large strings (such as the body text of a document) or + binary data (such as the contents of image or file fields) + should not be stored as catalog metadata. Plone comes with a number of standard indexes and metadata columns. These correspond to much of the *Dublin Core* set of metadata as well as -several Plone-specific attributes. You can view the indexes, columns and -the contents of the catalog through the ZMI pages of -the *portal\_catalog* tool. If you’ve never done this, it is probably -instructive to have a look, both to understand how the indexes and -columns may apply to your own content types, and to learn what searches -are already possible. +several Plone-specific attributes. +You can view the indexes, columns and the contents of the catalog through +the ZMI pages of the ``portal_catalog`` tool. +If you’ve never done this, it is probably instructive to have a look, +both to understand how the indexes and columns may apply to your own content +types, and to learn what searches are already possible. Indexes come in various types. The most common ones are: -- *FieldIndex*, the most common type, used to index a single value. -- *KeywordIndex*, used to index lists of values where you want to be - able to search for a subset of the values. As the name implies, - commonly used for keyword fields, such as the *Subject* Dublin Core - metadata field. -- *DateIndex*, used to index Zope 2 *DateTime* objects. Note that if - your type uses a Python *datetime* object, you’ll need to convert it - to a Zope 2 *DateTime* using a custom indexer! -- *DateRangeIndex*, used mainly for the effective date range. -- *ZCTextIndex*, used mainly for the *SearchableText* index. This is - the index used for full-text search. -- *ExtendedPathIndex*, a variant of *PathIndex*, which is used for the - *path* index. This is used to search for content by path and - optionally depth. +``FieldIndex`` + the most common type, used to index a single value. + +``KeywordIndex`` + used to index lists of values where you want to be able to search for a + subset of the values. + As the name implies, commonly used for keyword fields, + such as the ``Subject`` Dublin Core metadata field. + +``DateIndex`` + used to index Zope 2 ``DateTime`` objects. + Note that if your type uses a *Python* ``datetime`` object, + you’ll need to convert it to a Zope 2 ``DateTime`` using a custom + indexer! + +``DateRangeIndex`` + used mainly for the effective date range. + +``ZCTextIndex`` + used mainly for the ``SearchableText`` index. + This is the index used for full-text search. + +``ExtendedPathIndex`` + a variant of ``PathIndex``, which is used for the ``path`` index. + This is used to search for content by path and optionally depth. Adding new indexes and metadata columns --------------------------------------- @@ -78,21 +97,22 @@ If a value is found, it is indexed. .. note:: Objects are normally acquisition-wrapped when they are indexed, which - means that an indexed value may be acquired from a parent. This can be - confusing, especially if you are building container types and creating - new indexes for them. If child objects don’t have attributes/methods - with the same name, the parent object’s value will be indexed for all - children as well. - -Catalog indexes and metadata can be installed with the *catalog.xml* + means that an indexed value may be acquired from a parent. + This can be confusing, especially if you are building container types + and creating new indexes for them. + If child objects don’t have attributes/methods with names corresponding + to indexes, + the parent object’s value will be indexed for all children as well. + +Catalog indexes and metadata can be installed with the ``catalog.xml`` GenericSetup import step. It is useful to look at the one in Plone -(*parts/omelette/Products/CMFPlone/profiles/default/catalog.xml*). +(``parts/omelette/Products/CMFPlone/profiles/default/catalog.xml``). -As an example, let’s index the *track* property of a *Session* in the +As an example, let’s index the ``track`` property of a ``Session`` in the catalog, and add a metadata column for this property as well. In -*profiles/default/catalog.xml*, we have: +``profiles/default/catalog.xml``, we have: -:: +.. code-block:: xml @@ -102,43 +122,45 @@ catalog, and add a metadata column for this property as well. In -Notice how we specify both the index name and the indexed attribute. It -is possible to use an index name (the key you use when searching) that -is different to the indexed attribute, although they are usually the -same. The metadata column is just the name of an attribute. +Notice how we specify both the index name and the indexed attribute. +It is possible to use an index name (the key you use when searching) that +is different to the indexed attribute, +although they are usually the same. +The metadata column is just the name of an attribute. Creating custom indexers ------------------------ -Indexing based on attributes can sometimes be limiting. First of all, -the catalog is indiscriminate in that it attempts to index every -attribute that’s listed against an index or metadata column for every -object. Secondly, it is not always feasible to add a method or attribute -to a class just to calculate an indexed value. +Indexing based on attributes can sometimes be limiting. +First of all, the catalog is indiscriminate in that it attempts to index +every attribute that’s listed against an index or metadata column for every +object. +Secondly, it is not always feasible to add a method or attribute to a class +just to calculate an indexed value. Plone 3.3 and later ships with a package called `plone.indexer`_ to help -make it easier to write custom indexers: components that are invoked to -calculate the value the catalog sees when it tries to index a given -attribute. Indexers can be used to index a different value to the one -stored on the object, or to allow indexing of a “virtual” attribute that -does not actually exist on the object is question. Indexers are usually -registered on a per-type basis, so you can have different -implementations for different types of content. - -To illustrate indexers, we will add three indexers to *program.py*: Two -will provide values for the *start* and *end* indexes, normally used by -Plone’s *Event* type. We actually have attributes with the correct name -for these already, but they use Python *datetime* objects whereas the -*DateIndex* requires a Zope 2 *DateTime.DateTime* object. (Python didn’t -have a *datetime* module when this part of Zope was created!) The third -indexer will be used to provide a value for the *Subject* index that -takes its value from the *tracks* list. - -:: +make it easier to write custom indexers: +components that are invoked to calculate the value which the catalog sees +when it tries to index a given attribute. +Indexers can be used to index a different value to the one stored on the +object, or to allow indexing of a “virtual” attribute that does not actually +exist on the object is question. +Indexers are usually registered on a per-type basis, +so you can have different implementations for different types of content. + +To illustrate indexers, we will add three indexers to ``program.py``. +Two will provide values for the ``start`` and ``end`` indexes, +normally used by Plone’s ``Event`` type. +We actually have attributes with the correct name for these already, but +they use Python ``datetime`` objects whereas the ``DateIndex`` requires a +Zope 2 ``DateTime.DateTime`` object. +(Python didn’t have a ``datetime`` module when this part of Zope was +created!) +The third indexer will be used to provide a value for the ``Subject`` index +that takes its value from the ``tracks`` list. :: from DateTime import DateTime - from plone.indexer import indexer - + from plone.indexer import indexer ... @indexer(IProgram) @@ -160,29 +182,30 @@ takes its value from the *tracks* list. return obj.tracks grok.global_adapter(tracksIndexer, name="Subject") -Here, we use the *@indexer* decorator to create an indexer. This doesn’t -register the indexer component, though, so we need to use -*grok.global\_adapter()* to finalise the registration. Crucially, this is -where the indexer’s *name* is defined. This is the name of the indexed -attribute for which the indexer is providing a value. +Here, we use the ``@indexer`` decorator to create an indexer. +This doesn’t register the indexer component, though, so we need to use +``grok.global_adapter()`` to finalise the registration. +Crucially, this is where the indexer’s ``name`` is defined. +This is the name of the indexed attribute for which the indexer is providing +a value. .. note:: - Since all of these indexes are part of a standard Plone installation, we - won’t register them in *catalog.xml*. If you are creating custom - indexers and need to add new catalog indexes or columns for them, - remember that the “indexed attribute” name (and the column name) must - match the name of the indexer as set in its adapter registration. + Since all of these indexes are part of a standard Plone installation, + we won’t register them in ``catalog.xml``. + If you are creating custom indexers and need to add new catalog indexes + or columns for them, remember that the “indexed attribute” name (and the + column name) must match the name of the indexer as set in its adapter + registration. Searching using your indexes ---------------------------- Once we have registered our indexers and re-installed our product (to -ensure that the *catalog.xml* import step is allowed to install new +ensure that the ``catalog.xml`` import step is allowed to install new indexes in the catalog), we can use our new indexes just like we would -any of the default indexes. The pattern is always the same: - -:: +any of the default indexes. +The pattern is always the same:: from Products.CMFCore.utils import getToolByName # get the tool @@ -195,66 +218,102 @@ any of the default indexes. The pattern is always the same: url = brain.getURL() obj = brain.getObject() # Performance hit! -This shows a simple search using the *portal\_catalog* tool, which we -look up from some context object. We call the tool to perform a search, -passing search criteria as keyword arguments, where the left hand side -refers to an installed index and the right hand side is the search term. +This shows a simple search using the ``portal_catalog`` tool, +which we look up from some context object. +We call the tool to perform a search, +passing search criteria as keyword arguments, +where the left hand side refers to an installed index and the right hand +side is the search term. Some of the more commonly used indexes are: -- *Title*, the object’s title. -- *Description*, the object’s description. -- *path*, the object’s path. The argument is a string like ‘/foo/bar’. - To get the path of an object (e.g. a parent folder), do - *‘/’.join(folder.getPhysicalPath())*. Searching for an object’s path - will return the object and any children. To depth-limit the search, - e.g. to get only those 1 level deep, use a compound query, e.g. - *path={‘query’: ‘/’.join(folder.getPhysicalPath()), ‘depth’: 1}*. If - a depth is specified, the object at the given path is not returned - (but any children within the depth limit are). -- *object\_provides*, used to match interfaces provided by the object. - The argument is an interface name or list of interface names (of - which any one may match). To get the name of a given interface, you - can call *ISomeInterface.\_\_identifier\_\_*. -- *portal\_type*, used to match the portal type. Note that users can - rename portal types, so it is often better not to hardcode these. - Often, using an *object\_provides* search for a type-specific - interface will be better. Conversely, if you are asking the user to - select a particular type to search for, then they should be choosing - from the currently installed *portal\_types*. -- *SearchableText*, used for full-text searches. This supports operands - like *AND* and *OR* in the search string. -- *Creator*, the username of the creator of a content item -- *Subject*, a *KeywordIndex* of object keywords -- *review\_state*, an object’s workflow state - -In addition, the search results can be sorted based on any *FieldIndex*, -*KeywordIndex* or *DateIndex* using the following keyword arguments: - -- Use *sort\_on=‘’* to sort on a particular index. For - example, *sort\_on=‘sortable\_title’* will produce a sensible - title-based sort. *sort\_on=‘Date’* will sort on the publication - date, or the creation date if this is not set. -- Add *sort\_order=‘reverse’* to sort in reverse. The default is - *sort\_order=‘ascending’*. *‘descending’* can be used as an alias for - *‘reverse’*. -- Add *sort\_limit=10* to limit to approximately 10 search results. - Note that it is possible to get more results due to index - optimisations. Use a list slice on the catalog search results to be - absolutely sure that you have got the maximum number of results, e.g. - results = *catalog(…, sort\_limit=10)[:10]*. Also note that the use - of *sort\_limit* requires a *sort\_on* as well. +``Title`` + the object’s title. + +``Description`` + the object’s description. + +``path`` + the object’s path. The argument is a string like ``/foo/bar``. + To get the path of an object (e.g. a parent folder), do + ``'/'.join(folder.getPhysicalPath())``. + Searching for an object’s path will return the object and any children. + To depth-limit the search, e.g. to get only those 1 level deep, + use a compound query, e.g. + ``path={'query': '/'.join(folder.getPhysicalPath()), 'depth': 1}``. + If a depth is specified, the object at the given path is not returned + (but any children within the depth limit are). + +``object_provides`` + used to match interfaces provided by the object. + The argument is an interface name or list of interface names (of + which any one may match). + To get the name of a given interface, you can call + ``ISomeInterface.__identifier__``. + +``portal_type`` + used to match the portal type. + Note that users can rename portal types, + so it is often better not to hardcode these. + Often, using an ``object_provides`` search for a type-specific + interface will be better. + Conversely, if you are asking the user to select a particular type to + search for, then they should be choosing from the currently installed + ``portal_types``. + +``SearchableText`` + used for full-text searches. + This supports operands like ``AND`` and ``OR`` in the search string. + +``Creator`` + the username of the creator of a content item. + +``Subject`` + a ``KeywordIndex`` of object keywords. + +``review_state`` + an object’s workflow state. + +In addition, the search results can be sorted based on any ``FieldIndex``, +``KeywordIndex`` or ``DateIndex`` using the following keyword arguments: + +- Use ``sort_on=''`` to sort on a particular index. + For example, ``sort_on='sortable_title'`` will produce a sensible + title-based sort. + ``sort_on='Date'`` will sort on the publication date, or the creation date + if this is not set. +- Add ``sort_order='reverse'`` to sort in reverse. + The default is ``sort_order='ascending'``. + ``'descending'`` can be used as an alias for ``'reverse'``. +- Add ``sort_limit=10`` to limit to approximately 10 search results. + Note that it is possible to get more results due to index optimisations. + Use a list slice on the catalog search results to be + absolutely sure that you have got the maximum number of results, e.g. + ``results = catalog(…, sort_limit=10)[:10]``. + Also note that the use of ``sort_limit`` requires a ``sort_on`` as well. Some of the more commonly used metadata columns are: -- *Creator*, the user who created the content object -- *Date*, the publication date or creation date, whichever is later -- *Title*, the object’s title -- *Description*, the object’s description -- *getId*, the object’s id (note that this is an attribute, not a - function) -- *review\_state*, the object’s workflow state -- *portal\_type*, the object’s portal type +*Creator* + the user who created the content object. + +*Date* + the publication date or creation date, whichever is later. + +*Title* + the object’s title. + +*Description* + the object’s description. + +*getId* + the object’s id (note that this is an attribute, not a function). + +*review_state* + the object’s workflow state. + +*portal_type* + the object’s portal type. For more information about catalog indexes and searching, see the `ZCatalog chapter in the Zope 2 book`_. diff --git a/source/advanced/custom-add-and-edit-forms.txt b/source/advanced/custom-add-and-edit-forms.txt index c4540be..174ce14 100644 --- a/source/advanced/custom-add-and-edit-forms.txt +++ b/source/advanced/custom-add-and-edit-forms.txt @@ -5,146 +5,160 @@ Custom add and edit forms Until now, we have used Dexterity’s default content add and edit forms, supplying form hints in our schemata to influence how the forms are -built. For most types, that is all that’s ever needed. In some cases, -however, we want to build custom forms, or supply additional forms. +built. +For most types, that is all that’s ever needed. +In some cases, however, we want to build custom forms, or supply additional +forms. Dexterity uses the `z3c.form`_ library to build its forms, via the `plone.z3cform`_ integration package. .. note:: - the *plone.z3cform* package requires that standard *z3c.form* - forms are used via a form wrapper view. In Dexterity, this wrapper is - normally applied automatically by the form grokkers in - *plone.directives.form* and *plone.directives.dexterity*. + the `plone.z3cform`_ package requires that standard `z3c.form`_ + forms are used via a form wrapper view. + In Dexterity, this wrapper is normally applied automatically by the form + grokkers in `plone.directives.form`_ and `plone.directives.dexterity`_. Dexterity also relies on `plone.autoform`_, in particular its -*AutoExtensibleForm* base class, which is responsible for processing -form hints and setting up *z3c.form* widgets and groups (fieldsets). A -custom form, therefore, is simply a view that uses these libraries, +``AutoExtensibleForm`` base class, which is responsible for processing +form hints and setting up `z3c.form`_ widgets and groups (fieldsets). +A custom form, therefore, is simply a view that uses these libraries, although Dexterity provides some helpful base classes that make it easier to construct forms based on the schema and behaviors of a Dexterity type. .. note:: If you want to build standalone forms not related to content objects, - see the *z3c.form* documentation. For convenience, you may want to use - the base classes and schema support in `plone.directives.form`_. + see the `z3c.form`_ documentation. + For convenience, you may want to use the base classes and schema support + in `plone.directives.form`_. Edit forms ---------- An edit form is just a form that is registered for a particular type of -content and knows how to register its fields. If the form is named -*edit*, it will replace the default edit form, which is registered with -that name for the more general *IDexterityContent* interface. +content and knows how to register its fields. +If the form is named ``edit``, it will replace the default edit form, +which is registered with that name for the more general +``IDexterityContent`` interface. Dexterity provides a standard edit form base class that provides -sensible defaults for buttons, labels and so on. This should be -registered for a type schema (not a class). To create an edit form that -is identical to the default, we could do: - -:: +sensible defaults for buttons, labels and so on. +This should be registered for a type schema (not a class). +To create an edit form that is identical to the default, we could do:: class EditForm(dexterity.EditForm): grok.context(IFSPage) -The *dexterity* module is *plone.directives.dexterity* and the *grok* -module is *five.grok*. +The ``dexterity`` module is `plone.directives.dexterity`_ and +the ``grok`` module is `five.grok`_. The default name for the form is *edit*, but we could supply a different -name using *grok.name()*. The default permission is -*cmf.ModifyPortalContent*, but we could require a different permission -with *grok.require()*. We could also register the form for a particular -browser layer, using *grok.layer()*. +name using ``grok.name()``. +The default permission is ``cmf.ModifyPortalContent``, +but we could require a different permission with ``grok.require()``. +We could also register the form for a particular browser layer, +using ``grok.layer()``. This form is of course not terribly interesting, since it is identical to the default. However, we can now start changing fields and values. For example, we could: -- Override the *schema* property to tell *plone.autoform* to use a - different schema interface (with different form hints) than the - content type schema -- Override the *additional\_schemata* property to tell *plone.autoform* - to use different supplemental schema interfaces. The default is to - use all behavior interfaces that provide the *IFormFieldProvider* - marker from *plone.directives.form*. -- Override the *label* and *description* properties to provide - different a different title and description for the form. -- Set the *z3c.form* *fields* and *groups* attributes directly. -- Override the *updateWidgets()* method to modify widget properties, or - one of the other *update\*()* methods, to perform additional - processing on the fields. In most cases, these require us to call the - *super* version at the beginning. See the *plone.autoform* and - *z3c.form* documentation to learn more about the sequence of calls - that eminate from the form *update()* method in the - *z3c.form.form.BaseForm* class. +- Override the ``schema`` property to tell `plone.autoform`_ to use a + different schema interface (with different form hints) than the + content type schema. +- Override the ``additional_schemata`` property to tell `plone.autoform`_ + to use different supplemental schema interfaces. + The default is to use all behavior interfaces that provide the + ``IFormFieldProvider`` marker from `plone.directives.form`_. +- Override the ``label`` and ``description`` properties to provide + different a different title and description for the form. +- Set the `z3c.form`_ ``fields`` and ``groups`` attributes directly. +- Override the ``updateWidgets()`` method to modify widget properties, + or one of the other ``update``()`` methods, + to perform additional processing on the fields. + In most cases, these require us to call the ``super`` version at the + beginning. + See the `plone.autoform`_ and `z3c.form`_ documentation + to learn more about the sequence of calls that emanate from the form + ``update()`` method in the ``z3c.form.form.BaseForm`` class. Content add sequence -------------------- Add forms are similar to edit forms in that they are built from a type’s -schema and the schemata of its behaviors. However, for an add form to be -able to construct a content object, it needs to know the *portal\_type* -to use. +schema and the schemata of its behaviors. +However, for an add form to be able to construct a content object, +it needs to know which ``portal_type`` to use. -You should realise that the FTIs in the *portal\_types* tool can be -modified through the web. It is even possible to create new types -through the web that re-use existing classes and factories. +You should realise that the FTIs in the ``portal_types`` tool can be +modified through the web. +It is even possible to create new types through the web that re-use existing +classes and factories. For this reason, add forms are looked up via a namespace traversal -adapter alled *++add++.* You may have noticed this in the URLs to add -forms already. What actually happens is this: - -- Plone renders the *add* menu. - - To do so, it looks, among other places, for actions in the - *folder/add* category. This category is provided by the - *portal\_types* tool. - - The *folder/add* action category is constructed by looking up the - *add\_view\_expr* property on the FTIs of all addable types. This is a - TALES expression telling the add menu which URL to use. - - The default *add\_view\_expr* in Dexterity (and CMF 2.2) is - *string:${folder\_url}/++add++${fti/getId}*. That is, it uses the - *++add++* traversal namespace with an argument containing the FTI - name. -- A user clicks on an entry in the menu as is taken to a URL like */path/to/folder/++add++my.type*. - - The *++add++* namespace adapter looks up the FTI with the given name, - and gets its *factory* property. - - The *factory* property of an FTI gives the name of a particular - *zope.component.interfaces.IFactory* utility, which is used later to - construct an instance of the content object. Dexterity automatically - registers a factory instance for each type, with a name that matches - the type name, although it is possible to use an existing factory - name in a new type. This allows administrators to create new - “logical” types that are functionally identical to an existing type. - - The *++add++* namespace adapter looks up the actual form to render as - a multi-adapter from *(context, request, fti*) to *Interface* with a - name matching the *factory* property. Recall that a standard view is - a multi-adapter from *(context, request*) to *Interface* with a name - matching the URL segment for which the view is looked up. As such, - add forms are not standard views, because they get the additional - *fti* parameter when constructed. - - If this fails, there is no custom add form for this factory (as is - normally the case). The fallback is an unnamed adapter from - *(context, request, fti)*. The default Dexterity add form is - registered as such an adapter, specific to the *IDexterityFTI* - interface. -- The form is rendered like any other *z3c.form* form instance, and is - subject to validation, which may cause it to be loaded several times. -- Eventually, the is successfully submitted. At this point: - - The standard *AddForm* base class will look up the factory from the - FTI reference it holds and call it to create an instance. - - The default Dexterity factory looks at the *klass* attribute of the - FTI (*class* is a reserved word in Python…) to determine the actual - content class to use, creates an object and initialises it. - - The *portal\_type* attribute of the newly created instance is set to - the name of the FTI. Thus, if the FTI is a “logical type” created - through the web, but using an existing factory, the new instance’s - *portal\_type* will be set to the “logical type”. - - The object is initialised with the values submitted in the form - - An *IObjectCreatedEvent* is fired - - The object is added to its container - - The user is redirected to the view specified in the *immediate\_view* - property of the FTI +adapter alled ``++add++``. +You may have noticed this in the URLs to add forms already. +What actually happens is this: + +- Plone renders the :guilabel:`add` menu. + - To do so, it looks, among other places, for actions in the + *folder/add* category. This category is provided by the + ``portal_types`` tool. + - The *folder/add* action category is constructed by looking up the + ``add\_view\_expr`` property on the FTIs of all addable types. + This is a TALES expression telling the add menu which URL to use. + - The default ``add\_view\_expr`` in Dexterity (and CMF 2.2) is + ``string:${folder\_url}/++add++${fti/getId}``. + That is, it uses the ``++add++`` traversal namespace with an argument + containing the FTI name. +- A user clicks on an entry in the menu and is taken to a URL like + ``/path/to/folder/++add++my.type``. + - The ``++add++`` namespace adapter looks up the FTI with the given name, + and gets its ``factory`` property. + - The ``factory`` property of an FTI gives the name of a particular + ``zope.component.interfaces.IFactory`` utility, + which is used later to construct an instance of the content object. + Dexterity automatically registers a factory instance for each type, + with a name that matches the type name, + although it is possible to use an existing factory name in a new type. + This allows administrators to create new “logical” types that are + functionally identical to an existing type. + - The ``++add++`` namespace adapter looks up the actual form to render as + a multi-adapter from ``(context, request, fti``) to ``Interface`` with + a name matching the ``factory`` property. + Recall that a standard view is a multi-adapter from + ``(context, request)`` to ``Interface`` with a name matching the URL + segment for which the view is looked up. + As such, add forms are not standard views, because they get the + additional ``fti`` parameter when constructed. + - If this fails, there is no custom add form for this factory (as is + normally the case). + The fallback is an unnamed adapter from ``(context, request, fti)``. + The default Dexterity add form is registered as such an adapter, + specific to the ``IDexterityFTI`` interface. +- The form is rendered like any other ``z3c.form`` form instance, + and is subject to validation, + which may cause it to be loaded several times. +- Eventually, the form is successfully submitted. + At this point: + - The standard ``AddForm`` base class will look up the factory from the + FTI reference it holds and call it to create an instance. + - The default Dexterity factory looks at the ``klass`` [*]_ attribute of + the FTI to determine the actual content class to use, + creates an object and initialises it. + - The ``portal_type`` attribute of the newly created instance is set to + the name of the FTI. + Thus, if the FTI is a “logical type” created through the web, but + using an existing factory, the new instance’s ``portal_type`` will be + set to the “logical type”. + - The object is initialised with the values submitted in the form. + - An ``IObjectCreatedEvent`` is fired. + - The object is added to its container. + - The user is redirected to the view specified in the ``immediate_view`` + property of the FTI. + +.. [*] ``class`` is a reserved word in Python, so we use ``klass``. This sequence is pretty long, but thankfully we rarely have to worry about it. In most cases, we can use the default add form, and when we @@ -158,39 +172,41 @@ Custom add forms As with edit forms, Dexterity provides a sensible base class for add forms that knows how to deal with the Dexterity FTI and factory. -A custom form replicating the default would look like this: - -:: +A custom form replicating the default would look like this:: class AddForm(dexterity.AddForm): grok.name('example.fspage') -The name here should match the *factory* name. By default, Dexterity -types have a factory called the same as the FTI name. If no such factory -exists (i.e. you have not registered a custom *IFactory* utility), a -local factory utility will be created and managed by Dexterity when the +The name here should match the *factory* name. +By default, Dexterity types have a factory called the same as the FTI name. +If no such factory exists +(i.e. you have not registered a custom ``IFactory`` utility), +a local factory utility will be created and managed by Dexterity when the FTI is installed. -Also note that we do not specify a context here. Add forms are always -registered for any *IFolderish* context. We can specify a layer with -*grok.layer()* and a permission other than the default -*cmf.AddPortalContent* with *grok.require()*. +Also note that we do not specify a context here. +Add forms are always registered for any ``IFolderish`` context. +We can specify a layer with ``grok.layer()`` and a permission other than the +default ``cmf.AddPortalContent`` with ``grok.require()``. .. note:: If the permission used for the add form is different to the - *add\_permission* set in the FTI, the user needs to have *both* - permissions to be able to see the form and add content. For this reason, - most add forms will use the generic *cmf.AddPortalContent* permission. - The *add* menu will not render links to types where the user does not - have the add permission stated in the FTI, even if this is different to - *cmf.AddPortalContent*. + ``add_permission`` set in the FTI, the user needs to have *both* + permissions to be able to see the form and add content. + For this reason, most add forms will use the generic + ``cmf.AddPortalContent`` permission. + The :guilabel:`add` menu will not render links to types where the user + does not have the add permission stated in the FTI, + even if this is different to ``cmf.AddPortalContent``. -As with edit forms, we can customise this form by overriding *z3c.form* -and *plone.autoform* properties and methods. See the *z3c.form* -documentation on add forms for more details. +As with edit forms, we can customise this form by overriding `z3c.form`_ +and `plone.autoform`_ properties and methods. +See the `z3c.form`_ documentation on add forms for more details. .. _z3c.form: http://docs.zope.org/z3c.form +.. _five.grok: http://docs.zope.org/five.grok .. _plone.z3cform: http://pypi.python.org/pypi/plone.z3cform .. _plone.autoform: http://pypi.python.org/pypi/plone.autoform .. _plone.directives.form: http://pypi.python.org/pypi/plone.directives.form +.. _plone.directives.dexterity: http://pypi.python.org/pypi/plone.directives.dexterity diff --git a/source/advanced/custom-content-classes.txt b/source/advanced/custom-content-classes.txt index 3dfc1a5..b57f70d 100644 --- a/source/advanced/custom-content-classes.txt +++ b/source/advanced/custom-content-classes.txt @@ -3,26 +3,25 @@ Custom content classes **Adding a custom implementation** -When we learned about configuring the Dexterity FTI, we saw the *klass* -attribute and how it could be used to refer to either the *Container* or -*Item* content classes. These classes are defined in the -*plone.dexterity.content* module, and represent container (folder) and -item (non-folder) types, respectively. - -For most applications, these two classes will suffice. We will normally -use behaviors, adapters, event handlers and schema interfaces to build -additional functionality for our types. In some cases, however, it is -useful or necessary to override the class, typically to override some -method or property provided by the base class that cannot be implemented -with an adapter override. A custom class may also be able to provide -marginally better performance by side-stepping some of the -schema-dependent dynamic behavior found in the base classes. In real -life, you are very unlikely to notice, though. - -Creating a custom class is simple: simply derive form one of the -standard ones, e.g.: - -:: +When we learned about configuring the Dexterity FTI, +we saw the ``klass`` attribute and how it could be used to refer to either +the ``Container`` or ``Item`` content classes. +These classes are defined in the `plone.dexterity.content`_ module, +and represent container (folder) and item (non-folder) types, respectively. + +For most applications, these two classes will suffice. +We will normally use behaviors, adapters, event handlers and schema +interfaces to build additional functionality for our types. +In some cases, however, it is useful or necessary to override the class, +typically to override some method or property provided by the base class +that cannot be implemented with an adapter override. +A custom class may also be able to provide marginally better performance by +side-stepping some of the schema-dependent dynamic behavior found in the +base classes. +In real life, you are very unlikely to notice, though. + +Creating a custom class is simple: simply derive from one of the +standard ones, e.g.:: from plone.dexterity.content import Item @@ -30,9 +29,7 @@ standard ones, e.g.: """A custom content class""" ... -For a container type, we’d do: - -:: +For a container type, we’d do:: from plone.dexterity.content import Container @@ -42,41 +39,43 @@ For a container type, we’d do: You can now add any required attributes or methods to this class. -To make use of this class, set the *klass* attribute in the FTI to its +To make use of this class, set the ``klass`` attribute in the FTI to its dotted name, e.g. -:: +.. code-block:: xml - my.package.myitem.MyItem + my.package.myitem.MyItem This will cause the standard Dexterity factory to instantiate this class when the user submits the add form. .. note:: - As an alternative to setting *klass* in the FTI, you amy provide your - own *IFactory* utility for this type in lieu of Dexterity’s default - factory (see *plone.dexterity.factory*). However, you need to be careful - that this factory performs all necessary initialisation, so it is - normally better to use the standard factory. + As an alternative to setting ``klass`` in the FTI, + you may provide your own ``IFactory`` utility for this type in lieu of + Dexterity’s default factory (see `plone.dexterity.factory`_). + However, you need to be careful that this factory performs all necessary + initialisation, so it is normally better to use the standard factory. Custom class caveats -------------------- -There are a few important caveats when working with custom content -classes: - -- Make sure you use the correct base class: either - *plone.dexterity.content.Item* or - *plone.dexterity.content.Container*. -- If you mix in other base classes, it is safer to put the *Item* or - *Container* class first. If another class comes first, it may - override the *\_\_name\_\_*, *\_\_providedBy\_\_, - \_\_allow\_access\_to\_unprotected\_subobjects\_\_* and/or - *isPrincipiaFolderish* properties, and possibly the - *\_\_getattr\_\_()* and *\_\_getitem\_\_()* methods, causing problems - with the dynamic schemata and/or folder item security. In all cases, - you may need to explicitly set these attributes to the ones from the - correct base class. -- If you define a custom constructor, make sure it can be called with - no arguments, and with an optional *id* argument giving the name. +There are a few important caveats when working with custom content classes: + +- Make sure you use the correct base class: either + ``plone.dexterity.content.Item`` or + ``plone.dexterity.content.Container``. +- If you mix in other base classes, + it is safer to put the ``Item`` or ``Container`` class first. + If another class comes first, it may override the ``__name__``, + ``__providedBy__``, ``__allow_access_to_unprotected_subobjects__`` and/or + ``isPrincipiaFolderish`` properties, and possibly the ``__getattr__()`` + and ``__getitem__()`` methods, + causing problems with the dynamic schemata and/or folder item security. + In all cases, you may need to explicitly set these attributes to the ones + from the correct base class. +- If you define a custom constructor, make sure it can be called with + no arguments, and with an optional ``id`` argument giving the name. + +.. _plone.dexterity.content: http://pypi.python.org/pypi/plone.dexterity.content +.. _plone.dexterity.factory: http://pypi.python.org/pypi/plone.dexterity.factory diff --git a/source/advanced/defaults.txt b/source/advanced/defaults.txt index 48cb7b4..4631c63 100644 --- a/source/advanced/defaults.txt +++ b/source/advanced/defaults.txt @@ -7,9 +7,9 @@ It is often useful to calculate a default value for a field. This value will be used on the add form, before the field is set. To continue with our conference example, let’s set the default values -for the *start* and *end* dates to one week in the future and ten days +for the ``start`` and ``end`` dates to one week in the future and ten days in the future, respectively. We can do this by adding the following to -*program.py*: +``program.py``: .. code-block:: python @@ -18,27 +18,26 @@ in the future, respectively. We can do this by adding the following to # To get hold of the folder, do: context = data.context return datetime.datetime.today() + datetime.timedelta(7) - @form.default_value(field=IProgram['end']) def endDefaultValue(data): # To get hold of the folder, do: context = data.context return datetime.datetime.today() + datetime.timedelta(10) -We also need to import *datetime* at the top of the file, of course. +We also need to import ``datetime`` at the top of the file, of course. Notice how the functions specify a particular schema field that they provide the default value for. The decorator will actually register -these as “value adapters” for *z3c.form*, but you probably don’t need to +these as “value adapters” for `z3c.form`_, but you probably don’t need to worry about that. -The *data* argument is an object that contains an attribute for each +The ``data`` argument is an object that contains an attribute for each field in the schema. On the add form, most of these are likely to be -*None*, but on a different form, the values may be populated from the -context. The *data* object also has a *context* attribute that you can +``None``, but on a different form, the values may be populated from the +context. The ``data`` object also has a ``context`` attribute that you can use to get the form’s context. For add forms, that’s the containing folder; for other forms, it is normally a content object being edited or -displayed. If you need to look up tools (*getToolByName*) or acquire a -value from a parent object, use *data.context* as the starting point, +displayed. If you need to look up tools (``getToolByName``) or acquire a +value from a parent object, use ``data.context`` as the starting point, e.g.: .. code-block:: python @@ -48,7 +47,7 @@ e.g.: catalog = getToolByName(data.context, 'portal_catalog') The value returned by the method should be a value that’s allowable for -the field. In the case of *Datetime* fields, that’s a Python *datetime* +the field. In the case of ``Datetime`` fields, that’s a Python ``datetime`` object. It is possible to provide different default values depending on the type @@ -63,3 +62,5 @@ a particular form, you could use a decorator like: @form.default_value(field=IProgram['start'], form=FormClass) We’ll cover creating custom forms later in this manual. + +.. _plone.directives.form: http://pypi.python.org/pypi/plone.directives.form diff --git a/source/advanced/event-handlers.txt b/source/advanced/event-handlers.txt index 5d2da72..fa1b018 100644 --- a/source/advanced/event-handlers.txt +++ b/source/advanced/event-handlers.txt @@ -9,67 +9,68 @@ functionality, reacting when something happens to objects of our type. In Zope, that usually means writing event subscribers. Zope’s event model is *synchronous*. When an event is broadcast (via the -*notify()* function from the *zope.event* package), for example from the -*save* action of an add form, all registered event handlers will be +``notify()`` function from the `zope.event` package), for example from the +``save`` action of an add form, all registered event handlers will be called. There is no guarantee of which order the event handlers will be called in, however. Each event is described by an interface, and will typically carry some information about the event. Some events are known as *object events*, -and provide *zope.component.interfaces.IObjectEvent*. These have an -*object* attribute giving access to the (content) object that the event +and provide ``zope.component.interfaces.IObjectEvent``. These have an +``object`` attribute giving access to the (content) object that the event relates to. Object events allow event handlers to be registered for a specific type of object as well as a specific type of event. Some of the most commonly used event types in Plone are shown below. They are all object events. -- *zope.lifecycleevent.interfaces.IObjectCreatedEvent*, fired by the - standard add form just after an object has been created, but before - it has been added on the container. Note that it is often easier to - write a handler for *IObjectAddedEvent* (see below), because at this - point the object has a proper acquisition context. -- *zope.lifecycleevent.interfaces.IObjectModifiedEvent*, fired by the - standard edit form when an object has been modified -- *zope.app.container.interfaces.IObjectAddedEvent*, fired when an - object has been added to its container. The container is available as - the *newParent* attribute, and the name the new item holds in the - container is available as *newName*. -- *zope.app.container.interfaces.IObjectRemovedEvent*, fired when an - object has been removed from its container. The container is - available as the *oldParent* attribute, and the name the item held in - the container is available as *oldName*. -- *zope.app.container.interfaces.IObjectMovedEvent*, fired when an - object is added to, removed from, renamed in, or moved between - containers. This event is a super-type of *IObjectAddedEvent* and - *IObjectRemovedEvent*, shown above, so an event handler registered - for this interface will be invoked for the ‘added’ and ‘removed’ - cases as well. When an object is moved or renamed, all of - *oldParent*, *newParent*, *oldName* and *newName* will be set. -- *Products.CMFCore.interfaces.IActionSucceededEvent*, fired when a - workflow event has completed. The *workflow* attribute holds the - workflow instance involved, and the *action* attribute holds the - action (transition) invoked. - -Event handlers can be registered using ZCML with the ** +``zope.lifecycleevent.interfaces.IObjectCreatedEvent`` + fired by the standard add form just after an object has been created, + but before it has been added on the container. Note that it is often + easier to write a handler for ``IObjectAddedEvent`` (see below), because + at this point the object has a proper acquisition context. + +``zope.lifecycleevent.interfaces.IObjectModifiedEvent`` + fired by the standard edit form when an object has been modified. + +``zope.app.container.interfaces.IObjectAddedEvent`` + fired when an object has been added to its container. The container is + available as the ``newParent`` attribute, and the name the new item holds + in the container is available as ``newName``. + +``zope.app.container.interfaces.IObjectRemovedEvent`` + fired when an object has been removed from its container. The container + is available as the ``oldParent`` attribute, and the name the item held + in the container is available as ``oldName``. + +``zope.app.container.interfaces.IObjectMovedEvent`` + fired when an object is added to, removed from, renamed in, or moved + between containers. This event is a super-type of ``IObjectAddedEvent`` + and ``IObjectRemovedEvent``, shown above, so an event handler registered + for this interface will be invoked for the ‘added’ and ‘removed’ cases + as well. When an object is moved or renamed, all of ``oldParent``, + ``newParent``, ``oldName`` and ``newName`` will be set. + +``Products.CMFCore.interfaces.IActionSucceededEvent`` + fired when a workflow event has completed. The ``workflow`` attribute + holds the workflow instance involved, and the ``action`` attribute holds + the action (transition) invoked. + +Event handlers can be registered using ZCML with the ```` directive, but when working with Dexterity types, we’ll more commonly -use the *grok.subscriber()* in Python code. +use the ``grok.subscriber()`` in Python code. -As an example, let’s add an event handler to the *Presenter* type that +As an example, let’s add an event handler to the ``Presenter`` type that tries to find users with matching names matching the presenter id, and send these users an email. -First, we require a few additional imports at the top of *presenter.py*: - -:: +First, we require a few additional imports at the top of ``presenter.py``:: from zope.app.container.interfaces import IObjectAddedEvent from Products.CMFCore.utils import getToolByName Then, we’ll add the following event subscriber after the schema -definition: - -:: +definition:: @grok.subscribe(IPresenter, IObjectAddedEvent) def notifyUser(presenter, event): @@ -94,8 +95,10 @@ definition: There are many ways to improve this rather simplistic event handler, but it illustrates how events can be used. The first argument to -*grok.subscribe()* is an interface describing the object type. For -non-object events, this is omitted. The second arugment is the event +``grok.subscribe()`` is an interface describing the object type. For +non-object events, this is omitted. The second argument is the event type. The arguments to the function reflects these two, so the first -argument is the *IPresenter* instance and the second is an -*IObjectAddedEvent* instance. +argument is the ``IPresenter`` instance and the second is an +``IObjectAddedEvent`` instance. + +.. _zope.event: http://pypi.python.org/pypi/zope.event diff --git a/source/advanced/files-and-images.txt b/source/advanced/files-and-images.txt index 2c52d64..28e9a40 100644 --- a/source/advanced/files-and-images.txt +++ b/source/advanced/files-and-images.txt @@ -3,71 +3,67 @@ Files and images **Working with file and image fields, including BLOBs** -Plone has dedicated *File* and *Image* types, and it is often preferable +Plone has dedicated ``File`` and ``Image`` types, and it is often preferable to use these for managing files and images. However, it is sometimes -useful to treat binary data as fields on an object. When working with +useful to treat fields on an object as binary data. When working with Dexterity, you can accomplish this by using `plone.namedfile`_ and `plone.formwidget.namedfile`_. -The *plone.namedfile* package includes four field types, all found in -the *plone.namedfile.field* module: +The `plone.namedfile`_ package includes four field types, all found in +the ``plone.namedfile.field`` module: -- *NamedFile* stores non-BLOB files. This is useful for small files - when you don’t want to configure BLOB storage. -- *NamedImage* stores non-BLOB images. -- *NamedBlobFile* stores BLOB files (see note below). It is otherwise - identical to *NamedFile*. -- *NamedBlobImage* stores BLOB images (see note below). It is otherwise - identical to *NamedImage*. +- ``NamedFile`` stores non-BLOB files. This is useful for small files + when you don’t want to configure BLOB storage. +- ``NamedImage`` stores non-BLOB images. +- ``NamedBlobFile`` stores BLOB files (see note below). It is otherwise + identical to ``NamedFile``. +- ``NamedBlobImage`` stores BLOB images (see note below). It is otherwise + identical to ``NamedImage``. -Note that *NamedBlobFile* and *NamedBlobImage* depends on -*z3c.blobfile*. This dependency is specified via the *[blobs]* `extra`_. +Note that ``NamedBlobFile`` and ``NamedBlobImage`` depends on +`z3c.blobfile`_. This dependency is specified via the ``[blobs]`` `extra`_ +feature. .. note:: - If you do not have *z3c.blobfile* in your buildout (most likely because - you did not depend on the *[blobs]* extra for *plone.namedfile*), the - *NamedBlobFile* and *NamedBlobImage* field and value types will not be - importable. They are only defined if BLOB support is detected. + If you do not have ``z3c.blobfile`` in your buildout (most likely because + you did not depend on the ``[blobs]`` extra for ``plone.namedfile``), the + ``NamedBlobFile`` and ``NamedBlobImage`` field and value types will not + be importable. They are only defined if BLOB support is detected. In use, the four field types are all pretty similar. They actually store -persistent objects of type *plone.namedfile.NamedFile*, -*plone.namedfile.NamedImage*, *plone.namedfile.NamedBlobFile* (if -available) and *plone.namedfile.NamedBlobImage* (if available), +persistent objects of type ``plone.namedfile.NamedFile``, +``plone.namedfile.NamedImage``, ``plone.namedfile.NamedBlobFile`` (if +available) and ``plone.namedfile.NamedBlobImage`` (if available), respectively. Note the different module! These objects have attributes -like *data*, to access the raw binary data, *contentType*, to get a MIME -type, and *filename*, to get the original filename. The image values -also support *\_height* and *\_width* to get image dimensions. +like ``data``, to access the raw binary data, ``contentType``, to get a MIME +type, and ``filename``, to get the original filename. The image values +also support ``_height`` and ``_width`` to get image dimensions. To use the non-BLOB image and file fields, it is sufficient to depend on -*plone.formwidget.namedfile*, since this includes *plone.namefile* as a -dependency. We prefer to be explicit in *setup.py*, however, since we -will actually import directly from *plone.namedfile*: +``plone.formwidget.namedfile``, since this includes ``plone.namefile`` as a +dependency. We prefer to be explicit in ``setup.py``, however, since we +will actually import directly from ``plone.namedfile``:: -:: + install_requires=[ + ... + 'plone.namedfile', + 'plone.formwidget.namedfile', + ], - install_requires=[ - ... - 'plone.namedfile', - 'plone.formwidget.namedfile', - ], +To use the ``[blobs]`` extra, we would need the following line instead of +the ``plone.namedfile`` line instead:: -To use the *[blobs]* extra, we would need the following line instead of -the *plone.namedfile* line instead: - -:: - - 'plone.namedfile[blobs]', + 'plone.namedfile[blobs]', .. note:: - Again, we do not need separate ** lines in *configure.zcml* - for these new dependencies, because we use **. + Again, we do not need separate ```` lines in + ``configure.zcml`` for these new dependencies, because we use + ````. For the sake of illustration, we will add a (non-BLOB) image of the -speaker to the *Presenter* type. In *presenter.py*, we add: - -:: +speaker to the ``Presenter`` type. In ``presenter.py``, we add:: from plone.namedfile.field import NamedImage @@ -80,13 +76,11 @@ speaker to the *Presenter* type. In *presenter.py*, we add: ) To use this in a view, we can either use a display widget via a -*DisplayForm*, or construct a download URL manually. Since we don’t have -a *DisplayForm* for the *Presenter* type, we’ll do the latter (of +``DisplayForm``, or construct a download URL manually. Since we don’t have +a ``DisplayForm`` for the ``Presenter`` type, we’ll do the latter (of course, we could easily turn the view into a display form as well). -In *presenter\_templates/view.pt*, we add this block of TAL: - -:: +In ``presenter_templates/view.pt``, we add this block of TAL::
@@ -96,21 +90,20 @@ In *presenter\_templates/view.pt*, we add this block of TAL: />
-This constructs an image URL using the *@@download* view from -*plone.namedfile*. This view takes the name of the field containing the -file or image on the traversal subpath (*/picture*), and optionally a +This constructs an image URL using the ``@@download`` view from +``plone.namedfile``. This view takes the name of the field containing the +file or image on the traversal subpath (``/picture``), and optionally a filename on a further sub-path. The filename is used mainly so that the URL ends in the correct extension, which can help ensure web browsers -display the picture correctly. We also define the *height* and *width* +display the picture correctly. We also define the ``height`` and ``width`` of the image based on the values set on the object. For file fields, you can construct a download URL in a similar way, -using an ** tag, e.g.: - -:: +using an ```` tag, e.g.:: +.. _z3c.blobfile: http://pypi.python.org/pypi/z3c.blobfile .. _plone.namedfile: http://pypi.python.org/pypi/plone.namedfile .. _plone.formwidget.namedfile: http://pypi.python.org/pypi/plone.formwidget.namedfile .. _extra: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies diff --git a/source/advanced/permissions.txt b/source/advanced/permissions.txt index 48822dc..a6cd5d3 100644 --- a/source/advanced/permissions.txt +++ b/source/advanced/permissions.txt @@ -3,112 +3,150 @@ Permissions **Setting up add permissions, view permissions and field view/edit permissions** -Plone’s security system is based the concept of *permissions* protecting -operations (like accessing a view, viewing a field, modifying a field, -or adding a type of content) that are granted to *roles*, which in turn -are granted to *users* and/or *groups*. In the context of developing -content types, permissions are typically used in three different ways: - -- A content type or group of related content types often has a custom - *add permission* which controls who can add this type of content. -- Views (including forms) are sometimes protected by custom - permissions. -- Individual fields are sometimes protected by permissions, so that - some users can view and edit fields that others can’t see. - -It is easy to create new permissions. However, be aware that it is -considered good practice to use the standard permissions wherever -possible and use *workflow* to control which roles are granted these -permissions on a per-instance basis. We’ll cover workflow later in this -manual. +Plone’s security system is based on the concept of +*permissions* protecting *operations* +(like accessing a view, +viewing a field, +modifying a field, +or adding a type of content) +that are granted to *roles*, +which in turn are granted to *users* and/or *groups*. +In the context of developing content types, +permissions are typically used in three different ways: + +- A content type or group of related content types often has a custom + *add permission* which controls who can add this type of content. +- Views (including forms) are sometimes protected by custom + permissions. +- Individual fields are sometimes protected by permissions, + so that some users can view and edit fields that others can’t see. + +It is easy to create new permissions. +However, be aware that it is considered good practice +to use the standard permissions wherever possible and +use *workflow* to control which roles are granted these permissions +on a per-instance basis. +We’ll cover workflow later in this manual. Standard permissions ~~~~~~~~~~~~~~~~~~~~~ -The standard permissions can be found in *Product.Five*’s -*permissions.zcml* (*parts/omelette/Products/Five/permissions.zcml*). -Here, you will find a short *id* (also known as the *Zope 3 permission -id*) and a longer *title* (also known as the *Zope 2 permission title*). -For historical reasons, some areas in Plone use the id, whilst others -use the title. As a rule of thumb: - -- Browser views defined in ZCML or protected via a *grok.require()* - directive use the Zope 3 permission id -- Security checks using *zope.security.checkPermission()* use the Zope - 3 permission id -- Dexterity’s *add\_permission* FTI variable uses the Zope 3 permission - id. -- The *rolemap.xml* GenericSetup handler and workflows use the Zope 2 - permission title. -- Security checks using *AccessControl*’s - *getSecurityManager().checkPermission()*, including the methods on - the *portal\_membership* tool, use the Zope 2 permission title. - -The most commonly used permission are shown below. The Zope 2 permission -title is shown in parentheses. - -- *zope2.View (View)* is used to control access to the standard view - of a content item -- *zope2.DeleteObjects* (*Delete objects*) is used to control the - ability to delete child objects in a container -- *cmf.ModifyPortalContent* (*Modify portal content*) is used to - control write access to content items -- *cmf.ManagePortal* (*Manage portal*) is used to control access to - management screens -- *cmf.AddPortalContent* (*Add portal content*) is the standard add - permission required to add content to a folder -- *cmf.SetOwnProperties* (*Set own properties*) is used to allow users - to set their own member properties -- *cmf.RequestReview* (*Request Review*) is typically used as a - workflow transition guard to allow users to submit content for review -- *cmf.ReviewPortalContent* (*Review portal content*) is usually - granted to the *Reviewer* role, controlling the ability to publish or - reject content +The standard permissions can be found in ``Product.Five``\’s ``permissions.zcml`` +(``parts/omelette/Products/Five/permissions.zcml``). +Here, you will find a short ``id`` +(also known as the *Zope 3 permission id*) +and a longer ``title`` +(also known as the *Zope 2 permission title*). +For historical reasons, some areas in Plone use the id, +whilst others use the title. +As a rule of thumb: + +- Browser views defined in ZCML or protected via a ``grok.require()`` + directive use the Zope 3 permission id. +- Security checks using ``zope.security.checkPermission()`` use the Zope + 3 permission id +- Dexterity’s ``add_permission`` FTI variable uses the Zope 3 permission + id. +- The ``rolemap.xml`` GenericSetup handler and workflows use the Zope 2 + permission title. +- Security checks using ``AccessControl``’s + ``getSecurityManager().checkPermission()``, including the methods on + the ``portal_membership`` tool, use the Zope 2 permission title. + +The most commonly used permission are shown below. +The Zope 2 permission title is shown in parentheses. + +``zope2.View`` (:guilabel:`View`) + used to control access to the standard view of a content item; + +``zope2.DeleteObjects`` (:guilabel:`Delete objects`) + used to control the ability to delete child objects in a container; + +``cmf.ModifyPortalContent`` (:guilabel:`Modify portal content`) + used to control write access to content items; + +``cmf.ManagePortal`` (:guilabel:`Manage portal`) + used to control access to management screens; + +``cmf.AddPortalContent`` (:guilabel:`Add portal content`) + the standard add permission required to add content to a folder; + +``cmf.SetOwnProperties`` (:guilabel:`Set own properties`) + used to allow users to set their own member properties' + +``cmf.RequestReview`` (:guilabel:`Request Review`) + typically used as a workflow transition guard + to allow users to submit content for review; + +``cmf.ReviewPortalContent`` (:guilabel:`Review portal content`) + usually granted to the ``Reviewer`` role, + controlling the ability to publish or reject content. Standard roles ~~~~~~~~~~~~~~~ As with permissions, it is easy to create custom roles (use the -*rolemap.xml* GenericSetup import step - see *CMFPlone*’s version of +``rolemap.xml`` GenericSetup import step – see ``CMFPlone``\’s version of this file for an example), although you should use the standard roles where possible. The standard roles in Plone are: -- *Anonymous*, a pseudo-role that represents non-logged in users. +:guilabel:`Anonymous` + a pseudo-role that represents non-logged in users. .. note:: - if a permission is granted to *Anonymous*, it is effectively - granted to everyone. It is not possible to grant permissions to - non-logged in users without also granting them to logged in ones. + if a permission is granted to :guilabel:`Anonymous`, + it is effectively granted to everyone. + It is not possible to grant permissions to non-logged in users + without also granting them to logged in ones. -- *Authenticated*, a pseudo-role that represents logged-in users. -- *Owner*, which is automatically granted to the creator of an object. -- *Manager*, which represents super-users/administrators. Almost all - permissions that are not granted to *Anonymous* are granted to - *Manager*. -- *Reviewer*, which represents content reviewers separately from site - administrators. It is possible to grant the *Reviewer* role locally - on the *Sharing* tab, where it is shown as *Can review*. -- *Member*, representing “standard” Plone users +:guilabel:`Authenticated` + a pseudo-role that represents logged-in users. + +:guilabel:`Owner` + automatically granted to the creator of an object. + +:guilabel:`Manager` + which represents super-users/administrators. + Almost all permissions that are not granted to ``Anonymous`` + are granted to ``Manager``. + +:guilabel:`Reviewer` + which represents content reviewers separately from site administrators. + It is possible to grant the :guilabel:`Reviewer` role locally on the + :guilabel:`Sharing`` tab, where it is shown as :guilabel:`Can review`. + +:guilabel:`Member` + representing “standard” Plone users. In addition, there are three roles that are intended to be used as *local roles* only. These are granted to specific users or groups via -the *Sharing* tab, where they appear under more user friendly +the :guilabel:`Sharing` tab, where they appear under more user friendly pseudonyms. -- *Reader*, aka *Can view*, confers the right to view content. As a - role of thumb, the *Reader* role should have the *View* and *Access - contents information* permissions if the *Owner* roles does. -- *Editor*, aka *Can edit*, confers the right to edit content. As a - role of thumb, the *Editor* role should have the *Modify portal - content* permission if the *Owner* roles does. -- *Contributor*, aka *Can add*, confers the right to add new content. - As a role of thumb, the *Contributor* role should have the *Add - portal content* permission and any type-specific add permissions - globally (i.e. granted in *rolemap.xml*), although these permissions - are sometimes managed in workflow as well. +:guilabel:`Reader`, aka :guilabel:`Can view`, + confers the right to view content. + As a role of thumb, + the :guilabel:`Reader` role should have the + :guilabel:`View` and :guilabel:`Access contents information` permissions + if the :guilabel:`Owner` roles does. + +:guilabel:`Editor`, aka :guilabel:`Can edit`, + confers the right to edit content. + As a role of thumb, the :guilabel:`Editor` role should have the + :guilabel:`Modify portal content` permission + if the :guilabel:`Owner` roles does. + +:guilabel:`Contributor`, aka :guilabel:`Can add`, + confers the right to add new content. + As a role of thumb, + the:guilabel: `Contributor` role should have the + `Add:guilabel: portal content` permission + and any type-specific add permissions globally + (i.e. granted in ``rolemap.xml``), + although these permissions are sometimes managed in workflow as well. Performing permission checks in code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -122,11 +160,10 @@ context object, since permissions can change with workflow. a permission, and assign the permission to the appropriate role or roles. -As an example, let’s display a message on the view of a *Session* type -if the user has the *cmf.RequestReview* permission. In *session.py*, we -update the *View* class with the following: - -:: +As an example, +let’s display a message on the view of a ``Session`` type +if the user has the ``cmf.RequestReview`` permission. +In ``session.py``, we update the ``View`` class with the following:: from zope.security import checkPermission @@ -137,69 +174,76 @@ update the *View* class with the following: def canRequestReview(self): return checkPermission('cmf.RequestReview', self.context) -And in the *session\_templates/view.pt* template, we add: +And in the ``session_templates/view.pt`` template, we add: -:: +.. code-block:: html -
- Please submit this for review. -
+
+ Please submit this for review. +
Creating custom permissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Although the standard permissions should be used to control basic -operations (view, modify, delete, review), it is sometimes useful to -create new permissions. Combined with custom workflows, custom -permissions can be used to create highly tailored content review cycles -and data entry applications. They are also an important way to control -who can add what content. +Although the standard permissions should be used to control basic operations +(view, modify, delete, review), +it is sometimes useful to create new permissions. +Combined with custom workflows, +custom permissions can be used +to create highly tailored content review cycles +and data entry applications. +They are also an important way to control who can add what content. The easiest way to create a custom permission is with the help of the -`collective.autopermission`_ package, which allows permissions to be -defined using the ** ZCML statement. +`collective.autopermission`_ package, +which allows permissions to be defined +using the ```` ZCML statement. .. note:: - *collective.autopermission* is obsolete in Zope 2.12, where its - functionality has been merged into Zope itself - -As an example, let’s create some custom permissions for use with the -*Session* type. We’ll create a new add permission, so that we can let -any member submit a session to a program, and a permission which we will -later use to let reviewers edit some specific fields on the *Session* -type. + `collective.autopermission`_ is obsolete in Zope 2.12, where its + functionality has been merged into Zope itself. -First, we need to depend on *collective.autopermission*. In *setup.py*: +As an example, +let’s create some custom permissions +for use with the ``Session`` type. +We’ll create a new add permission, +so that we can let any member submit a session to a program, +and a permission which we will later use +to let reviewers edit some specific fields on the ``Session`` type. -:: +First, we need to depend on `collective.autopermission`_. In ``setup.py``:: - install_requires=[ - ... - 'collective.autopermission', - ], + install_requires=[ + ... + 'collective.autopermission', + ], .. note:: - Make sure *collective.autopermission’s* configuration is included before - any custom permissions are defined. In our case, the - ** line takes care of this. + Make sure `collective.autopermission`_\’s configuration is included + before any custom permissions are defined. + In our case, + the ```` line takes care of this. -Next, we’ll create a file called *permissions.zcml* to hold the -permissions (we could also place them directly into *configure.zcml*). -We need to include this in *configure.zcml*, just after the -** line. +Next, we’ll create a file called ``permissions.zcml`` to hold the +permissions (we could also place them directly into ``configure.zcml``). +We need to include this in ``configure.zcml``, just after the +```` line: -:: +.. code-block:: xml - + .. note:: - All permissions need to be defined before the ** line in *configure.zcml*. Otherwise, you may get errors - trying to use the permission with a *grok.require()* directive. + All permissions need to be defined before the + ```` line in ``configure.zcml``. + Otherwise, you may get errors trying to use the permission + with a ``grok.require()`` directive. -The *permissions.zcml* file looks like this: +The ``permissions.zcml`` file looks like this: -:: +.. code-block:: xml -New permissions are granted to the *Manager* role only by default. To -set a different default, we can use the *rolemap.xml* GenericSetup -import step, which maps permissions to roles at the site root. +New permissions are granted to the :guilabel:`Manager` role only by default. +To set a different default, +we can use the ``rolemap.xml`` GenericSetup import step, +which maps permissions to roles at the site root. -In *profiles/default/rolemap.xml*, we have the following: +In ``profiles/default/rolemap.xml``, we have the following: .. code-block:: xml @@ -243,18 +288,19 @@ In *profiles/default/rolemap.xml*, we have the following: .. note:: This file uses the Zope 2 permission title instead of the shorter Zope 3 - permission id + permission id. Content type add permissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Dexterity content types’ add permissions are set in the FTI, using the -*add\_permission* property. This can be changed through the web or in -the GenericSetup import step for the content type. +Dexterity content types’ add permissions are set in the FTI, +using the ``add_permission`` property. +This can be changed through the web +or in the GenericSetup import step for the content type. -To make the *Session* type use our new permission, we modify the -*add\_permission* line in -*profiles/default/example.conference.session.xml*: +To make the ``Session`` type use our new permission, we modify the +``add_permission`` line in +``profiles/default/example.conference.session.xml``: .. code-block:: xml @@ -264,74 +310,68 @@ Protecting views and forms ~~~~~~~~~~~~~~~~~~~~~~~~~~ Access to views and other browser resources (like viewlets or portlets) -can be protected by permissions, either using the *permission* attribute -on ZCML statements like ** or using the *grok.require()* +can be protected by permissions, either using the ``permission`` attribute +on ZCML statements like ```` or using the ``grok.require()`` directive. -We have already seen this directive on our views: - -:: +We have already seen this directive on our views:: class View(grok.View): grok.context(IPresenter) grok.require('zope2.View') We could use a custom permission name as the argument to -*grok.require()*. We could also use the special *zope.Public* permission +``grok.require()``. We could also use the special ``zope.Public`` permission name to make the view accessible to anyone. Protecting form fields ~~~~~~~~~~~~~~~~~~~~~~~ Individual fields in a schema may be associated with a *read* permission -and a *write* permission. The read permission is used to control access -to the field’s value via protected code (e.g. scripts or templates -created through the web) and URL traversal, and can be used to control -the appearance of fields when using display forms (if you use custom -views that access the attribute directly, you’ll need to perform your -own checks). Write permissions can be used to control whether or not a -given field appears on a type’s add and edit forms. - -In both cases, read and write permissions are annotated onto the schema -using directives similar to those we’ve already seen for form widget -hints. The *read\_permission()* and *write\_permission()* directives are -found in the `plone.directives.dexterity`_package. +and a *write* permission. +The read permission is used to control access to the field’s value +via protected code (e.g. scripts or templates created through the web) +and URL traversal, +and can be used to control the appearance of fields +when using display forms +(if you use custom views that access the attribute directly, +you’ll need to perform your own checks). +Write permissions can be used to control +whether or not a given field appears on a type’s add and edit forms. + +In both cases, +read and write permissions are annotated onto the schema using directives +similar to those we’ve already seen for form widget hints. +The ``read_permission()`` and ``write_permission()`` directives are +found in the `plone.directives.dexterity`_ package. As an example, let’s add a field for *Session* reviewers to record the track for a session. We’ll store the vocabulary of available tracks on -the parent *Program* object in a text field, so that the creator of the -*Program* can choose the available tracks. - -First, we add this to the *IProgram* schema in *program.py*: +the parent ``Program`` object in a text field, so that the creator of the +``Program`` can choose the available tracks. -:: +First, we add this to the ``IProgram`` schema in ``program.py``:: - form.widget(tracks=TextLinesFieldWidget) - tracks = schema.List( - title=_(u"Tracks"), - required=True, - default=[], - value_type=schema.TextLine(), - ) + form.widget(tracks=TextLinesFieldWidget) + tracks = schema.List( + title=_(u"Tracks"), + required=True, + default=[], + value_type=schema.TextLine(), + ) -The *TextLinesFieldWidget* is used to edit a list of text lines in a -text area. It is imported as: - -:: +The ``TextLinesFieldWidget`` is used to edit a list of text lines in a +text area. It is imported as:: from plone.z3cform.textlines.textlines import TextLinesFieldWidget -Next, we’ll add a vocabulary for this to *session.py*: - -:: +Next, we’ll add a vocabulary for this to ``session.py``:: from Acquisition import aq_inner, aq_parent from zope.schema.interfaces import IContextSourceBinder from zope.schema.vocabulary import SimpleVocabulary - ... - @grok.provider(IContextSourceBinder) def possibleTracks(context): @@ -346,31 +386,28 @@ Next, we’ll add a vocabulary for this to *session.py*: return SimpleVocabulary.fromValues(values) -This vocabulary finds the closest *IProgram* (in the add form, the -*context* will be the *Program*, but on the edit form, it will be the -*Session*, so we need to check the parent) and uses its *tracks* -variable as the vocabulary. - -Next, we add a field to the *ISession* interface in the same file and -protect it with the relevant write permission: - -:: +This vocabulary finds the closest ``IProgram`` +(in the add form, the ``context`` will be the ``Program``, +but on the edit form, it will be the ``Session``, +so we need to check the parent) +and uses its ``tracks`` variable as the vocabulary. - dexterity.write_permission(track='example.conference.ModifyTrack') - track = schema.Choice( - title=_(u"Track"), - source=possibleTracks, - required=False, - ) +Next, we add a field to the ``ISession`` interface in the same file and +protect it with the relevant write permission:: -The *dexterity* module is the root of the *plone.directives.dexterity* -package, imported as: + dexterity.write_permission(track='example.conference.ModifyTrack') + track = schema.Choice( + title=_(u"Track"), + source=possibleTracks, + required=False, + ) -:: +The ``dexterity`` module is the root of the `plone.directives.dexterity`_ +package, imported as:: from plone.directives import dexterity -With this in place, users with the *example.conference: Modify track* +With this in place, users with the ``example.conference: Modify track`` permission should be able to edit tracks for a session. For everyone else, the field will be hidden in the edit form. diff --git a/source/advanced/references.txt b/source/advanced/references.txt index b33690d..a2f120c 100644 --- a/source/advanced/references.txt +++ b/source/advanced/references.txt @@ -4,36 +4,33 @@ References **How to work with references between content objects** References are a way to maintain links between content that remain valid -even if one or both content items are moved or renamed. +even if one or both of the linked items are moved or renamed. Under the hood, Dexterity’s reference system uses `five.intid`_, a Zope -2 integration layer for *zope.intid*, to give each content item a unique +2 integration layer for `zope.intid`_, to give each content item a unique integer id. These are the basis for relationships maintained with the `zc.relationship`_ package, which in turn is accessed via an API provided by `z3c.relationfield`_, integrated into Zope 2 with `plone.app.relationfield`_. For most purposes, you need only to worry -about the *z3c.relationfield* API, which provides methods for finding +about the ``z3c.relationfield`` API, which provides methods for finding source and target objects for references and searching the relationship catalog. References are most commonly used in form fields with a selection or content browser widget. Dexterity comes with a standard widget in -`plone.formwidget.contenttree`_ configured for the *RelationList* and -*RelationChoice* fields from *z3c.relationfield*. +`plone.formwidget.contenttree`_ configured for the ``RelationList`` and +``RelationChoice`` fields from ``z3c.relationfield``. To illustrate the use of references, we will allow the user to create a -link between a *Session* and its *Presenter*. Since Dexterity already -ships with and installs *plone.formwidget.contenttree* and -*z3c.relationfield*, we do not need to add any further setup code, and -we can use the field directly in *session.py*: - -:: +link between a ``Session`` and its ``Presenter``. Since Dexterity already +ships with and installs ``plone.formwidget.contenttree`` and +``z3c.relationfield``, we do not need to add any further setup code, and +we can use the field directly in ``session.py``:: ... from z3c.relationfield.schema import RelationChoice from plone.formwidget.contenttree import ObjPathSourceBinder - ... from example.conference.presenter import IPresenter @@ -41,7 +38,6 @@ we can use the field directly in *session.py*: class ISession(form.Schema): """A conference session. Sessions are managed inside Programs. """ - ... presenter = RelationChoice( @@ -51,25 +47,23 @@ we can use the field directly in *session.py*: ) To allow multiple items to be selected, we could have used a -*RelationList* like: - -:: +``RelationList`` like:: - relatedItems = RelationList( - title=u"Related Items", - default=[], - value_type=RelationChoice(title=_(u"Related"), - source=ObjPathSourceBinder()), - required=False, - ) + relatedItems = RelationList( + title=u"Related Items", + default=[], + value_type=RelationChoice(title=_(u"Related"), + source=ObjPathSourceBinder()), + required=False, + ) -The *ObjPathSourceBinder* class is an *IContextSourceBinder* that returns +The ``ObjPathSourceBinder`` class is an ``IContextSourceBinder`` that returns a vocabulary with content objects as values, object titles as term titles and object paths as tokens. You can pass keyword arguments to the constructor for -*ObjPathSourceBinder()* to restrict the selectable objects. Here, we -demand that the object must provide the *IPresenter* interface. The +``ObjPathSourceBinder()`` to restrict the selectable objects. Here, we +demand that the object must provide the ``IPresenter`` interface. The syntax is the same as that used in a catalog search, except that only simple values and lists are allowed (e.g. you can’t use a dict to specify a range or values for a field index). @@ -77,43 +71,42 @@ specify a range or values for a field index). If you want to restrict the folders and other content shown in the content browser, you can pass a dictionary with catalog search parameters (and here, any valid catalog query will do) as the first -non-keyword argument (*navigation\_tree\_query*) to the -*ObjPathSourceBinder()* constructor. +non-keyword argument (``navigation_tree_query``) to the +``ObjPathSourceBinder()`` constructor. If you want to use a different widget, you can use the same source (or a custom source that has content objects as values) with something like the autocomplete widget. The following line added to the interface will -make the presenter selection similar to the *organizer* selection widget -we showed in the previous section: - -:: +make the presenter selection similar to the ``organizer`` selection widget +we showed in the previous section:: - form.widget(presenter=AutocompleteFieldWidget) + form.widget(presenter=AutocompleteFieldWidget) Once the user has created some relationships, the value stored in the -relation field is a *RelationValue* object. This provides various +relation field is a ``RelationValue`` object. This provides various attributes, including: -- *from\_object*, the object from which the relationship is made -- *to\_object*, the object to which the relationship is made -- *from\_id* and *to\_id*, the integer ids of the source and target -- *from\_path* and *to\_path*, the path of the source and target +- ``from_object``, the object from which the relationship is made; +- ``to_object``, the object to which the relationship is made; +- ``from_id`` and ``to_id``, the integer ids of the source and target; +- ``from_path`` and ``to_path``, the path of the source and target. -The *isBroken()* method can be used to determine if the relationship is +The ``isBroken()`` method can be used to determine if the relationship is broken. This normally happens if the target object is deleted. To display the relationship on our form, we can either use a display -widget on a *DisplayForm*, or use this API to find the object and -display it. We’ll do the latter in *session\_templates/view.pt*: +widget on a ``DisplayForm``, or use this API to find the object and +display it. We’ll do the latter in ``session_templates/view.pt``: .. code-block:: html -
- - -
+
+ + +
.. _five.intid: http://pypi.python.org/pypi/five.intid +.. _zope.intid: http://pypi.python.org/pypi/zope.intid .. _zc.relationship: http://pypi.python.org/pypi/zc.relationship .. _z3c.relationfield: http://pypi.python.org/pypi/z3c.relationfield .. _plone.app.relationfield: http://pypi.python.org/pypi/plone.app.relationfield diff --git a/source/advanced/rich-text-markup-transformations.txt b/source/advanced/rich-text-markup-transformations.txt index 9aa91b4..b83eaa9 100644 --- a/source/advanced/rich-text-markup-transformations.txt +++ b/source/advanced/rich-text-markup-transformations.txt @@ -12,10 +12,8 @@ again. Even when the input format is HTML, there is often a need for a transformation to tidy up the HTML and strip out tags that are not permitted. -It is possible to store HTML in a standard *Text* field. You can even -get a WYSIWYG widget, by using a schema such as this: - -:: +It is possible to store HTML in a standard ``Text`` field. You can even +get a WYSIWYG widget, by using a schema such as this:: from plone.directives import form from zope import schema @@ -26,11 +24,11 @@ get a WYSIWYG widget, by using a schema such as this: form.widget(body=WysiwygFieldWidget) body = schema.Text(title=u"Body text") +.. _richtext-label: + However, this approach does not allow for alternative markups or any form of content filtering. For that, we need to use a more powerful -field: *RichText* from the `plone.app.textfield`_ package. - -:: +field: ``RichText`` from the `plone.app.textfield`_ package:: from plone.directives import form from plone.app.textfield import RichText @@ -39,28 +37,26 @@ field: *RichText* from the `plone.app.textfield`_ package. body = RichText(title=u"Body text") -The *RichText* field constructor can take the following arguments in -addition to the usual arguments for a *Text* field: - -- *default\_mime\_type*, a string representing the default MIME type of - the input markup. This defaults to *text/html*. -- *output\_mime\_type*, a string representing the default output MIME - type. This defaults to *text/x-html-safe*, which is a Plone-specific - MIME type that disallows certain tags. Use the *HTML Filtering* - control panel in Plone to control the tags. -- *allowed\_mime\_types*, a tuple of strings giving a vocabulary of - allowed input MIME types. If this is *None* (the default), the - allowable types will be restricted to those set in Plone’s *Markup* - control panel. - -Also note: The *default* field can be set to either a unicode string (in +The ``RichText`` field constructor can take the following arguments in +addition to the usual arguments for a ``Text`` field: + +- ``default_mime_type``, a string representing the default MIME type of + the input markup. This defaults to ``text/html``. +- ``output_mime_type``, a string representing the default output MIME + type. This defaults to ``text/x-html-safe``, which is a Plone-specific + MIME type that disallows certain tags. Use the :guilabel:`HTML Filtering` + control panel in Plone to control the tags. +- ``allowed_mime_types``, a tuple of strings giving a vocabulary of + allowed input MIME types. If this is ``None`` (the default), the + allowable types will be restricted to those set in Plone’s + :guilabel:`Markup` control panel. + +Also note: The *default* field can be set to either a unicode object (in which case it will be assumed to be a string of the default MIME type) -or a *RichTextValue* object (see below). +or a ``RichTextValue`` object (see below). Below is an example of a field allow StructuredText and -reStructuredText, transformed to HTML by default. - -:: +reStructuredText, transformed to HTML by default:: from plone.directives import form from plone.app.textfield import RichText @@ -90,47 +86,44 @@ reStructuredText, transformed to HTML by default. The RichTextValue ~~~~~~~~~~~~~~~~~~~ -The *RichText* field does not store a string. Instead, it stores a -*RichTextValue* object. This is an immutable object that has the +The ``RichText`` field does not store a string. Instead, it stores a +``RichTextValue`` object. This is an immutable object that has the following properties: -- *raw*, a unicode string with the original input markup -- *mimeType*, the MIME type of the original markup, e.g. *text/html* or - *text/structured*. -- *encoding*, the default character encoding used when transforming the - input markup. Most likely, this will be utf–8 -- *raw\_encoded*, the raw input encoded in the given encoding -- *outputMimeType*, the MIME type of the default output, taken from the - field at the time of instantiation -- *output*, a unicode string representing the transformed output. If - possible, this is cached persistently until the *RichTextValue* is - replaced with a new one (as happens when an edit form is saved, for - example). - -The storage of the *RichTextValue* object is optimised for the case where +- ``raw``, a unicode string with the original input markup; +- ``mimeType``, the MIME type of the original markup, e.g. ``text/html`` or + ``text/structured``; +- ``encoding``, the default character encoding used when transforming the + input markup. Most likely, this will be UTF-8; +- ``raw_encoded``, the raw input encoded in the given encoding; +- ``outputMimeType``, the MIME type of the default output, taken from the + field at the time of instantiation; +- ``output``, a unicode object representing the transformed output. If + possible, this is cached persistently until the ``RichTextValue`` is + replaced with a new one (as happens when an edit form is saved, for + example). + +The storage of the ``RichTextValue`` object is optimised for the case where the transformed output will be read frequently (i.e. on the view screen of the content object) and the raw value will be read infrequently (i.e. on the edit screen). Because the output value is cached indefinitely, -you will need to replace the *RichTextValue* object with a new one if any +you will need to replace the ``RichTextValue`` object with a new one if any of the transformation parameters change. However, as we will see below, it is possible to apply a different transformation on demand should you need to. -The code snippet belows shows how a *RichTextValue* object can be +The code snippet belows shows how a ``RichTextValue`` object can be constructed in code. In this case, we have a raw input string of type -*text/plain* that will be transformed to a default output of -*text/html*. (Note that we would normally look up the default output -type from the field instance.) - -:: +``text/plain`` that will be transformed to a default output of +``text/html``. (Note that we would normally look up the default output +type from the field instance.):: from plone.app.textfield.value import RichTextValue - ... context.body = RichTextValue(u"Some input text", 'text/plain', 'text/html') -Of course, the standard widget used for a *RichText* field will +Of course, the standard widget used for a ``RichText`` field will correctly store this type of object for you, so it is rarely necessary to create one yourself. @@ -138,65 +131,61 @@ Using rich text fields in templates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ What about using the text field in a template? If you are using a -*DisplayForm*, the display widget for the *RichText* field will render +``DisplayForm``, the display widget for the ``RichText`` field will render the transformed output markup automatically. If you are writing TAL manually, you may try something like this: -:: +.. code-block:: html
-This, however, will render a string like: - -:: +This, however, will render a string like:: RichTextValue object. (Did you mean .raw or .output?) The correct syntax is: -:: +.. code-block:: html
-This will rendred the cached, transformed output. This operation is -approximately as efficient as rendering a simple *Text* field, since the +This will render the cached, transformed output. This operation is +approximately as efficient as rendering a simple ``Text`` field, since the transformation is only applied once, when the value is first saved. Alternative transformations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes, you may want to invoke alternative transformations. Under the -hood, the default implementation uses the *portal\_transforms* tool to +hood, the default implementation uses the ``portal_transforms`` tool to calculate a transform chain from the raw value’s input MIME type to the desired output MIME type. (Should you need to write your own transforms, take a look at `this tutorial`_.) This is abstracted behind an -*ITransformer* adapter to allow alternative implementations. - -To invoke a transformation in code, you can use the following syntax: +``ITransformer`` adapter to allow alternative implementations. -:: +To invoke a transformation in code, you can use the following syntax:: from plone.app.textfield.interfaces import ITransformer transformer = ITransformer(context) transformedValue = transformer(context.body, 'text/plain') -The *\_\_call\_\_()* method of the ITransformer adapter takes a -*RichTextValue* object and an output MIME type as parameters. +The ``__call__()`` method of the ``ITransformer`` adapter takes a +``RichTextValue`` object and an output MIME type as parameters. If you are writing a page template, there is an even more convenient syntax: -:: +.. code-block:: html
The first traversal name gives the name of the field on the context -(*body* in this case). The second and third give the output MIME type. +(``body`` in this case). The second and third give the output MIME type. If the MIME type is omitted, the default output MIME type will be used. .. note:: - Unlike the *output* property, the value is not cached, and so + Unlike the ``output`` property, the value is not cached, and so will be calculated each time the page is rendered. .. _this tutorial: ../../../../../../documentation/tutorial/portal-transforms diff --git a/source/advanced/static-resources.txt b/source/advanced/static-resources.txt index 4435de4..0b8f5a0 100644 --- a/source/advanced/static-resources.txt +++ b/source/advanced/static-resources.txt @@ -12,36 +12,35 @@ Registering a static resource directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The easiest way to manage static resources is to make use of the -*static* resource directory feature in *five.grok*. Simply add a -directory called *static* in the package and make sure that the -** line appears in *configure.zcml*. - -If a *static* resource directory in the *example.conference* package -contains a file called *conference.css*, it will be accessible on a URL -like -*http:///site/++resource++example.conference/conference.css.* -The resource name is the same as the package name wherein the *static* +``static`` resource directory feature in `five.grok`_. Simply add a +directory called ``static`` in the package and make sure that the +```` line appears in ``configure.zcml``. + +If a ``static`` resource directory in the ``example.conference`` package +contains a file called ``conference.css``, it will be accessible on a URL +like ``http:///site/++resource++example.conference/conference.css.`` +The resource name is the same as the package name wherein the ``static`` directory appears. .. note:: If you need to register additional directories, you can do so using the - ZCML directive. This requires two - attributes: *name* is the name that appears after the - *++resource++* namespace; *directory* is a relative path to the + ```` ZCML directive. This requires two + attributes: ``name`` is the name that appears after the + ``++resource++`` namespace; ``directory`` is a relative path to the directory containing resources. Importing CSS and JavaScript files in templates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ One common use of static resources is to add a static CSS or JavaScript -file to a specific template. We can do this by filling the *style\_slot* -or *javascript\_slot* in Plone’s *main\_template* in our own view +file to a specific template. We can do this by filling the ``style_slot`` +or ``javascript_slot`` in Plone’s ``main_template`` in our own view template and using an appropriate resource link. For example, we could add the following near the top of -*presenter\_templates/view.pt*: +``presenter_templates/view.pt``: -:: +.. code-block:: html @@ -61,24 +60,24 @@ Registering resources with Plone’s resource registries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes it is more appropriate to register a stylesheet with Plone’s -*portal\_css* registry (or a JavaScript file with -*portal\_javascripts*), rather than add the registration on a +``portal_css`` registry (or a JavaScript file with +``portal_javascripts``), rather than add the registration on a per-template basis. This ensures that the resource is available site-wide. .. note:: It may seem wasteful to include a resource that is not be used on all - pages in the global registry. Remember, however, that *portal\_css* and - *portal\_javascripts* will merge and compress resources, and set caching + pages in the global registry. Remember, however, that ``portal_css`` and + ``portal_javascripts`` will merge and compress resources, and set caching headers such that browsers and caching proxies can cache resources well. It is often more effective to have one slightly larger file that caches well, than to have a variable number of files that may need to be loaded at different times. To add a static resource file, you can use the GenericSetup -*cssregistry.xml* or *jsregistry.xml* import steps in the -*profiles/default* directory. For example, an import step to add the -*conference.css* file site-wide may involve a *cssregistry.xml* file +``cssregistry.xml`` or ``jsregistry.xml`` import steps in the +``profiles/default`` directory. For example, an import step to add the +``conference.css`` file site-wide may involve a ``cssregistry.xml`` file that looks like this: .. code-block:: xml @@ -92,7 +91,7 @@ that looks like this: Similarly, a JavaScript resource could be imported with a -*jsregistry.xml* like: +``jsregistry.xml`` like: .. code-block:: xml @@ -107,26 +106,27 @@ Image resources ~~~~~~~~~~~~~~~~ Images can be added to resource directories just like any other type of -resource. To use the image in a view, you can construct an ** tag +resource. To use the image in a view, you can construct an ```` tag like this: -:: +.. code-block:: html - + Content type icons ~~~~~~~~~~~~~~~~~~~ Finally, to use an image resource as the icon for a content type, simply -list it in the FTI under the *content\_icon* property. For example, in -*profiles/default/types/example.conference.presenter.xml*, we can use -the following line, presuming we have a *presenter.gif* in the *static* +list it in the FTI under the ``content_icon`` property. For example, in +``profiles/default/types/example.conference.presenter.xml``, we can use +the following line, presuming we have a ``presenter.gif`` in the ``static`` directory: .. code-block:: xml ++resource++example.conference/presenter.gif +.. _five.grok: http://pypi.python.org/pypi/five.grok diff --git a/source/advanced/validators.txt b/source/advanced/validators.txt index ccc6e98..0cd5a79 100644 --- a/source/advanced/validators.txt +++ b/source/advanced/validators.txt @@ -4,7 +4,7 @@ Validators **Creating custom validators for your type** Many applications require some form of data entry validation. The -simplest form of validation you get for free – the *z3c.form* library +simplest form of validation you get for free – the `z3c.form`_ library ensures that all data entered on Dexterity add and edit forms is valid for the field type. @@ -12,17 +12,21 @@ It is also possible to set certain properties on the fields to add further validation (or even create your own fields with custom validation logic, although that is a lot less common). These properties are set as parameters to the field constructor when the schema interface -is created. You should see the *zope.schema* package for details, but +is created. You should see the `zope.schema`_ package for details, but the most common constraints are: -- *required=True/False*, to make a field required or optional -- *min* and *max*, used for *Int*, *Float*, *Datetime*, *Date*, and - *Timedelta* fields, specify the minimum and maximum (inclusive) - allowed values of the given type -- *min\_length* and *max\_length*, used for collection fields *(Tuple, - List, Set, Frozenset, Dict)* and text fields *(Bytes, BytesLine, - ASCII, ASCIILine, Text, TextLine)*, set the minimum and maximum - (inclusive) length of a field +- ``required=True/False``, to make a field required or optional; +- ``min`` and ``max``, used for ``Int``, ``Float``, ``Datetime``, + ``Date``, and ``Timedelta`` fields, specify the minimum and maximum + (inclusive) allowed values of the given type +- ``min_length`` and ``max_length``, used for collection fields + (``Tuple``, ``List``, ``Set``, ``Frozenset``, ``Dict``) and text + fields (``Bytes``, ``BytesLine``, ``ASCII``, ``ASCIILine``, ``Text``, + ``TextLine``), set the minimum and maximum (inclusive) length of a + field. + +.. _zope.schema: http://pypi.python.org/pypi/zope.schema + Constraints ~~~~~~~~~~~ @@ -30,7 +34,7 @@ Constraints If this does not suffice, you can pass your own constraint function to a field. The constraint function should take a single argument: the value that is to be validated. This will be of the field’s type. The function -should return a boolean True or False. +should return a boolean ``True`` or ``False``. :: @@ -41,19 +45,17 @@ should return a boolean True or False. Hint: The constraint function does not have access to the context, but if you need to acquire a tool, you can use the - *zope.app.component.hooks.getSite()* method to obtain the site root - -To use the constraint, pass the function as the *constraint* argument to -the field constructor, e.g.: + ``zope.app.component.hooks.getSite()`` method to obtain the site root -:: +To use the constraint, pass the function as the ``constraint`` argument to +the field constructor, e.g.:: - my_field = schema.TextLine(title=_(u"My field"), constraint=checkForMagic) + my_field = schema.TextLine(title=_(u"My field"), constraint=checkForMagic) Constraints are easy to write, but do not necessarily produce very friendly error messages. It is however possible to customise these error -messages using *z3c.form* error view snippets. See the `z3c.form -documentation `_ for more details. +messages using `z3c.form`_ error view snippets. See the `z3c.form +documentation `_ for more details. Invariants ~~~~~~~~~~~~ @@ -64,10 +66,8 @@ an invariant. Invariants use exceptions to signal errors, which are displayed at the top of the form rather than next to a particular field. To illustrate an invariant, let’s make sure that the start date of a -*Program* is before the end date. In *program.py*, we add the following. -Code not relevant to this example is snipped with an ellipsis (…): - -:: +`Program` is before the end date. In `program.py`, we add the following. +Code not relevant to this example is snipped with an ellipsis (…):: ... @@ -103,6 +103,8 @@ Code not relevant to this example is snipped with an ellipsis (…): Form validators ~~~~~~~~~~~~~~~ -Finally, you can write more powerful validators by using the *z3c.form* -widget validators. See `the z3c.form documentation for details `_. +Finally, you can write more powerful validators by using the `z3c.form`_ +widget validators. See :ref:`the z3c.form documentation `_ for +details. +.. _z3c.form: http://pypi.python.org/pypi/z3c.form diff --git a/source/advanced/vocabularies.txt b/source/advanced/vocabularies.txt index aed174a..2ec3e6c 100644 --- a/source/advanced/vocabularies.txt +++ b/source/advanced/vocabularies.txt @@ -4,33 +4,29 @@ Vocabularies **Creating your own static and dynamic vocabularies** Vocabularies are normally used in conjunction with selection fields, and -are supported by the *zope.schema* package, with widgets provided by -*z3c.form*. +are supported by the `zope.schema`_ package, with widgets provided by +`z3c.form`_. -Selection fields use the *Choice* field type. To allow the user to -select a single value, use a *Choice* field directly: - -:: +Selection fields use the ``Choice`` field type. To allow the user to +select a single value, use a ``Choice`` field directly:: class IMySchema(form.Schema): myChoice = schema.Choice(...) -For a multi-select field, use a *List*, *Tuple, Set* or *Frozenset* with -a *Choice* as the *value\_type*: - -:: +For a multi-select field, use a ``List``, ``Tuple``, ``Set`` or +``Frozenset`` with a ``Choice`` as the ``value_type``:: class IMySchema(form.Schema): myList = schema.List(..., value_type=schema.Choice(...)) The choice field must be passed one of the following arguments: -- *values* can be used to give a list of static values -- *source* can be used to refer to an *IContextSourceBinder* or - *ISource* instance -- *vocabulary* can be used to refer to an *IVocabulary* instance or - (more commonly) a string giving the name of an - *IVocabularyFactory* named utility. +- ``values`` can be used to give a list of static values; +- ``source`` can be used to refer to an ``IContextSourceBinder`` or + ``ISource`` instance; +- ``vocabulary`` can be used to refer to an ``IVocabulary`` instance or + (more commonly) a string giving the name of an ``IVocabularyFactory`` + named utility. In the remainder of this section, we will show the various techniques for defining vocabularies through several iterations of a new field @@ -42,10 +38,8 @@ Static vocabularies Our first attempt uses a static list of organisers. We use the message factory to allow the labels (term titles) to be translated. The values -stored in the *organizer* field will be a unicode string representing -the chosen label, or *None* if no value is selected. - -:: +stored in the ``organizer`` field will be a unicode object representing +the chosen label, or ``None`` if no value is selected:: from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm @@ -61,8 +55,8 @@ the chosen label, or *None* if no value is selected. required=False, ) -Since *required* is *False*, there will be a "*no value*" option in the -drop-down list. +Since ``required`` is ``False``, there will be a :guilabel:`no value` option +in the drop-down list. Dynamic sources ~~~~~~~~~~~~~~~~ @@ -73,17 +67,15 @@ values and the labels shown in the selection widget. We can make a one-off dynamic vocabulary using a context source binder. This is simply a callable (usually a function or an object with a -*\_\_call\_\_* method) that provides the *IContextSourceBinder* -interface and takes a *context* parameter. The *context* argument is the +``__call__`` method) that provides the ``IContextSourceBinder`` +interface and takes a ``context`` parameter. The ``context`` argument is the context of the form (i.e. the folder on an add form, and the content object on an edit form). The callable should return a vocabulary, which -is most easily achieved by using the *SimpleVocabulary* class from -*zope.schema*. +is most easily achieved by using the ``SimpleVocabulary`` class from +`zope.schema`_. Here is an example using a function to return all users in a particular -group. - -:: +group:: from zope.schema.interfaces import IContextSourceBinder from zope.schema.vocabulary import SimpleVocabulary @@ -110,57 +102,53 @@ which we then turn into a vocabulary. When working with vocabularies, you’ll come across some terminology that is worth explaining: -- A *term* is an entry in the vocabulary. The term has a value. Most - terms are *tokenised* terms which also have a token, and some terms - are *titled*, meaning they have a title that is different to the - token. -- The *token* must be an ASCII string. It is the value passed with the - request when the form is submitted. A token must uniquely identify a - term. -- The *value* is the actual value stored on the object. This is not - passed to the browser or used in the form. The value is often a - unicode string, but can be any type of object. -- The *title* is a unicode string or translatable message. It is used - in the form. - -The *SimpleVocabulary* class contains two class methods that can be used +- A *term* is an entry in the vocabulary. The term has a value. Most + terms are *tokenised* terms which also have a token, and some terms + are *titled*, meaning they have a title that is different to the + token. +- The *token* must be an ASCII string. It is the value passed with the + request when the form is submitted. A token must uniquely identify a + term. +- The *value* is the actual value stored on the object. This is not + passed to the browser or used in the form. The value is often a + unicode object, but can be any type of object. +- The *title* is a unicode object or translatable message. It is used + in the form. + +The ``SimpleVocabulary`` class contains two class methods that can be used to create vocabularies from lists: -- *fromValues()* takes a simple list of values and returns a tokenised - vocabulary where the values are the items in the list, and the tokens - are created by calling *str()* on the values. -- *fromItems()* takes a list of *(token, value)* tuples and creates a - tokenised vocabulary with the token and value specified. +- ``fromValues()`` takes a simple list of values and returns a tokenised + vocabulary where the values are the items in the list, and the tokens + are created by calling ``str()`` on the values. +- ``fromItems()`` takes a list of ``(token, value)`` tuples and creates a + tokenised vocabulary with the token and value specified. -You can also instantiate a *SimpleVocabulary* yourself and pass a list -of terms in the initialiser. The *createTerm()* class method can be used -to create a term from a *value*, *token* and *title*. Only the value is -required. +You can also instantiate a ``SimpleVocabulary`` yourself and pass a list +of terms in the initialiser. The ``createTerm()`` class method can be used +to create a term from a ``value``, ``token`` and ``title``. Only the value +is required. -In the example above, we have chosen to create a *SimpleVocabulary* from +In the example above, we have chosen to create a ``SimpleVocabulary`` from terms with the user id used as value and token, and the user’s full name as a title. -To use this context source binder, we use the *source* argument to the -*Choice* constructor: - -:: +To use this context source binder, we use the ``source`` argument to the +``Choice`` constructor:: - organizer = schema.Choice( - title=_(u"Organiser"), - source=possibleOrganizers, - required=False, - ) + organizer = schema.Choice( + title=_(u"Organiser"), + source=possibleOrganizers, + required=False, + ) Parameterised sources ~~~~~~~~~~~~~~~~~~~~~~ We can improve this example by moving the group name out of the function, allowing it to be set on a per-field basis. To do so, we turn -our *IContextSourceBinder* into a class that is initialised with the -group name. - -:: +our ``IContextSourceBinder`` into a class that is initialised with the +group name:: class GroupMembers(object): """Context source binder to provide a vocabulary of users in a given @@ -186,21 +174,19 @@ group name. return SimpleVocabulary(terms) -Again, the source is set using the *source* argument to the *Choice* -constructor: +Again, the source is set using the ``source`` argument to the ``Choice`` +constructor:: -:: - - organizer = schema.Choice( - title=_(u"Organiser"), - source=GroupMembers('organizers'), - required=False, - ) + organizer = schema.Choice( + title=_(u"Organiser"), + source=GroupMembers('organizers'), + required=False, + ) -When the schema is initialised on startup, the a *GroupMembers* object +When the schema is initialised on startup, a ``GroupMembers`` object is instantiated, storing the desired group name. Each time the vocabulary is needed, this object will be called (i.e. the -*\_\_call\_\_()* method is invoked) with the context as an argument, +``__call__()`` method is invoked) with the context as an argument, expected to return an appropriate vocabulary. Named vocabularies @@ -220,17 +206,14 @@ distribute vocabularies in third party packages. .. note:: Named vocabularies cannot be parameterised in the way as we did - with the *GroupMembers* context source binder, since they are looked up + with the ``GroupMembers`` context source binder, since they are looked up by name only. We can turn our first "members in the *organizers* group" vocabulary into a named vocabulary by creating a named utility providing -*IVocabularyFactory*, like so: - -:: +``IVocabularyFactory``, like so:: from zope.schema.interfaces import IVocabularyFactory - ... class OrganizersVocabulary(object): @@ -258,46 +241,48 @@ into a named vocabulary by creating a named utility providing ensure uniqueness. We can make use of this vocabulary in any schema by passing its name to -the *vocabulary* argument of the *Choice* field constructor: +the ``vocabulary`` argument of the ``Choice`` field constructor:: -:: - - organizer = schema.Choice( - title=_(u"Organiser"), - vocabulary=u"example.conference.Organizers", - required=False, - ) + organizer = schema.Choice( + title=_(u"Organiser"), + vocabulary=u"example.conference.Organizers", + required=False, + ) Some common vocabularies ~~~~~~~~~~~~~~~~~~~~~~~~ As you might expect, there are a number of standard vocabularies that -come with Plone. These are found in the *plone.app.vocabularies* -package. Some of the more useful ones include - -- *plone.app.vocabularies.AvailableContentLanguages*, a list of all - available content languages -- *plone.app.vocabularies.SupportedContentLanguages*, a list of - currently supported content languages -- *plone.app.vocabularies.Roles*, the user roles available in the site -- *plone.app.vocabularies.PortalTypes*, a list of types installed in - *portal\_types* -- *plone.app.vocabularies.ReallyUserFriendlyTypes*, a list of those - types that are likely to mean something to users -- *plone.app.vocabularies.Workflows*, a list of workflows -- *plone.app.vocabularies.WorkflowStates*, a list of all states from - all workflows -- *plone.app.vocabularies.WorkflowTransitions*, a list of all - transitions from all workflows - -In addition, the package *plone.principalsource* provides several +come with Plone. These are found in the `plone.app.vocabularies`_ +package. Some of the more useful ones include: + +- ``plone.app.vocabularies.AvailableContentLanguages``, a list of all + available content languages; +- ``plone.app.vocabularies.SupportedContentLanguages``, a list of + currently supported content languages; +- ``plone.app.vocabularies.Roles``, the user roles available in the site; +- ``plone.app.vocabularies.PortalTypes``, a list of types installed in + ``portal_types``; +- ``plone.app.vocabularies.ReallyUserFriendlyTypes``, a list of those + types that are likely to mean something to users; +- ``plone.app.vocabularies.Workflows``, a list of workflows; +- ``plone.app.vocabularies.WorkflowStates``, a list of all states from + all workflows; +- ``plone.app.vocabularies.WorkflowTransitions``, a list of all + transitions from all workflows. + +In addition, the package `plone.principalsource`_ provides several vocabularies that are useful for selecting users and groups in a Dexterity context: -- *plone.principalsource.Users* provides users -- *plone.principalsource.Groups* provides groups -- plone.principalsource.Principals provides security principals (users - or groups) +``plone.principalsource.Users`` + provides users + +``plone.principalsource.Groups`` + provides groups + +``plone.principalsource.Principals`` + provides security principals (users or groups) Importantly, these sources are not iterable, which means that you cannot use them to provide a list of all users in the site. This is @@ -307,74 +292,72 @@ LDAP or Active Directory. Instead, you should use a search-based source such as one of these. We will use one of these together with an auto-complete widget to -finalise our *organizer* field. To do so, we need to add -*plone.principalsource* as a dependency of *example.conference*. In -*setup.py*, we add: - -:: +finalise our ``organizer`` field. To do so, we need to add +``plone.principalsource`` as a dependency of ``example.conference``. In +``setup.py``, we add:: - install_requires=[ - ... - 'plone.principalsource', - ], + install_requires=[ + ... + 'plone.principalsource', + ], .. note:: - Since we use an line in configure.zcml, we do - not need a separate line in configure.zcml for this new - dependency. - -The *organizer* field now looks like: + Since we use an ```` line in ``configure.zcml``, + we do not need a separate ```` line in ``configure.zcml`` for + this new dependency. -:: +The ``organizer`` field now looks like:: - organizer = schema.Choice( - title=_(u"Organiser"), - vocabulary=u"plone.principalsource.Users", - required=False, - ) + organizer = schema.Choice( + title=_(u"Organiser"), + vocabulary=u"plone.principalsource.Users", + required=False, + ) The autocomplete selection widget ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The *organizer* field now has a query-based source. The standard +The ``organizer`` field now has a query-based source. The standard selection widget (a drop-down list) is not capable of rendering such a source. Instead, we need to use a more powerful widget. For a basic widget, see `z3c.formwidget.query`_, but in a Plone context, you will more likely want to use `plone.formwidget.autocomplete`_, which extends -*z3c.formwidget.query* to provide friendlier user interface. +``z3c.formwidget.query`` to provide friendlier user interface. -The widget is provided with *plone.app.dexterity*, so we do not need to +The widget is provided with `plone.app.dexterity`_, so we do not need to configure it ourselves. We only need to tell Dexterity to use this widget instead of the default, using a form widget hint as shown -earlier. At the top of *program.py*, we add the following import: - -:: +earlier. At the top of ``program.py``, we add the following import:: from plone.formwidget.autocomplete import AutocompleteFieldWidget .. note:: - If we were using a multi-valued field, such as a *List* with a *Choice* - *value\_type*, we would use the *AutocompleteMultiFieldWidget* instead + If we were using a multi-valued field, such as a ``List`` with a + ``Choice`` ``value_type``, we would use the + ``AutocompleteMultiFieldWidget`` instead. -In the *IProgram* schema (which, recall, derives from *form.Schema* and +In the ``IProgram`` schema (which, recall, derives from ``form.Schema`` and is therefore processed for form hints at startup), we then add the -following: - -:: +following:: - form.widget(organizer=AutocompleteFieldWidget) - organizer = schema.Choice( - title=_(u"Organiser"), - vocabulary=u"plone.principalsource.Users", - required=False, - ) + form.widget(organizer=AutocompleteFieldWidget) + organizer = schema.Choice( + title=_(u"Organiser"), + vocabulary=u"plone.principalsource.Users", + required=False, + ) You should now see a dynamic auto-complete widget on the form, so long as you have JavaScript enabled. Start typing a user name and see what happens. The widget also has fall-back for non-JavaScript capable browsers. +.. _plone.app.dexterity: http://pypi.python.org/pypi/plone.app.dexterity +.. _plone.principalsource: http://pypi.python.org/pypi/plone.principalsource +.. _plone.app.vocabularies: http://pypi.python.org/pypi/plone.app.vocabularies +.. _z3c.form: http://pypi.python.org/pypi/z3c.form +.. _zope.schema: http://pypi.python.org/pypi/zope.schema .. _z3c.formwidget.query: http://pypi.python.org/pypi/z3c.formwidget.query .. _plone.formwidget.autocomplete: http://pypi.python.org/pypi/plone.formwidget.autocomplete diff --git a/source/advanced/webdav-and-other-file-representations.txt b/source/advanced/webdav-and-other-file-representations.txt index 267bf88..504e227 100644 --- a/source/advanced/webdav-and-other-file-representations.txt +++ b/source/advanced/webdav-and-other-file-representations.txt @@ -12,9 +12,9 @@ locations from various desktop programs. In addition, WebDAV powers the program from within Plone to edit a content object. To configure a WebDAV server, you can add the following option to the -*[instance]* section of your *buildout.cfg* and re-run buildout. +``[instance]`` section of your ``buildout.cfg`` and re-run buildout. -:: +.. code-block:: ini webdav-address = 9800 @@ -23,11 +23,15 @@ When Zope is started, you should now be able to mount it as a WebDAV server on the given port. Most operating systems support mounting WebDAV servers as folders. -Unfortunately, not all WebDAV implementations are very good. Dexterity -content should work with Windows Web Folders (open Internet Explorer, go -to File \| Open, type in a WebDAV address, e.g. http://localhost:9800, -and then select “Open as web folder” before hitting OK) and well-behaved -clients such as Novell NetDrive. +Unfortunately, not all WebDAV implementations are very good. +Dexterity content should work with Windows Web Folders [*]_ +and well-behaved clients such as Novell NetDrive. + +.. [*] open Internet Explorer, + go to :guilabel:`File | Open`, + type in a WebDAV address, e.g. http://localhost:9800, + and then select :guilabel:`Open as web folder` before hitting + :guilabel:`OK` On Mac OS X, the Finder claims to support WebDAV, but the implementation is so flakey that it is just as likely to crash Mac OS X as it is to let @@ -38,15 +42,14 @@ Default WebDAV behaviour ------------------------ By default, Dexterity content can be downloaded and uploaded using a -text format based on RFC (2)822, the same standard used to encode email -messages. Most fields are encoded in headers, whilst the field marked as -“primary” will be contained in the body of the message. If there is more -than one primary field, a multi-part message is created. - -A field can be marked as “primary” using the *primary()* directive from -*plone.directives.form*. For example: +text format based on :RFC:`2822`, the same standard used to encode email +messages. +Most fields are encoded in headers, whilst the field marked as “primary” +will be contained in the body of the message. +If there is more than one primary field, a multi-part message is created. -:: +A field can be marked as “primary” using the ``primary()`` directive from +`plone.directives.form`_. For example:: class ISession(form.Schema): """A conference session. Sessions are managed inside Programs. @@ -81,12 +84,10 @@ A field can be marked as “primary” using the *primary()* directive from required=False, ) -This will actually apply the *IPrimaryField* marker interface from the -*plone.rfc822* package to the given field(s). - -A WebDAV download of this content item will by default look like this: +This will actually apply the ``IPrimaryField`` marker interface from the +`plone.rfc822`_ package to the given field(s). -:: +A WebDAV download of this content item will by default look like this:: title: Test session description: First session @@ -98,34 +99,36 @@ A WebDAV download of this content item will by default look like this:

Details here

-Notice how most fields are encoded as header strings. The *presenter* -relation field stores a number, which is the integer id of the target -object. Note that this id is generated when the content object is -created, and so is unlikely to be valid on a different site. The -*details* field, which we marked as primary, is encoded in the body of -the message. +Notice how most fields are encoded as header strings. +The ``presenter`` relation field stores a number, +which is the integer id of the target object. +Note that this id is generated when the content object is created, +and so is unlikely to be valid on a different site. +The ``details`` field, which we marked as primary, +is encoded in the body of the message. -It is also possible to upload such a file to create a new session. In -order to do that, the *content\_type\_registry* tool needs to be +It is also possible to upload such a file to create a new session. +In order to do that, the ``content_type_registry`` tool needs to be configured with a predicate that can detect the type of content from the -uploaded file and instantiate the correct type of object. Such -predicates could be based on an extension or a filename pattern. Below, -we will see a different approach that uses a custom “file factory” for -the containing *Program* type. +uploaded file and instantiate the correct type of object. +Such predicates could be based on an extension or a filename pattern. +Below, we will see a different approach that uses a custom “file factory” +for the containing ``Program`` type. Containers ~~~~~~~~~~ -Container objects will be shown as collections (WebDAV-speak for -folders) for WebDAV purposes. This allows the WebDAV client to open the -container and list its contents. However, representing containers as -collections makes it impossible to access the data contained in the -various fields of the content object. - -To allow access to this information, a pseudo-file called *\_data* will -be exposed inside a Dexterity container. This file can be read and -written like any other, to access or modify the container’s data. It -cannot be copied, moved, renamed or deleted: those operations should be +Container objects will be shown as *collections* (WebDAV-speak for +folders) for WebDAV purposes. +This allows the WebDAV client to open the container and list its contents. +However, representing containers as collections makes it impossible to +access the data contained in the various fields of the content object. + +To allow access to this information, a pseudo-file called ``_data`` will +be exposed inside a Dexterity container. +This file can be read and written like any other, +to access or modify the container’s data. +It cannot be copied, moved, renamed or deleted: those operations should be performed on the container itself. Customising WebDAV behaviour @@ -134,43 +137,40 @@ Customising WebDAV behaviour There are several ways in which you can influence the WebDAV behaviour of your type. -- If you are happy with the RFC 2822 format, you can provide your own - *plone.rfc822.interfaces.IFieldMarshaler* adapters to provide - alternate serialisations and parsers for fields. See the - `plone.rfc822 documentation`_ for details. -- If you want to use a different file representation, you can provide - your own *IRawReadFile* and *IRawWriteFile* adapters. For example, if - you have a content object that stores binary data, you could return - this data directly, with an appropriate MIME type, to allow it to be - edited in a desktop program (e.g. an image editor if the MIME type is - *image/jpeg*). The file *plone.dexterity.filerepresentation* contains - two base classes, *ReadFileBase* and *WriteFileBase,* which you may - be able to use to make it easier to implement these interfaces. -- If you want to control how content objects are created when a new - file or directory is dropped into a particular type of container, you - can provide your own *IFileFactory* or *IDirectoryFactory* adapters. - See *plone.dexterity.filerepresentation* for the default - implementations. - -As an example, let’s register a custom *IFileFactory* adapter for the -*IProgram* type. This adapter will not rely on the -*content\_type\_registry* tool to determine which type to construct, but -will instead create a *Session* object, since that is the only type that -is allowed inside a *Program* container. - -The code, in *program.py*, looks like this: - -:: +- If you are happy with the :RFC:`2822` format, you can provide your own + ``plone.rfc822.interfaces.IFieldMarshaler`` adapters to provide + alternate serialisations and parsers for fields. + See the `plone.rfc822`_ documentation for details. +- If you want to use a different file representation, you can provide + your own ``IRawReadFile`` and ``IRawWriteFile`` adapters. + For example, if you have a content object that stores binary data, + you could return this data directly, with an appropriate MIME type, to + allow it to be edited in a desktop program + (e.g. an image editor if the MIME type is ``image/jpeg``). + The file ``plone.dexterity.filerepresentation`` contains + two base classes, ``ReadFileBase`` and ``WriteFileBase``, which you may + be able to use to make it easier to implement these interfaces. +- If you want to control how content objects are created when a new + file or directory is dropped into a particular type of container, you + can provide your own ``IFileFactory`` or ``IDirectoryFactory`` adapters. + See `plone.dexterity.filerepresentation`_ for the default implementations. + +As an example, let’s register a custom ``IFileFactory`` adapter for the +``IProgram`` type. +This adapter will not rely on the ``content_type_registry`` tool to +determine which type to construct, +but will instead create a ``Session`` object, +since that is the only type that is allowed inside a ``Program`` container. + +The code, in ``program.py``, looks like this:: from five import grok - ... from zope.component import createObject from zope.event import notify from zope.lifecycleevent import ObjectCreatedEvent from zope.filerepresentation.interfaces import IFileFactory - ... class ProgramFileFactory(grok.Adapter): @@ -185,18 +185,16 @@ The code, in *program.py*, looks like this: notify(ObjectCreatedEvent(session)) return session -This adapter overrides the *DefaultFileFactory* found in -*plone.dexterity.filerepresentation*. It creates an object of the -designated type, fires an *IObjectModifiedEvent* and then returns the +This adapter overrides the ``DefaultFileFactory`` found in +`plone.dexterity.filerepresentation`_. It creates an object of the +designated type, fires an ``IObjectModifiedEvent`` and then returns the object, which will then be populated with data from the uploaded file. To test this, you could write a text file like the one shown above in a text editor and save it on your desktop, then drag it into the folder in -your WebDAV client representing a *Program*. +your WebDAV client representing a ``Program``. -Here is a simple automated integration test for the same component: - -:: +Here is a simple automated integration test for the same component:: def test_file_factory(self): self.folder.invokeFactory('example.conference.program', 'p1') @@ -209,299 +207,345 @@ How it all works ---------------- The rest of this section describes in some detail how the various WebDAV -related components interact in Zope 2, CMF and Dexterity. This may be -helpful if you are trying to customise or debug WebDAV behaviour. +related components interact in Zope 2, CMF and Dexterity. +This may be helpful if you are trying to customise or debug WebDAV behaviour. Background ~~~~~~~~~~ -Basic WebDAV support can be found in the *webdav* package. This defines -two base classes, *webdav.Resource.Resource* and -*webdav.Collection.Collection*. *Collection* extends *Resource*. These -are mixed into item and container content objects, respectively. +Basic WebDAV support can be found in the ``webdav`` package. +This defines two base classes, ``webdav.Resource.Resource`` and +``webdav.Collection.Collection``. +``Collection`` extends ``Resource``. +These are mixed into *item* and *container* content objects, respectively. -The webdav package also defines the *NullResource* object. A -*NullResource* is a kind of placeholder, which supports the HTTP verbs -HEAD, PUT, and MKCOL. +The webdav package also defines the ``NullResource`` object. +A ``NullResource`` is a kind of placeholder, +which supports the HTTP verbs ``HEAD``, ``PUT``, and ``MKCOL``. -Contains based on *ObjectManager* (including those in Dexterity) will -return a *NullResource* if they cannot find the requested object and the +Contents based on ``ObjectManager`` (including those in Dexterity) will +return a ``NullResource`` if they cannot find the requested object and the request is a WebDAV request. -The *zope.filerepresentation* package defines a number of interfaces +The `zope.filerepresentation`_ package defines a number of interfaces which are intended to help manage file representations of content -objects. Dexterity uses these interfaces to allow the exact file read -and write operations to be overridden without subclassing. +objects. +Dexterity uses these interfaces to allow the exact file read and write +operations to be overridden without subclassing. -HEAD -~~~~ +``HEAD`` +~~~~~~~~~ -A HEAD request retrieves headers only. +A ``HEAD`` request retrieves headers only. -*Resource.HEAD()* sets Content-Type based on *self.content\_type()*, -*Content-Length* based on *self.get\_size()*, *Last-Modified* based on -*self.\_p\_mtime*, and an ETag based on *self.http\_\_etag()*, if -available. +``Resource.HEAD()`` sets +``Content-Type`` based on ``self.content_type()``, +``Content-Length`` based on ``self.get\_size()``, +``Last-Modified`` based on ``self._p_mtime``, +and an ``ETag`` based on ``self.http__etag()``, if available. -*Collection.HEAD()* looks for *self.index\_html.HEAD()* and returns its -value if that exists. Otherwise, it returns a 405 Method Not Allowed -response. If there is no *index\_html* object, it returns 404 Not Found. +``Collection.HEAD()`` looks for ``self.index_html.HEAD()`` and returns its +value if that exists. +Otherwise, it returns a "405 Method Not Allowed" response. If there is no +``index_html`` object, it returns "404 Not Found". -GET -~~~ +``GET`` +~~~~~~~~ -A GET request retrieves headers and body. +A ``GET`` request retrieves headers and body. -Zope calls *manage\_DAVget()* to retrieve the body. The default -implementation calls *manage\_FTPget()*. +Zope calls ``manage_DAVget()`` to retrieve the body. +The default implementation calls ``manage_FTPget()``. -In Dexterity, *manage\_FTPget()* adapts *self* to *IRawReadFile* and -uses its *mimeType* and *encoding* properties to set the *Content-Type* -header, and its *size()* method to set *Content-Length*. +In Dexterity, ``manage_FTPget()`` adapts ``self`` to ``IRawReadFile`` and +uses its ``mimeType`` and ``encoding`` properties to set the ``Content-Type`` +header, and its ``size()`` method to set ``Content-Length``. -If the *IRawReadFile* adapter is also an *IStreamIterator*, it will be -returned for the publisher to consume directly. This provides for -efficient serving of large files, although it does require that the file -can be read in its entirety with the ZODB connection closed. Dexterity -solves this problem by writing the file content to a temporary file on -the server. +If the ``IRawReadFile`` adapter is also an ``IStreamIterator``, +it will be returned for the publisher to consume directly. +This provides for efficient serving of large files, +although it does require that the file can be read in its entirety with the +ZODB connection closed. +Dexterity solves this problem by writing the file content to a temporary +file on the server. -If the *IRawReadFile* adapter is not a stream iterator, its contents are -returned as a string, by calling its *read()* method. Note that this -loads the entire file contents into memory on the server. +If the ``IRawReadFile`` adapter is not a stream iterator, its contents are +returned as a string, by calling its ``read()`` method. +Note that this loads the entire file contents into memory on the server. -The default *IRawReadFile* implementation for Dexterity content returns -an RFC 2822 style message document. Most fields on the object and any -enabled behaviours will be turned into UTF–8 encoded headers. The -primary field, if any, will be returned in the body, also most likely -encoded as an UTF–8 encoded string. Binary data may be base64 encoded -instead. +The default ``IRawReadFile`` implementation for Dexterity content returns +an :RFC:`2822`-style message document. +Most fields on the object and any enabled behaviours will be turned into +UTF-8 encoded headers. +The primary field, if any, will be returned in the body, also most likely +encoded as an UTF-8 encoded string. +Binary data may be base64-encoded instead. -A type which wishes to override this behaviour can provide its own -adapter. For example, an image type could return the raw image data. +A type which wishes to override this behaviour can provide its own adapter. +For example, an image type could return the raw image data. -PUT -~~~ +``PUT`` +~~~~~~~~ -A PUT request reads the body of a request and uses it to update a +A ``PUT`` request reads the body of a request and uses it to update a resource that already exists, or to create a new object. -By default *Resource.PUT()* fails with 405 Method Not Allowed. That is, -it is not by default possible to PUT to a resource that already exists. -The same is true of *Collection.PUT()*. +By default ``Resource.PUT()`` fails with "405 Method Not Allowed". +That is, it is not by default possible to ``PUT`` to a resource that already +exists. +The same is true of ``Collection.PUT()``. -In Dexterity, the *PUT()* method is overridden to adapt self to -*zope.filerepresentation.IRawWriteFile*, and call its *write()* method +In Dexterity, the ``PUT()`` method is overridden to adapt self to +``zope.filerepresentation.IRawWriteFile``, and call its ``write()`` method one or more times, writing the contents of the request body, before -calling *close()*. The *mimeType* and *encoding* properties will also be -set based on the value of the *Content-Type* header, if available. - -The default implementation of *IRawWriteFile* for Dexterity objects -assumes the input is an RFC 2822 style message document. It will read -header values and use them to set fields on the object or in behaviours, -and similarly read the body and update the corresponding primary field. - -*NullResource.PUT()* is responsible for creating a new content object -and initialising it (recall that a *NullResource* may be returned if a +calling ``close()``. +The ``mimeType`` and ``encoding`` properties will also be +set based on the value of the ``Content-Type`` header, if available. + +The default implementation of ``IRawWriteFile`` for Dexterity objects +assumes the input is an RFC 2822 style message document. +It will read header values and use them to set fields on the object or in +behaviours, and similarly read the body and update the corresponding primary +field. + +``NullResource.PUT()`` is responsible for creating a new content object +and initialising it (recall that a ``NullResource`` may be returned if a WebDAV request attempts to traverse to an object which does not exist). -It sniffs the content type and body from the request, and then looks for -the *PUT\_factory()* method on the parent folder. +It sniffs the content type and body from the request, +and then looks for the ``PUT_factory()`` method on the parent folder. -In Dexterity, *PUT\_factory()* is implemented to look up an -*IFileFactory* adapter on self and use it to create the empty file. The -default implementation will use the *content\_type\_registry* tool to +In Dexterity, ``PUT_factory()`` is implemented to look up an +``IFileFactory`` adapter on self and use it to create the empty file. +The default implementation will use the ``content_type_registry`` tool to determine a type name for the request (e.g. based on its extension or MIME type), and then construct an instance of that type. Once an instance has been constructed, the object will be initialised by -calling its *PUT()* method, as above. +calling its ``PUT()`` method, as above. -Note that when content is created via WebDAV, an *IObjectCreatedEvent* -will be fired from the *IFileFactory* adapter, just after the object has -been constructed. At this point, none of its values will be set. -Subsequently, at the end of the *PUT()* method, an -*IObjectModifiedEvent* will be fired. This differs from the event -sequence of an object created through the web. Here, only an -*IObjectCreatedEvent* is fired, and only *after* the object has been -fully initialised. +Note that when content is created via WebDAV, +an ``IObjectCreatedEvent`` will be fired from the ``IFileFactory`` adapter, +just after the object has been constructed. +At this point, none of its values will be set. +Subsequently, at the end of the ``PUT()`` method, +an ``IObjectModifiedEvent`` will be fired. +This differs from the event sequence of an object created through the web. +In this case, only an ``IObjectCreatedEvent`` is fired, +and only *after* the object has been fully initialised. -DELETE -~~~~~~ +``DELETE`` +~~~~~~~~~~~ -A DELETE request instructs the WebDAV server to delete a resource. +A ``DELETE`` request instructs the WebDAV server to delete a resource. -*Resource.DELETE()* calls *manage\_delObjects()* on the parent folder to +``Resource.DELETE()`` calls ``manage_delObjects()`` on the parent folder to delete an object. -*Collection.DELETE()* does the same, but checks for write locks of all -children of the collection, recursively, before allowing the delete. +``Collection.DELETE()`` does the same, +but checks for write locks of all children of the collection, recursively, +before allowing the delete. -PROPFIND -~~~~~~~~ +``PROPFIND`` +~~~~~~~~~~~~~ -A PROPFIND request returns all or a set of WebDAV properties. WebDAV -properties are metadata used to describe an object, such as the last +A ``PROPFIND`` request returns all or a set of WebDAV properties. +WebDAV properties are metadata used to describe an object, such as the last modified time or the author. -*Resource.PROPFIND()* parses the request and then looks for a -*propertysheets* attribute on self. +``Resource.PROPFIND()`` parses the request and then looks for a +``propertysheets`` attribute on self. -If an ‘allprop’ request is received, it calls *dav\_\_allprop()*, if -available, on each property sheet. This method returns a list of -name/value pairs in the correct WebDAV XML encoding, plus a status. +If an ``allprop`` request is received, it calls ``dav__allprop()``, +if available, on each property sheet. +This method returns a list of name/value pairs in the correct WebDAV XML +encoding, plus a status. -If a ‘propnames’ request is received, it calls *dav\_\_propnames()*, if -available, on each property sheet. This method returns a list of -property names in the correct WebDAV XML encoding, plus a status. +If a ``propnames`` request is received, it calls ``dav__propnames()``, +if available, on each property sheet. +This method returns a list of property names in the correct WebDAV XML +encoding, plus a status. -If a ‘propstat’ request is received, it calls *dav\_\_propstats()*, if -available, on each property sheet, for each requested property. This -method returns a property name/value pair in the correct WebDAV XML +If a ``propstat`` request is received, it calls ``dav__propstats()``, +if available, on each property sheet, +for each requested property. +This method returns a property name/value pair in the correct WebDAV XML encoding, plus a status. -The *PropertyManager* mixin class defines the *propertysheets* variable -to be an instance of *DefaultPropertySheets*. This in turn has two -property sheets, *default*, a *DefaultProperties* instance, and -*webdav*, a *DAVProperties* instance. - -The *DefaultProperties* instance contains the main property sheet. This -typically has a *title* property, for example. - -*DAVProperties* will provides various core WebDAV properties. It defines -a number of read-only properties: *creationdate*, *displayname*, -*resourcetype*, *getcontenttype*, *getcontentlength*, *source*, -*supportedlock*, and *lockdiscovery*. These in turn are delegated to -methods prefixed with *dav\_\_*, so e.g. reading the *creationdate* -property calls *dav\_\_creationdate()* on the property sheet instance. -These methods in turn return values based on the the property manager -instance (i.e. the content object). In particular: - -- *creationdate* returns a fixed date (January 1st, 1970). -- *displayname* returns the value of the *title\_or\_id()* method -- *resourcetype* returns an empty string or -- *getlastmodified* returns the ZODB modification time -- *getcontenttype* delegates to the *content\_type()* method, falling - back on the *default\_content\_type()* method. In Dexterity, - *content\_type()* is implemented to look up the *IRawReadFile* - adapter on the context and return the value of its *mimeType* - property. -- *getcontentlength* delegates to the *get\_size()* method (which is - also used for the “size” column in Plone folder listings). In - Dexterity, this looks up a *zope.size.interfaces.ISized* adapter on - the object and calls *sizeForSorting()*. If this returns a unit of - *‘bytes’*, the value portion is used. Otherwise, a size of 0 is - returned. -- *source* returns a link to */document\_src*, if that attribute exists -- *supportedlock* indicates whether *IWriteLock* is supported by the - content item -- *lockdiscovery* returns information about any active locks +The ``PropertyManager`` mixin class defines the ``propertysheets`` variable +to be an instance of ``DefaultPropertySheets``. +This in turn has two property sheets: +``default``, a ``DefaultProperties`` instance; and +``webdav``, a ``DAVProperties`` instance. + +The ``DefaultProperties`` instance contains the main property sheet. This +typically has a ``title`` property, for example. + +``DAVProperties`` will provides various core WebDAV properties. +It defines a number of read-only properties: +``creationdate``, ``displayname``, +``resourcetype``, ``getcontenttype``, ``getcontentlength``, ``source``, +``supportedlock``, and ``lockdiscovery``. +These in turn are delegated to methods prefixed with ``dav__``, so e.g. +reading the ``creationdate`` property calls ``dav__creationdate()`` on the +property sheet instance. +These methods in turn return values based on the property manager instance +(i.e. the content object). +In particular: + +``creationdate`` + returns a fixed date (January 1st, 1970). + +``displayname`` + returns the value of the ``title_or_id()`` method. + +``resourcetype`` + returns an empty string or ````. + +``getlastmodified`` + returns the ZODB modification time. + +``getcontenttype`` + delegates to the ``content_type()`` method, falling back on the + ``default_content_type()`` method. + In Dexterity, ``content_type()`` is implemented to look up the + ``IRawReadFile`` adapter on the context and return the value of its + ``mimeType`` property. + +``getcontentlength`` + delegates to the ``get_size()`` method (which is also used for the + “size” column in Plone folder listings). + In Dexterity, this looks up a ``zope.size.interfaces.ISized`` adapter on + the object and calls ``sizeForSorting()``. + If this returns a unit of ``'bytes'``, the value portion is used. + Otherwise, a size of 0 is returned. + +``source`` + returns a link to ``/document_src``, if that attribute exists. + +``supportedlock`` + indicates whether ``IWriteLock`` is supported by the content item. + +``lockdiscovery`` + returns information about any active locks. Other properties in this and any other property sheets are returned as stored when requested. -If the PROPFIND request specifies a depth of 1 or infinity (i.e. the -client wants properties for items in a collection), the process is -repeated for all items returned by the *listDAVObjects()* methods, which -by default returns all contained items via the *objectValues()* method. +If the ``PROPFIND`` request specifies a depth of 1 or infinity +(i.e. the client wants properties for items in a collection), +the process is repeated for all items returned by the ``listDAVObjects()`` +methods, +which by default returns all contained items via the ``objectValues()`` +method. -PROPPATCH -~~~~~~~~~ +``PROPPATCH`` +~~~~~~~~~~~~~~ -A PROPPATCH request is used to update the properties on an existing +A ``PROPPATCH`` request is used to update the properties on an existing object. -*Resource.PROPPATCH()* deals with the same types of properties from -property sheets as *PROPFIND()*. It uses the *PropertySheet* API to add -or update properties as appropriate. +``Resource.PROPPATCH()`` deals with the same types of properties from +property sheets as ``PROPFIND()``. +It uses the ``PropertySheet`` API to add or update properties as +appropriate. -MKCOL -~~~~~ +``MKCOL`` +~~~~~~~~~ -A MKCOL request is used to create a new collection resource, i.e. create -a new folder. +A ``MKCOL`` request is used to create a new collection resource, +i.e. create a new folder. -*Resource.MKCOL()* raises 405 Method Not Allowed, because the resource -already exists (remember that in WebDAV, the MKCOL request, like a PUT +``Resource.MKCOL()`` raises "405 Method Not Allowed", +because the resource already exists +(remember that in WebDAV, the ``MKCOL`` request, like a ``PUT`` for a new resource, is sent with a location that specifies the desired new resource location, not the location of the parent object). -*NullResource.MKCOL()* handles the valid case where a MKCOL request has -been sent to a new resource. After checking that the resource does not -already exist, that the parent is indeed a collection (folderish item), -and that the parent is not locked, it calls the *MKCOL\_handler()* -method on the parent folder. +``NullResource.MKCOL()`` handles the valid case where a ``MKCOL`` request +has been sent to a new resource. +After checking that the resource does not already exist, +that the parent is indeed a collection (folderish item), +and that the parent is not locked, +it calls the ``MKCOL_handler()`` method on the parent folder. -In Dexterity, *MKCOL()\_handler* is overridden to adapt self to an -*IDirectoryFactory* from *zope.filerepresentation* and use this to -create a directory. The default implementation simply calls -*manage\_addFolder()* on the parent. This will create an instance of the -*Folder* type. +In Dexterity, the ``MKCOL()_handler`` is overridden to adapt ``self`` to an +``IDirectoryFactory`` from `zope.filerepresentation`_ and use this to +create a directory. +The default implementation simply calls ``manage_addFolder()`` on the parent. +This will create an instance of the ``Folder`` type. -COPY -~~~~ +``COPY`` +~~~~~~~~ -A COPY request is used to copy a resource. +A ``COPY`` request is used to copy a resource. -*Resource.COPY()* implements this operation using the standard Zope +``Resource.COPY()`` implements this operation using the standard Zope content object copy semantics. -MOVE -~~~~ +``MOVE`` +~~~~~~~~ -A MOVE request is used to relocate or rename a resource. +A ``MOVE`` request is used to relocate or rename a resource. -*Resource.MOVE()* implements this operation using the standard Zope -content object move semantics. +``Resource.MOVE()`` implements this operation using the standard Zope +content-object move semantics. -LOCK -~~~~ +``LOCK`` +~~~~~~~~ -A LOCK request is used to lock a content object. +A ``LOCK`` request is used to lock a content object. -All relevant WebDAV methods in the *webdav* package are lock aware. That -is, they check for locks before attempting any operation that would +All relevant WebDAV methods in the ``webdav`` package are lock aware. +That is, they check for locks before attempting any operation that would violate a lock. -Also note that *plone.locking* uses the lock implementation from the -*webdav* package by default. +Also note that `plone.locking`_ uses the lock implementation from the +``webdav`` package by default. -*Resource.LOCK()* implements locking and lock refresh support. +``Resource.LOCK()`` implements locking and lock refresh support. -*NullResource.LOCK()* implements locking on a *NullResource*. In effect, -this means locking the name of the non-existent resource. When a -*NullResource* is locked, it is temporarily turned into a -*LockNullResource* object, which is a persistent object set onto the -parent (remember that a *NullResource* is a transient object returned +``NullResource.LOCK()`` implements locking on a ``NullResource``. +In effect, this means locking the name of the non-existent resource. +When a ``NullResource`` is locked, it is temporarily turned into a +``LockNullResource`` object, which is a persistent object set onto the +parent (remember that a ``NullResource`` is a transient object returned when a child object cannot be found in a WebDAV request). -UNLOCK -~~~~~~ +``UNLOCK`` +~~~~~~~~~~ -An UNLOCK request is used to unlock a locked object. +An ``UNLOCK`` request is used to unlock a locked object. -*Resource.UNLOCK()* handles unlock requests. +``Resource.UNLOCK()`` handles unlock requests. -*LockNullResource.UNLOCK()* handles unlocking of a *LockNullResource*. -This deletes the *LockNullResource* object from the parent container. +``LockNullResource.UNLOCK()`` handles unlocking of a ``LockNullResource``. +This deletes the ``LockNullResource`` object from the parent container. Fields on container objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ When browsing content via WebDAV, a container object (folderish item) -will appear as a folder. Most likely, this object will also have content -in the form of schema fields. To make this accessible, Dexterity -containers expose a pseudo-file with the name ‘\_data’, by injecting -this into the return value of *listDAVObjects()* and adding a special -traversal hook to allow its contents to be retrieved. - -This file supports HEAD, GET, PUT, LOCK, UNLOCK, PROPFIND and PROPPATCH -requests (an error will be raised if the user attempts to rename, copy, -move or delete it). These operate on the container object, obviously. +will appear as a folder. +Most likely, this object will also have content in the form of schema +fields. +To make this accessible, Dexterity containers expose a pseudo-file with the +name ``_data``, by injecting this into the return value of +``listDAVObjects()`` and adding a special traversal hook to allow its +contents to be retrieved. + +This file supports ``HEAD``, ``GET``, ``PUT``, ``LOCK``, ``UNLOCK``, +``PROPFIND`` and ``PROPPATCH`` requests (an error will be raised if the user +attempts to rename, copy, move or delete it). +These operate on the container object, obviously. For example, when the data object is updated via a PUT request, the -*PUT()* method on the container is called, by default delegating to an -*IRawWriteFile* adapter on the container. +``PUT()`` method on the container is called, by default delegating to an +``IRawWriteFile`` adapter on the container. -.. _plone.rfc822 documentation: http://pypi.python.org/pypi/plone.rfc822 +.. _Cyberduck: http://cyberduck.ch/ .. _External Editor: ../../../../../external-editor +.. _plone.dexterity.filerepresentation: http://pypi.python.org/pypi/plone.dexterity.filerepresentation +.. _plone.directives.form: http://pypi.python.org/pypi/plone.directives.form +.. _plone.locking: http://pypi.python.org/pypi/plone.locking .. _plone.recipe.zope2instance: http://pypi.python.org/pypi/plone.recipe.zope2instance -.. _Cyberduck: http://cyberduck.ch/ +.. _plone.rfc822: http://pypi.python.org/pypi/plone.rfc822 +.. _zope.filerepresentation: http://pypi.python.org/pypi/zope.filerepresentation diff --git a/source/advanced/workflow.txt b/source/advanced/workflow.txt index 3df2849..215646f 100644 --- a/source/advanced/workflow.txt +++ b/source/advanced/workflow.txt @@ -5,181 +5,212 @@ Workflow Workflow is used in Plone for three distinct, but overlapping purposes: -- To keep track of metadata, chiefly an object’s *state* -- To create content review cycles and model other types of processes -- To manage object security - -When writing content types, we will often create custom workflows to go -with them. In this section, we will explain at a high level how Plone’s -workflow system works (pardon the pun), and then show an example of a -simple workflow to go with our example types. An exhaustive manual on -using workflows is beyond the scope of this manual, but hopefully this -will cover the basics. +- To keep track of metadata, chiefly an object’s *state*; +- to create content review cycles and model other types of processes; +- to manage object security. + +When writing content types, +we will often create custom workflows to go with them. +In this section, +we will explain at a high level how Plone’s workflow system works +(pardon the pun), +and then show an example of a simple workflow to go with our example types. +An exhaustive manual on using workflows is beyond the scope of this manual, +but hopefully this will cover the basics. .. note:: - There is nothing Dexterity-specific about this section. Everything here - applies equally well to content objects created with Archetypes or using - CMF directly. + There is nothing Dexterity-specific about this section. + Everything here applies equally well to content objects + created with Archetypes or using CMF directly. A DCWorkflow refresher ---------------------- -What follows is a fairly detailed description of DCWorkflow, originally -posted `here`_. You may find some of this a little detailed on first -reading, so feel free to skip to the specifics later on. However, is -useful to be familiar with the high level concepts. You’re unlikely to -need multi-workflow chains in your first few attempts at workflow, for -instance, but it’s useful to know what it is if you come across the -term. +What follows is a fairly detailed description of `DCWorkflow`_, +originally posted `here`_. +You may find some of this a little detailed on first reading, +so feel free to skip to the specifics later on. +However, it is useful to be familiar with the high level concepts. +You’re unlikely to need multi-workflow chains +in your first few attempts at workflow, for instance, +but it’s useful to know what it is if you come across the term. + +.. _here: http://www.martinaspeli.net/articles/dcworkflows-hidden-gems -Plone’s workflow system is known as DCWorkflow. It is a -**states-and-transitions** system, which means that your workflow starts -in a particular **state** (the **initial state**) and then moves to -other states via **transitions**(also called **actions** in CMF). +Plone’s workflow system is known as DCWorkflow. +It is a **states-and-transitions** system, +which means that your workflow starts in a particular **state** +(the **initial state**) and then moves to other states via **transitions** +(also called **actions** in CMF). When an object enters a particular state (including the initial state), the workflow is given a chance to update **permissions** on the object. -A workflow manages a number of permissions - typically the “core” CMF -permissions like *View*, *Modify portal content* and so on - and will -set those on the object at each state change. Note that this is -event-driven, rather than a real-time security check: only by changing -the state is the security information updated. This is why you need to -click *Update security settings* at the bottom of the *portal\_workflow* +A workflow manages a number of permissions – +typically the “core” CMF permissions +like :guilabel:`View`, :guilabel:`Modify portal content` and so on – +and will set those on the object at each state change. +Note that this is event-driven, rather than a real-time security check: +only by changing the state is the security information updated. +This is why you need to click :guilabel:`Update security settings` +at the bottom of the ``portal_workflow`` screen in the ZMI when you change your workflows’ security settings and want to update existing objects. -A state can also assign **local roles** to **groups.** This is akin to -assigning roles to groups on Plone’s *Sharing* tab, but the mapping of -roles to groups happens on each state change, much like the mapping of -roles to permissions. Thus, you can say that in the -*pending\_secondary*state, members of the *Secondary reviewers* group -has the *Reviewer* local role. This is powerful stuff when combined with -the more usual role-to-permission mapping, although it is not very -commonly used. - -State changes result in a number of **variables** being recorded, such -as the *actor* (the user that invoked the transition), the *action* (the -name of the transition), the date and time and so on. The list of -variables is dynamic, so each workflow can define any number of -variables linked to TALES expressions that are invoked to calculate the -current value at the point of transition. The workflow also keeps track -of the current state of each object. The state is exposed as a special -type of workflow variable called the **state variable**. Most workflows -in Plone uses the name *review\_state* as the state variable. - -Workflow variables are recorded for each state change in the **workflow -history**. This allows you to see when a transition occurred, who -effected it, and what state the object was in before or after. In fact, -the “current state” of the workflow is internally looked up as the most -recent entry in the workflow history. - -Workflow variables are also the basis for **worklists**. These are -basically pre-defined catalog queries run against the current set of -workflow variables. Plone’s review portlet shows all current worklists -from all installed workflows. This can be a bit slow, but it does meant -that you can use a single portlet to display an amalgamated list of all -items on all worklists that apply to the current user. Most Plone -workflows have a single worklist that matches on the *review\_state* -variable, e.g. showing all items in the *pending* state. +A state can also assign **local roles** to **groups**. +This is akin to assigning roles to groups on Plone’s :guilabel:`Sharing` tab, +but the mapping of roles to groups happens on each state change, +much like the mapping of roles to permissions. +Thus, you can say that in the ``pending_secondary`` state, +members of the :guilabel:`Secondary reviewers` group +has the :guilabel:`Reviewer` local role. +This is powerful stuff when combined with the more usual role-to-permission +mapping, although it is not very commonly used. + +State changes result in a number of **variables** being recorded, +such as the *actor* (the user that invoked the transition), +the *action* (the name of the transition), +the date and time and so on. +The list of variables is dynamic, +so each workflow can define any number of variables +linked to TALES expressions that are invoked +to calculate the current value at the point of transition. +The workflow also keeps track of the current state of each object. +The state is exposed as a special type of workflow variable +called the **state variable**. +Most workflows in Plone uses the name ``review_state`` as the state variable. + +Workflow variables are recorded for each state change +in the **workflow history**. +This allows you to see when a transition occurred, +who effected it, and what state the object was in before or after. +In fact, the “current state” of the workflow is internally looked up +as the most recent entry in the workflow history. + +Workflow variables are also the basis for **worklists**. +These are basically pre-defined catalog queries +run against the current set of workflow variables. +Plone’s review portlet shows all current worklists +from all installed workflows. +This can be a bit slow, +but it does mean that you can use a single portlet +to display an amalgamated list of all items on all worklists +that apply to the current user. +Most Plone workflows have a single worklist +that matches on the ``review_state`` variable, +e.g. showing all items in the ``pending`` state. If states are the static entities in the workflow system, -**transitions** (actions) are provide the dynamic parts. Each state -defines zero or more possible exit transitions, and each transition -defines exactly one target state, though it is possible to mark a -transition as “stay in current state”. This can be useful if you want to -do something in reaction to a transition and record that the transition -happened in the workflow history, but not change the state (or security) -of the object. - -Transitions are controlled by one or more **guards**. These can be -permissions (the preferred approach), roles (mostly useful for the -*Owner* role - in other cases it is normally better to use permissions) -or TALES expressions. A transition is available if all its guard -conditions are true. A transition with no guard conditions is available -to everyone (including anonymous!). - -Transitions are user-triggered by default, but may be **automatic**. An -automatic transition triggers immediately following another transition -provided its guard conditions pass. It will not necessarily trigger as -soon as the guard condition becomes true, as that would involve -continually re-evaluating guards for all active workflows on all -objects! - -When a transition is triggered, the *IBeforeTransitionEvent* and -*IAfterTransitionEvent* **events** are triggered. These are low-level -events from *Products.DCWorkflow* that can tell you a lot about the -previous and current states. There is a higher level -*IActionSucceededEvent* in *Products.CMFCore* that is more commonly used -to react after a workflow action has completed. - -In addition to the events, you can configure workflow **scripts**. These -are either created through-the-web or (more commonly) as External -Methods, and may be set to execute before a transition is complete (i.e. -before the object enters the target state) or just after it has been -completed (the object is in the new state). Note that if you are using -event handlers, you’ll need to check the event object to find out which -transition was invoked, since the events are fired on all transitions. +**transitions** (actions) provide the dynamic parts. +Each state defines zero or more possible exit transitions, +and each transition defines exactly one target state, +though it is possible to mark a transition as “stay in current state”. +This can be useful if you want to do something in reaction to a transition +and record that the transition happened in the workflow history, +but not change the state (or security) of the object. + +Transitions are controlled by one or more **guards**. +These can be permissions (the preferred approach), +roles (mostly useful for the :guilabel:`Owner` role – +in other cases it is normally better to use permissions) +or TALES expressions. +A transition is available if all its guard conditions are true. +A transition with no guard conditions is available to everyone +(including anonymous!). + +Transitions are user-triggered by default, but may be **automatic**. +An automatic transition triggers immediately following another transition +provided its guard conditions pass. +It will not necessarily trigger as soon as the guard condition becomes true, +as that would involve continually re-evaluating guards +for all active workflows on all objects! + +When a transition is triggered, +the ``IBeforeTransitionEvent`` and ``IAfterTransitionEvent`` **events** +are triggered. +These are low-level events from ``Products.DCWorkflow`` that can tell you a +lot about the previous and current states. +There is a higher level ``IActionSucceededEvent`` in ``Products.CMFCore`` +that is more commonly used to react after a workflow action has completed. + +In addition to the events, you can configure workflow **scripts**. +These are either created through-the-web +or (more commonly) as External Methods [*]_, +and may be set to execute before a transition is complete +(i.e. before the object enters the target state) +or just after it has been completed (the object is in the new state). +Note that if you are using event handlers, +you’ll need to check the event object to find out which transition was +invoked, since the events are fired on all transitions. The per-transition scripts are only called for the specific transitions for which they were configured. Multi-chain workflows ~~~~~~~~~~~~~~~~~~~~~ -Workflows are mapped to types via the *portal\_workflow* tool. There is -a default workflow, indicated by the string *(Default)*. Some types have -no workflow, which means that they hold no state information and -typically inherit permissions from their parent. It is also possible for -types to have **multiple workflows**. You can list multiple workflows by -separating their names by commas. This is called a **workflow chain**. +Workflows are mapped to types via the ``portal_workflow`` tool. +There is a default workflow, indicated by the string ``(Default)``. +Some types have no workflow, +which means that they hold no state information and +typically inherit permissions from their parent. +It is also possible for types to have **multiple workflows**. +You can list multiple workflows by separating their names by commas. +This is called a **workflow chain**. Note that in Plone, the workflow chain of an object is looked up by -multi-adapting the object and the workflow to the *IWorkflowChain* -interface. The adapter factory should return a tuple of string workflow -names (*IWorkflowChain* is a specialisation of *IReadSequence*, i.e. a -tuple). The default obviously looks at the mappings in the -*portal\_workflow* tool, but it is possible to override the mapping, +multi-adapting the object and the workflow to the ``IWorkflowChain`` +interface. +The adapter factory should return a tuple of string workflow names +(``IWorkflowChain`` is a specialisation of ``IReadSequence``, i.e. a tuple). +The default obviously looks at the mappings in the ``portal_workflow`` tool, +but it is possible to override the mapping, e.g. by using a custom adapter registered for some marker interface, which in turn could be provided by a type-specific behavior. Multiple workflows applied in a single chain co-exist in time. Typically, you need each workflow in the chain to have a different state -variable name. The standard *portal\_workflow* API (in particular, -*doActionFor()*, which is used to change the state of an object) also -asumes the transition ids are unique. If you have two workflows in the -chain and both currently have a *submit* action available, only the -first workflow will be transitioned if you do -*portal\_workflow.doActionFor(context, ‘submit’)*. Plone will show all -available transitions from all workflows in the current object’s chain -in the *State* drop-down, so you do not need to create any custom UI for -this. However, Plone always assumes the state variable is called -*review\_state* (which is also the variable indexed in -*portal\_catalog*). Therefore, the state of a secondary workflow won’t -show up unless you build some custom UI. - -In terms of security, remember that the role-to-permission (and -group-to-local-role) mappings are event-driven and are set after each -transition. If you have two concurrent workflows that manage the same -permissions, the settings from the last transition invoked will apply. +variable name. +The standard ``portal_workflow`` API (in particular, +``doActionFor()``, which is used to change the state of an object) +also asumes the transition ids are unique. +If you have two workflows in the chain and both currently have a ``submit`` +action available, +only the first workflow will be transitioned if you do +``portal_workflow.doActionFor(context, ‘submit’)``. +Plone will show all available transitions from all workflows in the current +object’s chain in the ``State`` drop-down, +so you do not need to create any custom UI for this. +However, Plone always assumes the state variable is called ``review_state`` +(which is also the variable indexed in ``portal_catalog``). +Therefore, the state of a secondary workflow won’t show up +unless you build some custom UI. + +In terms of security, remember that the role-to-permission +(and group-to-local-role) mappings +are event-driven and are set after each transition. +If you have two concurrent workflows that manage the same permissions, +the settings from the last transition invoked will apply. If they manage different permissions (or there is a partial overlap) then only the permissions managed by the most-recently-invoked workflow will change, leaving the settings for other permissions untouched. -Multiple workflows can be very useful in case you have concurrent -processes. For example, an object may be published, but require -translation. You can track the review state in the main workflow and the -translation state in another. If you index the state variable for the -second workflow in the catalog (the state variable is always available -on the indexable object wrapper so you only need to add an index with -the appropriate name to *portal\_catalog*) you can search for all -objects pending translation, for example using a *Collection*. +Multiple workflows can be very useful in case you have concurrent processes. +For example, an object may be published, but require translation. +You can track the review state in the main workflow +and the translation state in another. +If you index the state variable for the second workflow in the catalog +(the state variable is always available on the indexable object wrapper +so you only need to add an index with the appropriate name +to ``portal_catalog``) +you can search for all objects pending translation, +for example using a *Collection*. Creating a new workflow ----------------------- With the theory out of the way, let’s show how to create a new workflow. -Workflows are managed in the*portal\_workflow* tool. You can use the ZMI +Workflows are managed in the ``portal_workflow`` tool. You can use the ZMI to create new workflows and assign them to types. However, it is usually preferable to create an installable workflow configuration using GenericSetup. By default, each workflow as well as the workflow @@ -190,33 +221,32 @@ For the purposes of this manual, we will show an alternative configuration syntax based on spreadsheets (in CSV format). This is provided by the `collective.wtf`_ package. You can read more about the details of the syntax in its documentation. Here, we will only show how -to use it to create a simple workflow for the *Session* type, allowing +to use it to create a simple workflow for the ``Session`` type, allowing members to submit sessions for review. -To use *collective.wtf*, we need to depend on it. In *setup.py*, we -have: +To use ``collective.wtf``, we need to depend on it. +In ``setup.py``, we have:: -:: - - install_requires=[ - ... - 'collective.wtf', - ], + install_requires=[ + ... + 'collective.wtf', + ], .. note:: - As before, the ** line in *configure.zcml* takes - care of configuring the package for us. + As before, the ```` line in ``configure.zcml`` + takes care of configuring the package for us. -A workflow definition using *collective.wtf* consists of a CSV file in -the *profiles/default/workflow\_csv* directory, which we will create, -and a *workflows.xml* file in *profiles/default* which maps types to -workflows. +A workflow definition using ``collective.wtf`` consists of a CSV file in +the ``profiles/default/workflow_csv`` directory, +which we will create, +and a ``workflows.xml`` file in ``profiles/default`` +which maps types to workflows. -The workflow mapping in *profiles/default/workflows.xml* looks like +The workflow mapping in ``profiles/default/workflows.xml`` looks like this: -:: +.. code-block:: xml @@ -228,9 +258,10 @@ this: The CSV file itself is found in -*profiles/default/workflow\_csv/example.conference.session\_workflow.csv*. -It contains the following, which was exported to CSV from an OpenOffice -spreadsheet. You can find the original spreadsheet with the +``profiles/default/workflow_csv/example.conference.session_workflow.csv``. +It contains the following, +which was exported to CSV from an OpenOffice spreadsheet. +You can find the original spreadsheet with the `example.conference source code`_. This applies some useful formatting, which is obviously lost in the CSV version. @@ -239,6 +270,8 @@ which is obviously lost in the CSV version. For your own workflows, you may want to use `this template`_ as a starting point. +.. _this template: ../Workflow%20template.ods + :: "[Workflow]" @@ -302,46 +335,54 @@ which is obviously lost in the CSV version. "Target state:","published" "Guard permission:","Review portal content" -Here, you can see several states and transitions. Each state contains a -role/permission map, and a list of the possible exit transitions. Each -transition contains a target state and other meta-data such as a title +Here, you can see several states and transitions. +Each state contains a role/permission map, +and a list of the possible exit transitions. +Each transition contains a target state and other meta-data such as a title and a description, as well as guard permissions. .. note:: - like most other GenericSetup import steps, the workflow uses + Like most other GenericSetup import steps, the workflow uses the Zope 2 permission title when referring to permissions. When the package is (re-)installed, this workflow should be available -under *portal\_workflow* and mapped to the *Session* type. +under ``portal_workflow`` and mapped to the ``Session`` type. .. note:: - If you have existing instances, don’t forget to go to *portal\_workflow* - in the ZMI and click *Update security settings* at the bottom of the - page. This ensures that existing objects reflect the most recent - security settings in the workflow. + If you have existing instances, don’t forget to go to ``portal_workflow`` + in the ZMI and click :guilabel:`Update security settings` + at the bottom of the page. + This ensures that existing objects reflect the most recent security + settings in the workflow. A note about add permissions ---------------------------- -This workflow assumes that regular members can add *Session*proposals to -*Programs*, which are then reviewed. Previously, we granted the -*example.conference: Add session*permission to the *Member* role. This -is necessary, but not sufficient to allow memers to add sessions to -programs. The user will also need the generic *Add portal content* -permission in the *Program* folder. +This workflow assumes that regular members can add *Session* proposals to +*Programs*, which are then reviewed. +Previously, we granted the +``example.conference: Add session`` permission to the ``Member`` role. +This is necessary, but not sufficient +to allow members to add sessions to programs. +The user will also need the generic ``Add portal content`` permission in the +``Program`` folder. There are two ways to achieve this: -- Build a workflow for the *Program* type that manages this permission -- Use the *Sharing* tab to grant *Can add* to the *Authenticated Users* - group. This grants the *Contributor* local role to members. By - default, this role is granted the *Add portal content* permission. - +- Build a workflow for the ``Program`` type that manages this permission +- Use the :guilabel:`Sharing` tab to grant :guilabel:`Can add` to the + :guilabel:`Authenticated Users` group. + This grants the :guilabel:`Contributor` local role to members. + By default, this role is granted the :guilabel:`Add portal content` + permission. +.. [*] An *External Method* is a Python script evaluated in Zope context. + See `Logic Objects + `_ + in the Zope 2 Bok. .. _collective.wtf: http://pypi.python.org/pypi/collective.wtf .. _example.conference source code: http://svn.plone.org/svn/collective/example.conference/trunk/example/conference/profiles/default/workflow_csv -.. _this template: ../Workflow%20template.ods -.. _here: http://www.martinaspeli.net/articles/dcworkflows-hidden-gems +.. _DCWorkflow: http://pypi.python.org/pypi/Products.DCWorkflow diff --git a/source/custom-views.txt b/source/custom-views.txt index b064adf..bd5dab8 100644 --- a/source/custom-views.txt +++ b/source/custom-views.txt @@ -8,8 +8,8 @@ Simple views **Creating basic views** -So far, our types have used the default views, which use the display -widgets from *z3c.form*, much like the add and edit forms use the edit +So far, our types have used the default views, which use the *display* +widgets from `z3c.form`_, much like the add and edit forms use the *edit* widgets. This is functional, but not very attractive. Most types will need one or more custom view templates. @@ -17,22 +17,22 @@ Dexterity types are no different to any other content type in Plone. You can register a view for your schema interface, and it will be available on your type. If the view is named *view*, it will be the default view, at least if you use the standard FTI configuration. This is because the -FTI’s *default\_view* property is set to *view*, and *view* is in the -list of *view\_methods.* +FTI’s ``default_view`` property is set to ``view``, and ``view`` is in the +list of ``view_methods.`` When working with Dexterity, we will typically configure our views using -the *five.grok* configuration system, eschewing ZCML configuration. -Below, we will show how to add simple views for the *Program* and -*Speaker* types. Next, we will show how to use display forms to take +the `five.grok`_ configuration system, eschewing ZCML configuration. +Below, we will show how to add simple views for the ``Program`` and +``Speaker`` types. Next, we will show how to use display forms to take advantage of the standard widgets if required. -The *five.grok* view approach uses a class in the content type’s module, +The `five.grok`_ view approach uses a class in the content type’s module, which is automatically associated with a template in an accompanying directory. These directories should be created next to the module files, -so we will have *program\_templates*, *presenter\_templates* and -*session\_templates*. +so we will have ``program_templates``, ``presenter_templates`` and +``session_templates``. -In *program.py*, the view is registered as follows: +In ``program.py``, the view is registered as follows: .. code-block:: python @@ -52,7 +52,7 @@ In *program.py*, the view is registered as follows: sort_on='sortable_title') This creates a view registration similar to what you may do with a -** ZCML directive. We have also added a helper method +```` ZCML directive. We have also added a helper method which will be used in the view. Note that this requires some imports at the top of the file: @@ -65,25 +65,25 @@ the top of the file: The view registration works as follows: -- The view name will be *@@view*, taken from the class name in +- The view name will be ``@@view``, taken from the class name in lowercase. You can specify an alternative name with - *grok.name(‘some-name’)* if required. -- The *grok.context()* directive specifies that this view is used for - objects providing *IProgram*. -- You can add a *grok.layer()* directive if you want to specify a + ``grok.name('some-name')`` if required. +- The ``grok.context()`` directive specifies that this view is used for + objects providing ``IProgram``. +- You can add a ``grok.layer()`` directive if you want to specify a browser layer. -- The *grok.require()* directive specifies the required permission for - this view. It uses the Zope 3 permission name. *zope2.View* and - *zope.Public* are the most commonly used permissions (in fact, - *zope.Public* is not actually a permission, it just means “no +- The ``grok.require()`` directive specifies the required permission for + this view. It uses the Zope 3 permission name. ``zope2.View`` and + ``zope.Public`` are the most commonly used permissions (in fact, + ``zope.Public`` is not actually a permission, it just means “no permission required”). For a list of other standard permissions, see - *parts/omelette/Products/Five/permissions.zcml*. We will cover + ``parts/omelette/Products/Five/permissions.zcml``. We will cover creating custom permissions later in this manual. - Any methods added to the view will be available to the template via - the *view* variable. The content object is available via *context*, + the ``view`` variable. The content object is available via ``context``, as usual. -This is associated with a file in *program\_templates/view.pt*. The file +This is associated with a file in ``program_templates/view.pt``. The file name matches the class name (even if a different view name was specified). This contains: @@ -146,22 +146,19 @@ specified). This contains: For the most part, this template outputs the values of the various -fields, using the *sessions()* method on the view to obtain the sessions +fields, using the ``sessions()`` method on the view to obtain the sessions contained within the program. -.. note :: +.. note:: Notice how the ``details`` *RichText* field is output as + ``tal:content="structure context/details/output"``. The + ``structure`` keyword ensures that the rendered HTML is not escaped. + The extra traversal to ``details/output`` is necessary because the + *RichText* field actually stores a *RichTextValue* object that + contains not only the raw text as entered by the user, but also a + MIME type (e.g. ``text/html``) and the rendered output text. + *RichText* fields are covered in more detail :ref:`later in this manual `. - Notice how the *details RichText* field is output as - *tal:content=“structure context/details/output”*. The *structure* - keyword ensure the rendered HTML is not escaped. The extra traversal to - *details/output* is necessary because the *RichText* field actually - stores a *RichTextValue* object that contains not only the raw text as - entered by the user, but also a MIME type (e.g. *text/html*) and the - rendered output text. *RichText* fields are covered in more detail later - in this manual. - - -The view for Presenter, in *presenter.py*, is even simpler: +The view for ``Presenter``, in ``presenter.py``, is even simpler: .. code-block:: python @@ -169,7 +166,7 @@ The view for Presenter, in *presenter.py*, is even simpler: grok.context(IPresenter) grok.require('zope2.View') -Its template, in *presenter\_templates/view.pt*, is similar to the +Its template, in ``presenter_templates/view.pt``, is similar to the previous template: .. code-block:: html @@ -211,7 +208,7 @@ be created by putting a little more work into the templates. You should also realise that you can create any type of view using this technique. Your view does not have to be related to a particular content -type, even. You could set the context to *Interface*, for example, to +type, even. You could set the context to ``Interface``, for example, to make a view that’s available on all types. Display forms @@ -219,25 +216,23 @@ Display forms **Using display widgets in your views** -In the previous section, we created a view extending *grok.View*. This +In the previous section, we created a view extending ``grok.View``. This kind of view is the most common, but sometimes we want to make use of the widgets and information in the type’s schema more directly, for example to invoke transforms or re-use more complex HTML. To do this, you can use a *display form*. This is really just a view base class that knows about the schema of a type. We will use an example -in *session.py*, with a template in*session\_templates/view.pt.* - -.. note :: +in ``session.py``, with a template in ``session_templates/view.pt.`` - Display forms involve the same type of overhead as add- and - edit-forms. If you have complex forms with many behaviors, fieldsets and - widget hints, you may notice a slow-down compared to standard views, at - least on high volume sites. +.. note:: Display forms involve the same type of overhead as add- and + edit-forms. If you have complex forms with many behaviors, fieldsets and + widget hints, you may notice a slow-down compared to standard views, at + least on high volume sites. The new view class is pretty much the same as before, except that we -derive from *dexterity.DisplayForm* -(*plone.directives.dexterity.DisplayForm*): +derive from ``dexterity.DisplayForm`` +(``plone.directives.dexterity.DisplayForm``): .. code-block:: python @@ -248,21 +243,21 @@ derive from *dexterity.DisplayForm* This gives our view a few extra properties that we can use in the template: -- *view.w* is a dictionary of all the display widgets, keyed by field +- ``view.w`` is a dictionary of all the display widgets, keyed by field names. For fields provided by behaviors, that is usually prefixed with the behavior interface name - (*IBehaviorInterface**.field\_name*). For the default schema, + (``IBehaviorInterface.field_name``). For the default schema, unqualified names apply. -- *view.widgets* contains a list of widgets in schema order for the +- ``view.widgets`` contains a list of widgets in schema order for the default fieldset -- *view.groups* contains a list of fieldsets in fieldset order. -- *view.fieldsets* contains a dict mapping fieldset name to fieldset -- On a fieldset (group), you can access a *widgets* list to get widgets - in that fieldset +- ``view.groups`` contains a list of fieldsets in fieldset order. +- ``view.fieldsets`` contains a dict mapping fieldset name to fieldset +- On a fieldset (group), you can access a ``widgets`` list to get widgets + in that fieldset. -The *w* dict is the mostly commonly used. +The ``w`` dict is the mostly commonly used. -The *session\_templates/view.pt* template contains the following: +The ``session_templates/view.pt`` template contains the following: .. code-block:: html @@ -276,29 +271,24 @@ The *session\_templates/view.pt* template contains the following: - - -
- -

- -
- -

- -

- -
- -
- + +
+

+
+

+

+
+
-Notice how we use expressions like *view/w/details/render* (where -*details* is the field name) to get the rendering of a widget. Other -properties include *\_\_name\_\_*, the field name, and *label*, the +Notice how we use expressions like ``view/w/details/render`` (where +``details`` is the field name) to get the rendering of a widget. Other +properties include ``__name__``, the field name, and ``label``, the field title. + +.. _z3c.form: http://pypi.python.org/pypi/z3c.form +.. _five.grok: http://pypi.python.org/pypi/five.grok diff --git a/source/prerequisite.txt b/source/prerequisite.txt index 759e888..d4ab970 100644 --- a/source/prerequisite.txt +++ b/source/prerequisite.txt @@ -8,18 +8,20 @@ Buildout configuration **Setting up a development buildout** -To use Dexterity, you simply need to depend on the *plone.app.dexterity* +To use Dexterity, you simply need to depend on the `plone.app.dexterity`_ package. You will also need to extend a Dexterity *known good set (KGS)* to make sure that you get the right versions of the packages that make up Dexterity. The easiest way to achieve this is to use a buildout that pins certain versions. For a minimal buildout, see the `installation how-to `_. In this -section, will expand upon this to add some development tools. +section, we will expand upon this to add some development tools. To create the buildout, you can start with a standard Plone buildout and modify -buildout.cfg to looks something like this. You should update the Dexterity and -Plone versions as appropriate:: +``buildout.cfg`` to look something like this. You should update the Dexterity and +Plone versions as appropriate: + +.. code-block:: ini [buildout] extensions = mr.developer buildout.dumppickedversions @@ -72,50 +74,50 @@ Plone versions as appropriate:: You will see references to a package called *example.conference*. We'll create that shortly. Let's first go through and explain the buildout, however. -* We define two buildout extensions, `mr.developer - `_, which helps us manage our - code, and `buildout.dumppickedversions - `_, which helps us keep track of which +* We define two buildout extensions, `mr.developer`_, which helps us manage our + code, and `buildout.dumppickedversions`_, which helps us keep track of which versions buildout has picked for our dependencies. If you are not familiar - with mr.developer, you should read its documentation, in particular the - documentation about the ./bin/develop command. + with `mr.developer`_, you should read its documentation, in particular the + documentation about the ``./bin/develop`` command. * We extend the version configuration for release 1.1 of Dexterity, targeted at Plone 4.1.2. You should adjust your Plone version accordingly. This allows Dexterity to update certain packages in Plone. You should check `the Dexterity project page `_ to discover which is the latest version. The known good set URL will give us the latest known good set of Dexterity packages, making it safe to depend on *plone.app.dexterity* in our *example.conference* product. The - *versions = versions* line will make buildout use the known good set defined by + ``versions = versions`` line will make buildout use the known good set defined by the extended URLs. -* We tell *mr.developer* which section contain the sources to our packages with - *sources = sources* (the *[sources]* section is at the end of the file). We also +* We tell `mr.developer`_ which section contain the sources to our packages with + ``sources = sources`` (the ``[sources]`` section is at the end of the file). We also tell it to automatically check out and configure our *example.conference* package. This will check out the package form the given version repository - URL, put it in src/ and ensure that it is configured as a develop egg. + URL, put it in ``src/`` and ensure that it is configured as a develop egg. * If you don't have a version repository yet, you can just put the egg in the - src/ directory. The *develop = src/\** line will pick the egg up from there. - **Note**: With mr.developer installed, we comment this out so that we don't get + ``src/`` directory. The ``develop = src/*`` line will pick the egg up from there. + **Note**: With `mr.developer`_ installed, we comment this out so that we don't get the same egg loaded twice. -* We configure a standard Zope instance and add two development tools to the - *eggs* line: - * *Products.PdbDebugMode* will drop to a pdb shell when an exception occurs. - * *plone.reload* lets you go to *localhost:8080/@@reload* to reload code. - Look at the `plone.reload `_ - documentation for details +* We configure a standard Zope instance and add two development tools + to the ``eggs`` line: + + * ``Products.PdbDebugMode`` will drop to a pdb shell when an + exception occurs. + * `plone.reload`_ lets you go to ``localhost:8080/@@reload`` to + reload code. Look at the `plone.reload`_ documentation for details. + * We also add the *Plone egg* and our new package. * We configure a standard Zope 2 server, which is used by our instance. -* We configure *collective.recipe.omelette* so that we get a set of links in - *parts/omelette* giving access to all the code that is currently used by the - instance. If you are on Windows, you will need to install junction.exe for +* We configure `collective.recipe.omelette`_ so that we get a set of links in + ``parts/omelette`` giving access to all the code that is currently used by the + instance. If you are on Windows, you will need to install ``junction.exe`` for this to work. See the `omelette documentation `_ for details. -* We install a testrunner. This will give us a bin/test command which can use +* We install a testrunner. This will give us a ``bin/test`` command which can use to run our tests. **Note**: Only those eggs listed directly here will be available to the test runner. If you want to run tests for a dependency, you - need to list it explicitly under the *eggs* option in the *[test] part*. + need to list it explicitly under the ``eggs`` option in the ``[test] part``. -With this buildout, and a standard *bootstrap.py* file, you can run the usual -*python bootstrap.py; ./bin/buildout* sequence to configure Plone and Dexterity. +With this buildout, and a standard ``bootstrap.py`` file, you can run the usual +``python bootstrap.py; ./bin/buildout`` sequence to configure Plone and Dexterity. Before we do that, though, we need to create the package. Creating a package @@ -125,12 +127,12 @@ Creating a package Typically, our content types will live in a separate package to our theme and other customisations. In the previous section, we showed how our buildout -refers to a package in the *src/* directory, either placed there manually or -checked out by *mr.developer*, called *example.conference*. You can find the +refers to a package in the ``src/`` directory, either placed there manually or +checked out by `mr.developer`_, called *example.conference*. You can find the latest version of this package in the `Collective repository `_. -To create a new package, we can start with *ZopeSkel* and the *plone* +To create a new package, we can start with *ZopeSkel* and the ``plone`` template. See `this how-to `_ for more information on how to install ZopeSkel. @@ -140,25 +142,25 @@ for more information on how to install ZopeSkel. with `zopeskel.dexterity `_ to create a package skeleton that will be ready for immediate use. -We run the following from the src/ directory: +We run the following from the ``src/`` directory: .. code-block:: bash $ paster create -t plone example.conference If you are using this template, make sure that you specify a namespace -*(example)* and package name *(conference)* that matches the egg name -*(example.conference)* on the command line. Answer *False* when asked to create -a Zope 2 product, and *False* again when asked if the product is zip-safe. +(*example*) and package name (*conference*) that matches the egg name +(*example.conference*) on the command line. Answer ``False`` when asked to create +a Zope 2 product, and ``False`` again when asked if the product is zip-safe. Next, we will normalise the code created by paster, mainly by removing things we don't need. -First, we edit *setup.py* to add *plone.app.dexterity* as a dependency and -specify the package as a *z3c.autoinclude* plug-in. This ensures that we do not +First, we edit ``setup.py`` to add `plone.app.dexterity`_ as a dependency and +specify the package as a `z3c.autoinclude`_ plug-in. This ensures that we do not need to load its ZCML separately once the package is configured in -*buildout.cfg* (this feature is enabled in Plone 3.3 and later). We will also -add a dependency on *collective.autopermission*, which will help us define +``buildout.cfg`` (this feature is enabled in Plone 3.3 and later). We will also +add a dependency on `collective.autopermission`_, which will help us define custom permissions later. We can remove the paster plugin entry point and paster_plugins line. We will not need these:: @@ -201,7 +203,7 @@ We can remove the paster plugin entry point and paster_plugins line. We will not ) -Next, we edit configure.zcml and add the following: +Next, we edit ``configure.zcml`` and add the following: .. code-block:: html @@ -229,10 +231,10 @@ Next, we edit configure.zcml and add the following: Here, we first automatically include the ZCML configuration for all -packages listed under *install\_requires* in *setup.py*. This feature is -part of *z3c.autoinclude*, which is included with Plone 3.3 and later. -The alternative would be to manually add a line like ** for each dependency. +packages listed under ``install\_requires`` in ``setup.py``. This feature is +part of `z3c.autoinclude`_, which is included with Plone 3.3 and later. +The alternative would be to manually add a line like ```` for each dependency. Next, we *grok* the package to construct and register schemata, views, forms and so on based on conventions used in the various files we will @@ -241,9 +243,9 @@ add throughout this tutorial. Finally, we register a GenericSetup profile to make the type installable, which we will build up over the next several sections. -The profile requires a directory *profiles/default*. You should create -the *profiles* directory in the same folder as *configure.zcml*, and -*default* under that. In *default*, add a file called *metadata.xml* +The profile requires a directory ``profiles/default``. You should create +the ``profiles`` directory in the same folder as ``configure.zcml``, and +``default`` under that. In ``default``, add a file called ``metadata.xml`` with the following contents: .. code-block:: xml @@ -256,8 +258,8 @@ with the following contents: This gives the profile a version number (which is different to the -*package* version set in *setup.py*) in case we need to define upgrade -steps in the future, and declares that *plone.app.dexterity* should be +*package* version set in ``setup.py``) in case we need to define upgrade +steps in the future, and declares that `plone.app.dexterity`_ should be installed when this package is installed. We can add other profiles to depend on in the same way if you need to. @@ -273,3 +275,11 @@ The buildout should now configure Plone, Dexterity and the *example.conference* package. We are now ready to start adding types. + +.. _plone.app.dexterity: http://pypi.python.org/pypi/plone.app.dexterity +.. _mr.developer: http://pypi.python.org/pypi/mr.developer +.. _buildout.dumppickedversions: http://pypi.python.org/pypi/buildout.dumppickedversions +.. _plone.reload: http://pypi.python.org/pypi/plone.reload +.. _collective.recipe.omelette: http://pypi.python.org/pypi/collective.recipe.omelette +.. _z3c.autoinclude: http://pypi.python.org/pypi/z3c.autoinclude +.. _collective.autopermission: http://pypi.python.org/pypi/collective.autopermission diff --git a/source/reference/fields.txt b/source/reference/fields.txt index 047c955..4bfc355 100644 --- a/source/reference/fields.txt +++ b/source/reference/fields.txt @@ -4,93 +4,96 @@ Fields **The standard schema fields** The following tables shows the most common field types for use in -Dexterity schemata. See the documentation on `creating schemata`_ for -information about how to use these. +Dexterity schemata. +See the documentation on `creating schemata`_ for information about how to +use these. Field properties ---------------- -Fields are initialised with properties passed in their constructors. To -avoid having to repeat the available properties for each field, we’ll +Fields are initialised with properties passed in their constructors. +To avoid having to repeat the available properties for each field, we’ll list them once here, grouped into the interfaces that describe them. You’ll see those interfaces again in the tables below that describe the -various field types. Refer to the table below to see what properties a -particular interface implies. - -+------------+--------------------+-----------+----------------------------------------------------+ -| Interface | Property | Type | Description | -+============+====================+===========+====================================================+ -| IField | title | unicode | The title of the field. Used in the widget. | -| +--------------------+-----------+----------------------------------------------------+ -| | description | unicode | A description for the field. Used in the widget. | -| +--------------------+-----------+----------------------------------------------------+ -| | required | bool | Whether or not the field is required. Used for | -| | | | form validation. The default is *True*. | -| +--------------------+-----------+----------------------------------------------------+ -| | readonly | bool | Whether or not the field is read-only. Default | -| | | | is *False*. | -| +--------------------+-----------+----------------------------------------------------+ -| | default | | The default value for the field. Used in forms | -| | | | and sometimes as a fallback value. Must be a | -| | | | valid value for the field if set. The default | -| | | | is *None*. | -| +--------------------+-----------+----------------------------------------------------+ -| | missing_value | | A value that represents "this field is not set". | -| | | | Used by form validation. Defaults to *None*. For | -| | | | lists and tuples, it is sometimes useful to set | -| | | | this to an empty list/tuple. | -+------------+--------------------+-----------+----------------------------------------------------+ -| IMinMaxLen | min_length | int | The minimum required length. Used for string | -| | | | fields. Default is *0*. | -| +--------------------+-----------+----------------------------------------------------+ -| | max_length | int | The maximum allowed length. Used for string | -| | | | fields. Default is *None* (no check). | -+------------+--------------------+-----------+----------------------------------------------------+ -| IMinMax | min | | The minimum allowed value. Must be a valid value | -| | | | for the field, e.g. for an *Int* field this should | -| | | | be an integer. Default is *None* (no check). | -| +--------------------+-----------+----------------------------------------------------+ -| | max | | The maximum allowed value. Must be a valid value | -| | | | for the field, e.g. for an Int field this should | -| | | | be an integer. Default is *None* (no check). | -+------------+--------------------+-----------+----------------------------------------------------+ -| ICollection| value_type | | Another *Field* instance that describes the | -| | | | allowable values in a list, tuple or other | -| | | | collection. Must be set for any collection field. | -| | | | One common usage is to set this to a *Choice*, to | -| | | | model a multi-selection field with a vocabulary. | -| +--------------------+-----------+----------------------------------------------------+ -| | unique | bool | Whether or not values in the collection must be | -| | | | unique. Usually not set directly - use a *Set* or | -| | | | *Frozenset* to guarantee uniqueness in an | -| | | | efficient way. | -+------------+--------------------+-----------+----------------------------------------------------+ -| IDict | key_type | | Another *Field* instance that describes the | -| | | | allowable keys in a dictionary. Similar to the | -| | | | *value_type* of a collection. Must be set. | -| +--------------------+-----------+----------------------------------------------------+ -| | value_type | | Another *Field* instance that describes the | -| | | | allowable values in a dictionary. Similar to the | -| | | | *value_type* of a collection. Must be set. | -+------------+--------------------+-----------+----------------------------------------------------+ -| IObject | schema | Interface | An interface that must be provided by any object | -| | | | stored in this field. | -+------------+--------------------+-----------+----------------------------------------------------+ -| IRichText | default_mime_type | str | Default MIME type for the input text of a rich | -| | | | text field. Defaults to *text/html*. | -| +--------------------+-----------+----------------------------------------------------+ -| | output_mime_type | str | Default output MIME type for the transformed | -| | | | value of a rich text field. Defaults to | -| | | | *text/x-html-safe*. There must be a transformation | -| | | | chain in the *portal_transforms* tool that can | -| | | | transform from the input value to the *output* | -| | | | value for the output property of the *RichValue* | -| | | | object to contain a value. | -| +--------------------+-----------+----------------------------------------------------+ -| | allowed_mime_types | tuple | A list of allowed input MIME types. The default | -| | | | is *None*, in which case the site-wide settings | -| | | | (from the *Markup* control panel) will be used. | -+------------+--------------------+-----------+----------------------------------------------------+ +various field types. +Refer to the table below to see what properties a particular interface +implies. + +=========== =================== ========== =================================================== +Interface Property Type Description +=========== =================== ========== =================================================== +IField title unicode The title of the field. Used in the widget. + +\ description unicode A description for the field. Used in the widget. + +\ required bool Whether or not the field is required. Used for + form validation. The default is ``True``. + +\ readonly bool Whether or not the field is read-only. Default + is ``False``. + +\ default The default value for the field. Used in forms + and sometimes as a fallback value. Must be a + valid value for the field if set. The default + is ``None``. + +\ missing_value A value that represents "this field is not set". + Used by form validation. Defaults to ``None``. For + lists and tuples, it is sometimes useful to set + this to an empty list/tuple. + +IMinMaxLen min_length int The minimum required length. Used for string + fields. Default is ``0``. + +\ max_length int The maximum allowed length. Used for string + fields. Default is ``None`` (no check). + +IMinMax min The minimum allowed value. Must be a valid value + for the field, e.g. for an ``Int`` field this + should be an integer. Default is ``None`` (no + check). + +\ max The maximum allowed value. Must be a valid value + for the field, e.g. for an Int field this should + be an integer. Default is ``None`` (no check). + +ICollection value_type Another ``Field`` instance that describes the + allowable values in a list, tuple or other + collection. Must be set for any collection field. + One common usage is to set this to a ``Choice``, + to model a multi-selection field with a vocabulary. + +\ unique bool Whether or not values in the collection must be + unique. Usually not set directly – use a ``Set`` + or ``Frozenset`` to guarantee uniqueness in an + efficient way. + +IDict key_type Another ``Field`` instance that describes the + allowable keys in a dictionary. Similar to the + ``value_type`` of a collection. Must be set. + +\ value_type Another ``Field`` instance that describes the + allowable values in a dictionary. Similar to the + ``value_type`` of a collection. Must be set. + +IObject schema Interface An interface that must be provided by any object + stored in this field. + +IRichText default_mime_type str Default MIME type for the input text of a rich + text field. Defaults to ``text/html``. + +\ output_mime_type str Default output MIME type for the transformed + value of a rich text field. Defaults to + ``text/x-html-safe``. There must be a + transformation chain in the ``portal_transforms`` + tool that can transform from the input value to + the ``output`` value for the output property of + the ``RichValue`` object to contain a value. + +\ allowed_mime_types tuple A list of allowed input MIME types. The default + is ``None``, in which case the site-wide settings + (from the ``Markup`` control panel) will be used. +=========== =================== ========== =================================================== Field types ----------- @@ -101,109 +104,91 @@ grouped by the module from which they can be imported. Fields in zope.schema ~~~~~~~~~~~~~~~~~~~~~ -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Name | Type | Description | Properties | -+==================+=============+=========================================================================================================================================================================================+===================================+ -| Choice | N/A | Used to model selection from a voacabulary, which must be supplied. Often used as the *value\_type* of a selection field. The value type is the value of the terms in the vocabulary. | See `vocabularies`_. | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Bytes | str | Used for binary data. | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| ASCII | str | ASCII text (multi-line) | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| BytesLine | str | A single line of binary data, i.e. a Bytes with newlines disallowed | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| ASCIILine | str | A single line of ASCII text | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Text | unicode | Unicode text (multi-line). Often used with a WYSIWYG widget, although the default is a text area. | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| TextLine | unicode | A single line of Unicode text | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Bool | bool | True or False | IField | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Int | int, long | An integer number. Both ints and longs are allowed. | IField, IMinMax | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Float | float | A floating point number. | IField, IMinMax | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Tuple | tuple | A tuple (non-mutable) | IField, ICollection, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| List | list | A list | IField, ICollection, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Set | set | A set | IField, ICollection, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Frozenset | frozenset | A frozenset (non-mutable) | IField, ICollection, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Password | unicode | Stores a simple string, but implies a password widget. | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Dict | dict | Stores a dictionary. Both *key\_type* and *value\_type* must be set to fields. | IField, IMinMaxLen, IDict | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Datetime | datetime | Stores a Python *datetime* (not a Zope 2 *DateTime*) | IField, IMinMax | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Date | date | Stores a python *date* | IField, IMinMax | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Timedelta | timedelta | Stores a python *timedelta* | IField, IMinMax | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| SourceText | unicode | A textfield intended to store source text (e.g. HTML or Python code) | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Object | N/A | Stores a Python object that conforms to the interface given as the *schema*. There is no standard widget for this. | IField, IObject | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| URI | str | A URI (URL) string | IField, MinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Id | str | A unique identifier - either a URI or a dotted name. | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| DottedName | str | A dotted name string. | IField, IMinMaxLen | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| InterfaceField | Interface | A Zope interface. | IField | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ -| Decimal | Decimal | Stores a Python *Decimal*. Requires version 3.4 or later of *zope.schema*. Not available by default in Zope 2.10. | IField, IMinMax | -+------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+ - -Fields in plone.namedfile.field -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +================= ============ ================================================================================= ================================ +Name Type Description Properties +================= ============ ================================================================================= ================================ +Choice N/A Used to model selection from a vocabulary, which must be supplied. See `vocabularies`_. + Often used as the ``value_type`` of a selection field. The value + type is the value of the terms in the vocabulary. +Bytes str Used for binary data. IField, IMinMaxLen +ASCII str ASCII text (multi-line). IField, IMinMaxLen +BytesLine str A single line of binary data, i.e. a ``Bytes`` with newlines IField, IMinMaxLen + disallowed. +ASCIILine str A single line of ASCII text. IField, IMinMaxLen +Text unicode Unicode text (multi-line). Often used with a WYSIWYG widget, IField, IMinMaxLen + although the default is a text area. +TextLine unicode A single line of Unicode text. IField, IMinMaxLen +Bool bool ``True`` or ``False``. IField +Int int, long An integer number. Both ints and longs are allowed. IField, IMinMax +Float float A floating point number. IField, IMinMax +Tuple tuple A tuple (non-mutable). IField, ICollection, IMinMaxLen +List list A list. IField, ICollection, IMinMaxLen +Set set A set. IField, ICollection, IMinMaxLen +Frozenset frozenset A frozenset (non-mutable). IField, ICollection, IMinMaxLen +Password unicode Stores a simple string, but implies a password widget. IField, IMinMaxLen +Dict dict Stores a dictionary. Both ``key_type`` and ``value_type`` must be set to fields. IField, IMinMaxLen, IDict +Datetime datetime Stores a Python ``datetime`` (not a Zope 2 ``DateTime``). IField, IMinMax +Date date Stores a python ``date``. IField, IMinMax +Timedelta timedelta Stores a python ``timedelta``. IField, IMinMax +SourceText unicode A textfield intended to store source text (e.g. HTML or Python code). IField, IMinMaxLen +Object N/A Stores a Python object that conforms to the interface given as the IField, IObject + ``schema``. There is no standard widget for this. +URI str A URI (URL) string. IField, MinMaxLen +Id str A unique identifier – either a URI or a dotted name. IField, IMinMaxLen +DottedName str A dotted name string. IField, IMinMaxLen +InterfaceField Interface A Zope interface. IField +Decimal Decimal Stores a Python ``Decimal``. Requires version 3.4 or later of IField, IMinMax + `zope.schema`_. Not available by default in Zope 2.10. +================= ============ ================================================================================= ================================ + +Fields in ``plone.namedfile.field`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ See `plone.namedfile`_ and `plone.formwidget.namedfile`_ for more details. -+------------------+------------------+----------------------------------------------------------------------------------------------------------------------------------------+--------------+ -| Name | Type | Description | Properties | -+==================+==================+========================================================================================================================================+==============+ -| NamedFile | NamedFile | A binary uploaded file. Normally used with the widget from *plone.formwidget.namedfile*. | IField | -+------------------+------------------+----------------------------------------------------------------------------------------------------------------------------------------+--------------+ -| NamedImage | NamedImage | A binary uploaded image. Normally used with the widget from *plone.formwidget.namedfile*. | IField | -+------------------+------------------+----------------------------------------------------------------------------------------------------------------------------------------+--------------+ -| NamedBlobFile | NamedBlobFile | A binary uploaded file stored as a ZODB BLOB. Requires the [blobs] extra to *plone.namedfile*. Otherwise identical to *NamedFile*. | IField | -+------------------+------------------+----------------------------------------------------------------------------------------------------------------------------------------+--------------+ -| NamedBlobImage | NamedBlobImage | A binary uploaded image stored as a ZODB BLOB. Requires the [blobs] extra to *plone.namedfile*. Otherwise identical to *NamedImage*. | IField | -+------------------+------------------+----------------------------------------------------------------------------------------------------------------------------------------+--------------+ - -Fields in z3c.relationfield.schema -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`See z3c.relationfield for more details`_ - -+------------------+-----------------+----------------------------------------------------------------+----------------+ -| Name | Type | Description | Properties | -+==================+=================+================================================================+================+ -| Relation | RelationValue | Stores a single *RelationValue*. | IField | -+------------------+-----------------+----------------------------------------------------------------+----------------+ -| RelationList | list | A *List* field that defaults to *Relation* as the value type | See *List* | -+------------------+-----------------+----------------------------------------------------------------+----------------+ -| RelationChoice | RelationValue | A *Choice* field intended to store *RelationValue*’s | See *Choice* | -+------------------+-----------------+----------------------------------------------------------------+----------------+ - -Fields in plone.app.textfield -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`See plone.app.textfield for more details`_ - -+------------+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+ -| Name | Type | Description | Properties | -+============+=================+=======================================================================================================================================================================+=====================+ -| RichText | RichTextValue | Stores a *RichTextValue*, which encapsulates a raw text value, the source MIME type, and a cached copy of the raw text transformed to the default output MIME type. | IField, IRichText | -+------------+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+ - -.. _See plone.app.textfield for more details: http://pypi.python.org/pypi/plone.app.textfield -.. _See z3c.relationfield for more details: http://pypi.python.org/pypi/z3c.relationfield -.. _plone.namedfile: http://pypi.python.org/pypi/plone.namedfile +=============== =============== ================================================================================= ========== +Name Type Description Properties +=============== =============== ================================================================================= ========== +NamedFile NamedFile A binary uploaded file. Normally used with the widget from IField + `plone.formwidget.namedfile`_. +NamedImage NamedImage A binary uploaded image. Normally used with the widget from IField + `plone.formwidget.namedfile`_. +NamedBlobFile NamedBlobFile A binary uploaded file stored as a ZODB BLOB. Requires the ``[blobs]`` extra to IField + `plone.namedfile`_. Otherwise identical to ``NamedFile``. +NamedBlobImage NamedBlobImage A binary uploaded image stored as a ZODB BLOB. Requires the ``[blobs]`` extra to IField + `plone.namedfile`_. Otherwise identical to ``NamedImage``. +=============== =============== ================================================================================= ========== + +Fields in ``z3c.relationfield.schema`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See `z3c.relationfield`_ for more details. + +================= ================ ================================================================ =============== +Name Type Description Properties +================= ================ ================================================================ =============== +Relation RelationValue Stores a single ``RelationValue``. IField +RelationList list A ``List`` field that defaults to ``Relation`` as the value type See ``List`` +RelationChoice RelationValue A ``Choice`` field intended to store ``RelationValue``’s See ``Choice`` +================= ================ ================================================================ =============== + +Fields in `plone.app.textfield`_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See `plone.app.textfield`_ for more details. + +========= ============== ====================================================================================== ================== +Name Type Description Properties +========= ============== ====================================================================================== ================== +RichText RichTextValue Stores a ``RichTextValue``, which encapsulates a raw text value, the source MIME type, IField, IRichText + and a cached copy of the raw text transformed to the default output MIME type. +========= ============== ====================================================================================== ================== + +.. _creating schemata: ../schema-driven-types.html#the-schema +.. _plone.app.textfield: http://pypi.python.org/pypi/plone.app.textfield .. _plone.formwidget.namedfile: http://pypi.python.org/pypi/plone.formwidget.namedfile +.. _plone.namedfile: http://pypi.python.org/pypi/plone.namedfile .. _vocabularies: ../advanced/vocabularies.html -.. _creating schemata: ../schema-driven-types.html#the-schema +.. _z3c.relationfield: http://pypi.python.org/pypi/z3c.relationfield +.. _zope.schema: http://pypi.python.org/pypi/zope.schema diff --git a/source/reference/widgets.txt b/source/reference/widgets.txt index 74ceeb2..7f84b03 100644 --- a/source/reference/widgets.txt +++ b/source/reference/widgets.txt @@ -10,31 +10,21 @@ content types, see the `schema introduction`_. The table below shows some commonly used custom widgets. -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Widget | Imported from | Field | Description | -+===============================+=================================+=================+===============================================================================================================================================================+ -| WysiwygFieldWidget | plone.app.z3cform.wysiwyg | Text | Use Plone’s standard WYSIWYG HTML editor on a standard text field. Note that if you used a *RichText* field, you will get the WYSIWYG editor automatically. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| RichTextWidget | plone.app.textfield.widget | RichText | Use Plone’s standard WYSIWYG HTML editor on a *RichText* field. This also allows text-based markup such as reStructuredText. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| AutocompleteFieldWidget | plone.formwidget.autocomplete | Choice | Autocomplete widget based on jQuery Autocomplete. Requires a Choice field with a query source. See `vocabularies`_. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| AutocompleteMultiFieldWidget | plone.formwidget.autocomplete | Collection | Multi-select version of the above. Used for a List, Tuple, Set or Frozenset with a Choice value\_type. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| ContentTreeFieldWidget | plone.formwidget.contenttree | RelationChoice | Content browser. Requires a query source with content objects as values. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| MultiContentTreeFieldWidget | plone.formwidget.contenttree | RelationList | Content browser. Requires a query source with content objects as values. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| NamedFileFieldWidget | plone.formwidget.namedfile | NamedFile | A file upload widget | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| NamedImageFieldWidget | plone.formwidget.namedimage | NamedImage | An image upload widget | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| TextLinesFieldWidget | plone.z3cform.textlines | Collection | One-per-line list entry for List, Tuple, Set or Frozenset fields. Requires a value_type of TextLine or ASCIILine. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| SingleCheckBoxFieldWidget | z3c.form.browser.checkbox | Bool | A single checkbox for true/false. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| CheckBoxFieldWidget | z3c.form.browser.checkbox | Collection | A set of checkboxes. Used for Set or Frozenset fields with a Choice value_type and a vocabulary. | -+-------------------------------+---------------------------------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+ +============================== ================================= ================= ============================================================================================================================================================= + Widget Imported from Field Description +============================== ================================= ================= ============================================================================================================================================================= + WysiwygFieldWidget plone.app.z3cform.wysiwyg Text Use Plone’s standard WYSIWYG HTML editor on a standard text field. Note that if you used a *RichText* field, you will get the WYSIWYG editor automatically. + RichTextWidget plone.app.textfield.widget RichText Use Plone’s standard WYSIWYG HTML editor on a *RichText* field. This also allows text-based markup such as reStructuredText. + AutocompleteFieldWidget plone.formwidget.autocomplete Choice Autocomplete widget based on jQuery Autocomplete. Requires a Choice field with a query source. See `vocabularies`_. + AutocompleteMultiFieldWidget plone.formwidget.autocomplete Collection Multi-select version of the above. Used for a List, Tuple, Set or Frozenset with a Choice value\_type. + ContentTreeFieldWidget plone.formwidget.contenttree RelationChoice Content browser. Requires a query source with content objects as values. + MultiContentTreeFieldWidget plone.formwidget.contenttree RelationList Content browser. Requires a query source with content objects as values. + NamedFileFieldWidget plone.formwidget.namedfile NamedFile A file upload widget + NamedImageFieldWidget plone.formwidget.namedimage NamedImage An image upload widget + TextLinesFieldWidget plone.z3cform.textlines Collection One-per-line list entry for List, Tuple, Set or Frozenset fields. Requires a value_type of TextLine or ASCIILine. + SingleCheckBoxFieldWidget z3c.form.browser.checkbox Bool A single checkbox for true/false. + CheckBoxFieldWidget z3c.form.browser.checkbox Collection A set of checkboxes. Used for Set or Frozenset fields with a Choice value_type and a vocabulary. +============================== ================================= ================= ============================================================================================================================================================= .. _z3c.form documentation: http://docs.zope.org/z3c.form/widget.html .. _schema introduction: ../schema-driven-types.html#the-schema diff --git a/source/schema-driven-types.txt b/source/schema-driven-types.txt index d43e6ef..205c86e 100644 --- a/source/schema-driven-types.txt +++ b/source/schema-driven-types.txt @@ -9,19 +9,19 @@ The schema **Writing a schema for the type** A simple Dexterity types consists of a schema and an FTI (Factory Type -Information, the object configured in *portal\_types* in the ZMI). We’ll +Information, the object configured in :guilabel:`portal_types` in the ZMI). We’ll create the schemata here, and the FTI on the next page. Each schema is typically in a separate module. Thus, we will add three -files to our product: *presenter.py*, *program.py*, and *session.py*. +files to our product: ``presenter.py``, ``program.py``, and ``session.py``. Each will start off with a schema interface. First, we will define a message factory to aid future internationalisation of the package. Every string that is presented to -the user should be wrapped in *\_()* as shown with the titles and +the user should be wrapped in ``_()`` as shown with the titles and descriptions below. -The message factory lives in the package root *\_\_init\_\_.py* file: +The message factory lives in the package root ``__init__.py`` file: .. code-block:: python @@ -33,7 +33,7 @@ Notice how we use the package name as the translation domain. We can now define the schemata for our three types. -For the Presenter type, *presenter.py* looks like this: +For the Presenter type, ``presenter.py`` looks like this: .. code-block:: python @@ -76,7 +76,7 @@ description metadata used in Plone’s folder listings and searches, which defaults to these fields. In general, every type should have a title field, although it could be provided by behaviors (more on those later). -For the *Program* type, *program.py* looks like this: +For the *Program* type, ``program.py`` looks like this: .. code-block:: python @@ -116,7 +116,7 @@ For the *Program* type, *program.py* looks like this: required=False, ) -Finally, session.py for the Session type looks like this: +Finally, ``session.py`` for the Session type looks like this: .. code-block:: python @@ -152,9 +152,9 @@ Schema interfaces vs. other interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As you may have noticed, each schema is basically just an interface -(*zope.interface.Interface)* with fields. The standard fields are found -in the *zope.schema* package. You should look at its interfaces -(*parts/omelette/zope/schema/interfaces.py*) to learn about the various +(``zope.interface.Interface``) with fields. The standard fields are found +in the `zope.schema`_ package. You should look at its interfaces +(``parts/omelette/zope/schema/interfaces.py``) to learn about the various schema fields available, and review the `online documentation`_ for the package. You may also want to look up `plone.namedfile`_, which you can use if you require a file field, `z3c.relationfield`_, which can be used @@ -162,22 +162,24 @@ for references, and `plone.app.textfield`_, which supports rich text with a WYSIWYG editor. We will cover these field types later in this manual. They can also be found in the reference at the end. -Unlike a standard interface, however, we are deriving from *form.Schema* -(actually, *plone.directives.form.Schema*). This is just a marker +Unlike a standard interface, however, we are deriving from ``form.Schema`` +(actually, ``plone.directives.form.Schema``). This is just a marker interface that allows us to add some form hints to the interface, which -are then used by Dexterity (actually, the *plone.autoform* package) to +are then used by Dexterity (actually, the `plone.autoform`_ package) to construct forms. Take a look at the `plone.directives.form`_ documentation to learn more about the various hints that are possible. -The most common ones are *form.fieldset()*, to define groups of fields, -*form.widget()*, to set a widget for a particular field, and -*form.omit()* to hide one or more fields from the form. We will see +The most common ones are ``form.fieldset()``, to define groups of fields, +``form.widget()``, to set a widget for a particular field, and +``form.omit()`` to hide one or more fields from the form. We will see examples of these later in the manual. +.. _zope.schema: .. _online documentation: http://pypi.python.org/pypi/zope.schema .. _plone.namedfile: http://pypi.python.org/pypi/plone.namedfile .. _z3c.relationfield: http://pypi.python.org/pypi/z3c.relationfield .. _plone.app.textfield: http://pypi.python.org/pypi/plone.app.textfield .. _plone.directives.form: http://pypi.python.org/pypi/plone.directives.form +.. _plone.autoform: http://pypi.python.org/pypi/plone.autoform The FTI -------- @@ -187,7 +189,7 @@ The FTI With the schema in place, we just need to make our types installable. We do this with GenericSetup. -First, we add a *types.xml* file to *profiles/default*: +First, we add a ``types.xml`` file to ``profiles/default``: .. code-block:: xml @@ -198,14 +200,14 @@ First, we add a *types.xml* file to *profiles/default*: We use the package name as a prefix and the type name in lowercase to -create a unique name. It is important that the *meta\_type* is +create a unique name. It is important that the ``meta_type`` is *Dexterity FTI*. We then need to add an XML file for each of the types, where the file name matches the type name. First, we add a directory -*profiles/default/types*, and then add the following: +``profiles/default/types``, and then add the following: -For the *Presenter* type, we have *example.conference.presenter.xml*: +For the *Presenter* type, we have ``example.conference.presenter.xml``: .. code-block:: xml @@ -267,29 +269,29 @@ know what you can change. The important lines here are: -- The *name* attribute on the root element must match the name in - *types.xml* and the filename +- The ``name`` attribute on the root element must match the name in + ``types.xml`` and the filename - We use the package name as the translation domain again, via - *i18n:domain* + ``i18n:domain`` - We set a title and description for the type - We also specify an icon. Here, we use a standard icon from Plone’s - *plone\_images* skin layer. You’ll learn more about static resources + ``plone_images`` skin layer. You’ll learn more about static resources later. -- We set *global\_allow* to *True*. This means that the type will be +- We set ``global_allow`` to ``True``. This means that the type will be addable in standard folders. -- The schema interface is referenced by the *schema* property. -- We set the *klass* property to the standard - *plone.dexterity.content.Item*. There is also - *plone.dexterity.content.Container*. +- The schema interface is referenced by the ``schema`` property. +- We set the ``klass`` property to the standard + ``plone.dexterity.content.Item``. There is also + ``plone.dexterity.content.Container``. - We specify the name of an add permission. The default - *cmf.AddPortalContent* should be used unless you configure a custom + ``cmf.AddPortalContent`` should be used unless you configure a custom permission. Custom permissions are convered later in this manual. - We add a *behavior*. Behaviors are re-usable aspects providing - semantics and/or schema fields. Here, we add the *INameFromTitle* + semantics and/or schema fields. Here, we add the ``INameFromTitle`` behavior, which will give our content object a readable id based on - the *title* property. We’ll cover other behaviors later. + the ``title`` property. We’ll cover other behaviors later. -The *Session* type, in *example.conference.session.xml*, is very +The ``Session`` type, in ``example.conference.session.xml``, is very similar: .. code-block:: xml @@ -345,10 +347,10 @@ similar: -Again, this is an Item. Here, we have set *global\_allow* to False, +Again, this is an Item. Here, we have set ``global_allow`` to ``False``, since these objects should only be addable inside a *Program*. -The *Program*, in *example.conference.program.xml*, looks like this: +The ``Program``, in ``example.conference.program.xml``, looks like this: .. code-block:: xml @@ -405,9 +407,9 @@ The *Program*, in *example.conference.program.xml*, looks like this: -The difference here is that we use the *Container* class, and we filter -the containable types (*filter\_content\_types* and -*allowed\_content\_types*) to allow only *Sessions* to be added inside +The difference here is that we use the ``Container`` class, and we filter +the containable types (``filter_content_types`` and +``allowed_content_types``) to allow only ``Sessions`` to be added inside this folder. Testing the type @@ -416,9 +418,9 @@ Testing the type **How to start up Plone and test the type, and some trouble-shooting tips.** With a schema and FTI for each type, and our GenericSetup profile -registered in configure.zcml, we should be able to test our type. Make -sure that you have run a buildout, and then start *./bin/instance fg* as -normal. Add a Plone site, and go to the *portal\_quickinstaller* in the +registered in ``configure.zcml``, we should be able to test our type. Make +sure that you have run a buildout, and then start ``./bin/instance fg`` as +normal. Add a Plone site, and go to the :guilabel:`portal_quickinstaller` in the ZMI. You should see your package there and be able to install it. Once installed, you should be able to add objects of the new content @@ -427,44 +429,44 @@ types. If Zope doesn’t start up: - Look for error messages on the console, and make sure you start in - the foreground with *./bin/instance fg*. You could have a syntax + the foreground with ``./bin/instance fg``. You could have a syntax error or a ZCML error. -If you don’t see your package in *portal\_quickinstaller*: +If you don’t see your package in :guilabel:`portal_quickinstaller`: -- Ensure that the package is either checked out by *mr.developer* or - that you have a *develop* line in *buildout.cfg* to load it as a - develop egg. *develop = src/\** should suffice, but you can also add - the package explicitly, e.g. with *develop - =src/example.conference.* +- Ensure that the package is either checked out by ``mr.developer`` or + that you have a ``develop`` line in ``buildout.cfg`` to load it as a + develop egg. ``develop = src/*`` should suffice, but you can also add + the package explicitly, e.g. with + ``develop = src/example.conference.`` - Ensure that the package is actually loaded as an egg. It should be - referenced in the *eggs* section under *[instance]* . + referenced in the ``eggs`` section under ``[instance]`` . - You can check that the package is correctly configured in the - buildout by looking at the generated *bin/instance* script - (*bin\\instance-script.py* on Windows). There should be a line for + buildout by looking at the generated ``bin/instance`` script + (``bin\instance-script.py`` on Windows). There should be a line for your package in the list of eggs at the top of the file. - Make sure that the package’s ZCML is loaded. You can do this by - installing a ZCML slug (via the *zcml* option in the *[instance]* - section of *buildout.cfg*) or by adding an ** line in - another package’s *configure.zcml*. However, the easiest way with - Plone 3.3 and later is to add the *z3c.autoinclude.plugin* entry - point to *setup.py*. -- Ensure that you have added a ** - stanza to *configure.zcml*. + installing a ZCML slug (via the ``zcml`` option in the ``[instance]`` + section of ``buildout.cfg``) or by adding an ```` line in + another package’s ``configure.zcml``. However, the easiest way with + Plone 3.3 and later is to add the ``z3c.autoinclude.plugin`` entry + point to ``setup.py``. +- Ensure that you have added a ```` + stanza to ``configure.zcml``. -If the package fails to install in *portal\_quickinstaller*: +If the package fails to install in :guilabel:`portal_quickinstaller`: -- Look for errors in the *error\_log* at the root of the Plone site, in +- Look for errors in the :guilabel:`error_log` at the root of the Plone site, in your console, or in your log files. - Check the syntax and placement of the profile files. Remember that - you need a *types.xml* listing your types, and corresponding files in - *types/\*.xml*. + you need a ``types.xml`` listing your types, and corresponding files in + ``types/*.xml``. If your forms do not look right (e.g. you are missing custom widgets): -- Make sure your schema derives from *form.Schema*. +- Make sure your schema derives from ``form.Schema``. - Remember that the directives require you to specify the correct field name, even if they are placed before or after the relevant field. -- Check that you have a ** line in - *configure.zcml*. +- Check that you have a ```` line in + ``configure.zcml``. diff --git a/source/testing/integration-tests.txt b/source/testing/integration-tests.txt index 1071dc2..ba55b55 100644 --- a/source/testing/integration-tests.txt +++ b/source/testing/integration-tests.txt @@ -3,39 +3,38 @@ Integration tests **Writing integration tests with PloneTestCase** -We’ll now add some integration tests for our type. These should ensure -that the package installs cleanly, and that our custom types are addable -in the right places and have the right schemata, at the very least. +We’ll now add some integration tests for our type. +These should ensure that the package installs cleanly, and that our custom +types are addable in the right places and have the right schemata, at the +very least. To help manage test setup, we’ll make use of the Zope test runner’s -concept of *layers*. Layers allow common test setup (such as configuring -a Plone site and installing a product) to take place once and be re-used -by multiple test cases. Those test cases can still modify the -environment, but their changes will be torn down and the environment -reset to the layer’s initial state between each test, facilitating test -isolation. - -As the name implies, layers are, erm, layered. One layer can extend -another. If two test cases in the same test run use two different layers -with a common ancestral layer, the ancestral layer is only set up and -torn down once. +concept of *layers*. +Layers allow common test setup (such as configuring a Plone site and +installing a product) to take place once and be re-used by multiple test +cases. +Those test cases can still modify the environment, but their changes will be +torn down and the environment reset to the layer’s initial state between +each test, facilitating test isolation. + +As the name implies, layers are, erm, layered. +One layer can extend another. +If two test cases in the same test run use two different layers with a +common ancestral layer, the ancestral layer is only set up and torn down +once. We’ll use `collective.testcaselayer`_ to write and manage layers. We -need to depend on this, so in *setup.py*, we have: +need to depend on this, so in ``setup.py``, we have:: -:: - - install_requires=[ - ... - 'collective.testcaselayer', - ], + install_requires=[ + ... + 'collective.testcaselayer', + ], .. note:: - Don’t forget to re-run buildout after making changes to *setup.py*. - -We then add our own layer to *tests/layer.py*: + Don’t forget to re-run buildout after making changes to ``setup.py``. -:: +We then add our own layer to ``tests/layer.py``:: from Products.PloneTestCase import ptc import collective.testcaselayer.ptc @@ -51,17 +50,17 @@ We then add our own layer to *tests/layer.py*: Layer = IntegrationTestLayer([collective.testcaselayer.ptc.ptc_layer]) This extends a base layer that sets up Plone, and adds some custom layer -setup for our package, in this case installing the *example.conference* -extension profile. We could also perform additional setup here, such as -creating some initial content or setting the default roles for the test -run. See the *collective.testcaselayer* documentation for more details. - -To use the layer, we can create a new test case based on *PloneTestCase* -that uses our layer. We’ll add one to *test\_program.py* first. (In the -code snippet below, the unit test we created previously has been removed -to conserve space.) - -:: +setup for our package, +in this case installing the ``example.conference`` extension profile. +We could also perform additional setup here, such as creating some initial +content or setting the default roles for the test run. +See the `collective.testcaselayer`_ documentation for more details. + +To use the layer, we can create a new test case based on ``PloneTestCase`` +that uses our layer. +We’ll add one to ``test_program.py`` first. +(In the code snippet below, the unit test we created previously has been +removed to conserve space.):: import unittest @@ -134,52 +133,55 @@ to conserve space.) return unittest.defaultTestLoader.loadTestsFromName(__name__) This illustrates a basic set of tests that make sense for most content -types. There are many more things we could test (for example, we could -test the add permissions more thoroughly, and we ought to test the -*sessions()* method on the view with some actual content!), but even -this small set of integration tests tells us that our product has -installed, that the content type is addable, that it has the right -factory, and that instances of the type provide the right schema -interface. +types. +There are many more things we could test +(for example, we could test the add permissions more thoroughly, +and we ought to test the ``sessions()`` method on the view with some actual +content!), +but even this small set of integration tests tells us that +our product has installed, +that the content type is addable, +that it has the right factory, and +that instances of the type provide the right schema interface. There are some important things to note about this test case: -- We extend *PloneTestCase*, which means we have access to a full Plone - integration test environment. See the `testing tutorial`_ for more - details. -- We set the *layer* attribute to our custom layer. This means that all - tests in our test case will have the *example.conference:default* - profile installed. -- We test that the content is addable (here, as a normal member in - their member folder, since that is the default security context for - the test - use *self.setRoles([‘Manager’])* to get the *Manager* role - and *self.portal* to access the portal root), that the FTI is - installed and can be located, and that both the FTI and instances of - the type know about the correct type schema. -- We also test that the view can be looked up and has the correct - methods. We’ve not included a full functional test (e.g. using - *zope.testbrowser*) or any other front-end testing here. If you - require those, take a look at the testing tutorial. -- We also test that our custom indexers are working, by creating an - appropriate object and searching for it again. Note that we need to - reindex the object after we’ve modified it so that the catalog is up - to date. -- The *defaultTestLoader* will find this test and load it, just as it - found the *TestProgramUnit* test case. +- We extend ``PloneTestCase``, which means we have access to a full Plone + integration test environment. + See the `testing tutorial`_ for more details. +- We set the ``layer`` attribute to our custom layer. + This means that all tests in our test case will have the + ``example.conference:default`` profile installed. +- We test that the content is addable (here, as a normal member in + their member folder, since that is the default security context for + the test – use ``self.setRoles([‘Manager’])`` to get the ``Manager`` role + and ``self.portal`` to access the portal root), + that the FTI is installed and can be located, and + that both the FTI and instances of the type know about the correct type + schema. +- We also test that the view can be looked up and has the correct methods. + We’ve not included a fully functional test (e.g. using + ``zope.testbrowser``) or any other front-end testing here. + If you require those, take a look at the testing tutorial. +- We also test that our custom indexers are working, + by creating an appropriate object and searching for it. + Note that we need to reindex the object after we’ve modified it so that + the catalog is up to date. +- The ``defaultTestLoader`` will find this test and load it, just as it + found the ``TestProgramUnit`` test case. To run our tests, we can still do. -:: +.. code-block:: console $ ./bin/test example.conference -You should now notice layers being set up and torn down. Again, use the -*-t* option to run a particular test case (or test method) only. - -The other tests are similar. We have *tests/test\_session.py* to test -the *Session* type: +You should now notice layers being set up and torn down. +Again, use the ``-t`` option to run a particular test case (or test method) +only. -:: +The other tests are similar. We have ``tests/test_session.py`` to test +the ``Session`` type:: import unittest @@ -255,15 +257,14 @@ the *Session* type: def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) -Notice here how we test that the *Session* type cannot be added directly -to a folder, and that it can be added inside a program. We also add a -test for the *possible\_tracks()* vocabulary method, as well as tests -for the installation of the *track* index and metadata column and the -custom workflow. - -And in *tests/test\_presenter.py*, we test the *Presenter* type: +Notice here how we test +that the ``Session`` type cannot be added directly to a folder, and +that it can be added inside a program. +We also add a test for the ``possible_tracks()`` vocabulary method, +as well as tests for the installation of the ``track`` index and metadata +column and the custom workflow. -:: +And in ``tests/test_presenter.py``, we test the ``Presenter`` type:: import unittest @@ -308,37 +309,37 @@ Faster tests with Roadrunner ---------------------------- You will have noticed that running unit tests was much quicker than -running integration tests. That is unfortunate, but to be expected: the -integration test setup basically requires starting all of Zope and -configuring a Plone site. +running integration tests. +That is unfortunate, but to be expected: the integration test setup +basically requires starting all of Zope and configuring a Plone site. Luckily, there is a tool that we can use to speed things up, and if you’ve been following along the tutorial, you already have it in your -buildout: `Roadrunner`_. This is a command that takes the place of -*./bin/instance test* that preloads the Zope environment and allows you -to re-run tests much faster. +buildout: `Roadrunner`_. +This is a command that takes the place of ``./bin/instance test`` that +preloads the Zope environment and allows you to re-run tests much faster. To run our tests with roadrunner, we would do: -:: +.. code-block:: console $ ./bin/roadrunner -s example.conference This runs the tests once, and then drops to the Roadrunner prompt: -:: +.. code-block:: console rr> -Simply hitting enter here, or typing a command like *test**-s -example.conference* will re-run your tests, this time taking much less -time. +Simply hitting enter here, or typing a command like +``test -s example.conference`` will re-run your tests, +this time taking much less time. -Roadrunner works best when you are adding and debugging your tests. For -example, it’s a very quick way to get to a *pdb* prompt: just set a -breakpoint in your test with *import pdb; pdb.set\_trace()* and re-run -it in roadrunner. You can then step into your test code and the code -under test. +Roadrunner works best when you are adding and debugging your tests. +For example, it’s a very quick way to get to a ``pdb`` prompt: just set a +breakpoint in your test with ``import pdb; pdb.set_trace()`` and re-run +it in roadrunner. +You can then step into your test code and the code under test. Roadrunner should pick up changes to your tests automatically. However, it may not pick up changes to your application code, grokked components diff --git a/source/testing/unit-tests.txt b/source/testing/unit-tests.txt index a19b7b9..c84f59a 100644 --- a/source/testing/unit-tests.txt +++ b/source/testing/unit-tests.txt @@ -3,23 +3,24 @@ Unit tests **Writing simple unit tests** -As all good developers know, automated tests are very important! If you -are not comfortable with automated testing and test-driven development, -you should read the `Plone testing tutorial`_. In this section, we will -assume you are familiar with Plone testing basics, and show some tests -that are particularly relevant to our example types. - -Firstly, we will add a few unit tests. Recall that unit tests are simple -tests for a particular function or method, and do not depend on an -outside environment being set up. As a rule of thumb, if something can -be tested with a simple unit test, do so, because: - -- Unit tests are quick to write -- They are also quick to run -- Because they are more isolated, you are less likely to have tests - that pass or fail due to incorrect assumptions or by luck -- You can usually test things more thoroughly and exhaustively with - unit tests than with (slower) integration tests +As all good developers know, automated tests are very important! +If you are not comfortable with automated testing and test-driven +development, you should read the `Plone testing tutorial`_. +In this section, we will assume you are familiar with Plone testing basics, +and show some tests that are particularly relevant to our example types. + +Firstly, we will add a few unit tests. +Recall that unit tests are simple tests for a particular function or method, +and do not depend on an outside environment being set up. +As a rule of thumb, if something can be tested with a simple unit test, do +so, because: + +- Unit tests are quick to write. +- They are also quick to run. +- Because they are more isolated, you are less likely to have tests + that pass or fail due to incorrect assumptions or by luck. +- You can usually test things more thoroughly and exhaustively with + unit tests than with (slower) integration tests. You’ll typically supplement a larger number of unit tests with a smaller number of integration tests, to ensure that your application’s correctly @@ -28,18 +29,17 @@ wired up and working. That’s the theory, at least. When we’re writing content types, we’re often more interested in integration test, because a type schema and FTI are more like configuration of the Plone and Dexterity frameworks than -imperative programming. We can’t “unit test” the type’s schema -interface, but we can and should test that the correct schema is picked -up and used when our type is installed. We will often write unit tests -(with mock objects, where required) for custom event handlers, default -value calculation functions and other procedural code. +imperative programming. +We can’t “unit test” the type’s schema interface, but we can and should test +that the correct schema is picked up and used when our type is installed. +We will often write unit tests (with mock objects, where required) for +custom event handlers, default value calculation functions and other +procedural code. In that spirit, let’s write some unit tests for the default value -handler and the invariant in *program.py*. We’ll add the directory -*tests*, with an *\_\_init\_\_.py* and a file *test\_program.py* that -looks like this: - -:: +handler and the invariant in ``program.py``. +We’ll add the directory ``tests``, with an ``__init__.py`` and a file +``test_program.py`` that looks like this:: import unittest import datetime @@ -104,24 +104,26 @@ looks like this: def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) -This is a simple test using the Python standard library’s *unittest* +This is a simple test using the Python standard library’s ``unittest`` module. There are a few things to note here: -- We have created a dummy class to simulate a *Program* instance. It - doesn’t contain anything at all, but we set some attributes onto it - for certain tests. This is a very simple way to do mocks. There are - much more sophisticated mock testing approaches, but starting simple - is good. -- Each test is self contained. There is no test layer or test case - setup/tear-down. -- We use the *defaultTestLoader* to load all test classes in the module - automatically. The test runner will look for modules in the *tests* - package with names starting with *test\** that have a *test\_suite()* - method to get test suites. +- We have created a dummy class to simulate a ``Program`` instance. It + doesn’t contain anything at all, but we set some attributes onto it + for certain tests. + This is a very simple way to do mocks. + There are much more sophisticated mock testing approaches, but starting + simple is good. +- Each test is self contained. There is no test layer or test case + setup/tear-down. +- We use the ``defaultTestLoader`` to load all test classes in the module + automatically. + The test runner will look for modules in the ``tests`` + package with names starting with ``test`` that have a ``test_suite()`` + method to get test suites. To run the tests, we can do: -:: +.. code-block:: console $ ./bin/text example.conference @@ -129,15 +131,16 @@ Hopefully it should show five passing tests. .. note:: - This uses the testrunner configured via the *[test]* part in our - *buildout.cfg*. This provides better test reporting and a few more - advanced options (like output colouring). We could also use the built-in - test runner in the *instance* script, e.g. with *./bin/instance test -s - example.conference*. + This uses the testrunner configured via the ``[test]`` part in our + ``buildout.cfg``. + This provides better test reporting and a few more advanced options + (like output colouring). + We could also use the built-in test runner in the ``instance`` script, + e.g. with ``./bin/instance test -s example.conference``. To run just this test suite, we can do: -:: +.. code-block:: console $ ./bin/test example.conference -t TestProgramUnit @@ -146,7 +149,7 @@ e.g. because they are integration tests and require lengthy setup. To get a report about test coverage, we can run: -:: +.. code-block:: console $ ./bin/test example.conference --coverage