# HG changeset patch # User Damien Correia # Date 1545409714 -3600 # Node ID 5a82a9a2ea46776aeba5d698225d18eb990f0f7b # Parent 9ab9f762abed2eb4f1c34ac65336f8fc6fb57f91 Merged doc diff -r 9ab9f762abed -r 5a82a9a2ea46 src/source/dev_guide/custom-skin.rst --- a/src/source/dev_guide/custom-skin.rst Fri Dec 21 15:52:29 2018 +0100 +++ b/src/source/dev_guide/custom-skin.rst Fri Dec 21 17:28:34 2018 +0100 @@ -207,3 +207,66 @@ .. tip:: When a setting_interface is associated to a renderer, you can access to `settings` attributes through the template + +.. _templatehowto: + +How to define or change a template for a specific skin? +======================================================= + +Override the default template for a renderer +-------------------------------------------- + +If you want to modify the template for a particular rendering mode, you can use the function :py:func:`override_template` + +.. code-block:: python + + from pyams_template.template import override_template + + from my_website.skin.public.layer import ICustomLayer + from pyams_default_theme.component.keynumber.portlet import KeyNumberPortletHorizontalRenderer + + + override_template(context=KeyNumberPortletHorizontalRenderer, + template="templates/keynumber-horizontal.pt", + layer=ICustomLayer + ) + + +This new template can be applied to a particular :ref:`Skin ` by specifying on which layer to use this renderer +*(ex: layer=IMyWebsiteLayer)* + + + +Redefine the default template for a renderer +-------------------------------------------- + +You must redefine an adapter to add new variables or static resources for your new template, + +.. code-block:: python + + # import interfaces + from my_website.skin.public.layer import ICustomLayer + + from pyams_content.component.keynumber.portlet.interfaces import IKeyNumberPortletSettings + from pyams_portal.interfaces import IPortletRenderer, IPortalContext + + # import packages + from my_website.skin.public import my_carousel_init ,my_carousel_animation + + from pyams_default_theme.component.keynumber.portlet import KeyNumberPortletHorizontalRenderer + from pyams_template.template import template_config + from pyams_utils.adapter import adapter_config + from zope.interface import Interface + + + @adapter_config(context=(IPortalContext, IBaseLayer, Interface, IKeyNumberPortletSettings), + provides=IPortletRenderer) + @template_config(template='templates/keynumber-horizontal.pt', layer=ICustomLayer) + class MyCustomKeyNumberPortletHorizontalRenderer(KeyNumberPortletHorizontalRenderer): + """Key numbers portlet horizontal renderer""" + + resources = (my_carousel_init, my_carousel_animation) + + +The attribute :py:attr:`resources` is used to load in the template static resources. The application will automatically +integrate resource content when the template is calling. diff -r 9ab9f762abed -r 5a82a9a2ea46 src/source/dev_guide/howto-adapter.rst --- a/src/source/dev_guide/howto-adapter.rst Fri Dec 21 15:52:29 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. diff -r 9ab9f762abed -r 5a82a9a2ea46 src/source/dev_guide/howto-template.rst --- a/src/source/dev_guide/howto-template.rst Fri Dec 21 15:52:29 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -.. _templatehowto: - - -How to define or change a template for a specific skin? -======================================================= - -Override the default template for a renderer --------------------------------------------- - -If you want to modify the template for a particular rendering mode, you can use the function :py:func:`override_template` - -.. code-block:: python - - from pyams_template.template import override_template - - from my_website.skin.public.layer import ICustomLayer - from pyams_default_theme.component.keynumber.portlet import KeyNumberPortletHorizontalRenderer - - - override_template(context=KeyNumberPortletHorizontalRenderer, - template="templates/keynumber-horizontal.pt", - layer=ICustomLayer - ) - - -This new template can be applied to a particular :ref:`Skin ` by specifying on which layer to use this renderer -*(ex: layer=IMyWebsiteLayer)* - - - -Redefine the default template for a renderer --------------------------------------------- - -You must redefine an adapter to add new variables or static resources for your new template, - -.. code-block:: python - - # import interfaces - from my_website.skin.public.layer import ICustomLayer - - from pyams_content.component.keynumber.portlet.interfaces import IKeyNumberPortletSettings - from pyams_portal.interfaces import IPortletRenderer, IPortalContext - - # import packages - from my_website.skin.public import my_carousel_init ,my_carousel_animation - - from pyams_default_theme.component.keynumber.portlet import KeyNumberPortletHorizontalRenderer - from pyams_template.template import template_config - from pyams_utils.adapter import adapter_config - from zope.interface import Interface - - - @adapter_config(context=(IPortalContext, IBaseLayer, Interface, IKeyNumberPortletSettings), - provides=IPortletRenderer) - @template_config(template='templates/keynumber-horizontal.pt', layer=ICustomLayer) - class MyCustomKeyNumberPortletHorizontalRenderer(KeyNumberPortletHorizontalRenderer): - """Key numbers portlet horizontal renderer""" - - resources = (my_carousel_init, my_carousel_animation) - - -The attribute :py:attr:`resources` is used to load in the template static resources. The application will automatically -integrate resource content when the template is calling. diff -r 9ab9f762abed -r 5a82a9a2ea46 src/source/dev_guide/internals.rst --- a/src/source/dev_guide/internals.rst Fri Dec 21 15:52:29 2018 +0100 +++ b/src/source/dev_guide/internals.rst Fri Dec 21 17:28:34 2018 +0100 @@ -3,15 +3,213 @@ Understanding PyAMS internals ============================= +Adapters +-------- + +Adapters are important concepts of ZCA and PyAMS framework. If you don't know what are adapters, see :ref:`zca`. + 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!! + + Traversal --------- -Namespaces ----------- +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. + + +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. + .. _traverser: