Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lots of tidying, sorry for the noise. #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -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
============

Expand Down
6 changes: 2 additions & 4 deletions buildout.cfg
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -23,6 +20,7 @@ eggs =
collective.sphinx.includedoc
collective.sphinx.autoatschema


[versions]
roadrunner = 0.2.3.1
zc.recipe.egg=1.2.0
Expand Down
15 changes: 8 additions & 7 deletions source/advanced/behaviours.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,15 +28,16 @@ and imported using GenericSetup:
</property>

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 *<plone:behavior />* ZCML
Behaviors are normally registered with the ``<plone:behavior />`` 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
381 changes: 220 additions & 161 deletions source/advanced/catalog-indexing-strategies.txt

Large diffs are not rendered by default.

258 changes: 137 additions & 121 deletions source/advanced/custom-add-and-edit-forms.txt

Large diffs are not rendered by default.

95 changes: 47 additions & 48 deletions source/advanced/custom-content-classes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,33 @@ 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

class MyItem(Item):
"""A custom content class"""
...

For a container type, we’d do:

::
For a container type, we’d do::

from plone.dexterity.content import Container

Expand All @@ -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

<property name="klass">my.package.myitem.MyItem</property>
<property name="klass">my.package.myitem.MyItem</property>

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
23 changes: 12 additions & 11 deletions source/advanced/defaults.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
91 changes: 47 additions & 44 deletions source/advanced/event-handlers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 *<subscriber />*
``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 ``<subscriber />``
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):
Expand All @@ -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
Loading