diff -r d7dd088ed557 -r 097b0c025eec src/source/developer_guide/howto-adapter.rst --- a/src/source/developer_guide/howto-adapter.rst Tue Dec 11 16:43:45 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -.. _adapterhowto: - - -How to define an annotations adapter? -===================================== - -Adapters are important concepts of ZCA and PyAMS framework. If you don't know what are adapters, see :ref:`zca`. - - -What are annotations? -+++++++++++++++++++++ - -When an adapter have to add persistent attributes to a persistent class, it can add these attributes directly into -persistent instances. But this way can lead to conflicts when several adapters want to use the same attribute name for -different kinds of information. - -Annotations are an elegant way to handle this use case: they are based on a BTree which is stored into a -specific instance attribute (*__annotations__*). Any adapter can then use this dictionary to store it's own -informations, using it's own namespace as dictionary key. - -ZODB browser allows you to display existing annotations: - -.. image:: _static/annotations-1.png - -This example displays several annotations, each using it's own namespace: - -.. image:: _static/annotations-2.png - - -Designing interfaces -++++++++++++++++++++ - -The first step with ZCA is to design your interfaces. - -The are going to base our example on PyAMS_content 'paragraphs' component: a content class is marked as a -*paragraphs container target*, a class that can store paragraphs. But the real storage of paragraphs is done by -another *container* class: - -.. code-block:: python - :linenos: - - from zope.annotation.interfaces import IAttributeAnnotatable - from zope.containers.constraints import containers, contains - - - class IBaseParagraph(Interface): - """Base paragraph interface""" - - containers('.IParagraphContainer') - - - class IParagraphContainer(IOrderedContainer): - """Paragraphs container""" - - contains(IBaseParagraph) - - - class IParagraphContainerTarget(IAttributeAnnotatable): - """Paragraphs container marker interface""" - - - PARAGRAPH_CONTAINER_KEY = 'pyams_content.paragraph' - - -- line 5 to 8: :class:`IBaseParagraph` is the base interface for all paragraphs; constraint implies that paragraphs - can only be stored in a container implementing :class:`IParagraphContainer` interface. -- line 11 to 14: :class:`IParagraphContainer` is the base interface for paragraphs containers; constraint implies that - such a container can only contain objects implementing :class:`IBaseParagraph` interface. -- line 17 to 18: :class:`IParagraphContainerTarget` is only a *marker* interface which doesn't provide any method or - attribute; it only inherits from :class:`IAttributeAnnotatable`, which implies that classes implementing this - interface allows other classes to add informations as annotations through a dedicated *__annotations__* attribute. -- line 21: this is the key which will be used to store our annotation. - - -Creating persistent classes -+++++++++++++++++++++++++++ - -The first step is to declare that a given content class can store paragraphs: - -.. code-block:: python - :linenos: - - from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget - from zope.interface import implementer - - @implementer(IParagraphContainerTarget) - class WfNewsEvent(WfSharedContent): - """News event class""" - -Here we just say "Well, I'm a shared content, and I'm OK to store paragraphs!". - -So we can design the paragraphs container class. It's this class which will *really* store the paragraphs: - -.. code-block:: python - :linenos: - - @implementer(IParagraphContainer) - class ParagraphContainer(BTreeOrderedContainer): - """Paragraphs container""" - -The paragraphs container class inherits from a :class:`BTreeOrderedContainer` and implements -:class:`IParagraphContainer`. - -The last operation is to create the adapter, which is the *glue* between the *target* class and the paragraphs -container: - -.. code-block:: python - :linenos: - - from pyams_utils.adapter import adapter_config, get_annotation_adapter - - @adapter_config(context=IParagraphContainerTarget, provides=IParagraphContainer) - def paragraph_container_factory(target): - """Paragraphs container factory""" - return get_annotation_adapter(target, - PARAGRAPH_CONTAINER_KEY, - ParagraphContainer, - name='++paras++') - -PyAMS provides a shortcut to create an annotation adapter in :func:`pyams_utils.adapter.get_annotation_adapter`. -It's mandatory arguments are: - -- **context** (line 6): the context to which the adapter is applied -- **key** (line 7): the string key used to access and store context's annotations -- **factory** (line 8): if the requested annotation is missing, a new one is created using this factory (which can be a class or - a function) - -Optional arguments are: - -- **markers** (None by default): if set, should be a list of marker interfaces which will be assigned to object - created by the factory -- **notify**: if *True* (default), an :class:`ObjectCreatedEvent` event is notified on object creation -- **locate**: if *True* (default), context is set as *parent* of created object -- **parent**: if *locate* is True and if *parent* is set, this is the object to which the new object should be *parented* - instead of initial context -- **name** (None by default): some objects need to be traversed, especially when you have to be able to access them through an URL; this - is the name given to created object. - - -Using your adapter -++++++++++++++++++ - -Starting from your *content* object, it's then very simple to access to the paragraphs container: - -.. code-block:: python - :linenos: - - event = WfNewsEvent() - paragraphs_container = IParagraphContainer(event, None) - -And that's it! From now I can get access to all paragraphs associated with my initial content!! - - -Managing traversal -++++++++++++++++++ - -As said before, sometimes you have to be able to *traverse* from an initial content to a given sub-content -managed by an adapter. - -PyAMS defines a custom :class:`pyams_utils.traversing.NamespaceTraverser`: when a request traversing subpath is -starting with '++' characters, it is looking for a named traverser providing :class:`ITraversable` interface -to the last traversed object. - -.. code-block:: python - :linenos: - - @adapter_config(name='paras', context=IParagraphContainerTarget, provides=ITraversable) - class ParagraphContainerNamespace(ContextAdapter): - """++paras++ namespace adapter""" - - def traverse(self, name, furtherpath=None): - return IParagraphContainer(self.context) - -- line 1: the adapter is named "paras"; this is matching the *++paras++* name which was given to our annotation adapter -- line 2: the adapter is just a simple context adapter, so inheriting from :class:`pyams_utils.adapter.ContextAdapter` -- lines 5 to 6: the *traverse* method is used to access the adapted content; if a name like "++ns++value" is given - to an adapted object, the "value" part is given as *name" argument. - -From now, as soon as an URL like "/mycontent/++paras++/" will be used, you will get access to the paragraphs container. -This is a standard BTree container, so will get access to it's sub-objects by key.