Merge doc-dc
authorThierry Florac <thierry.florac@onf.fr>
Fri, 18 May 2018 11:27:11 +0200
changeset 64 ab51f4b614c4
parent 60 33c8e14078d7 (diff)
parent 63 9fdc2d81e685 (current diff)
child 65 5300aa064606
Merge doc-dc
requirements.txt
--- a/requirements.txt	Wed May 16 15:10:38 2018 +0200
+++ b/requirements.txt	Fri May 18 11:27:11 2018 +0200
@@ -38,7 +38,7 @@
 pyramid-fanstatic==0.5
 pyramid-zodbconn==0.7
 pyzmq==16.0.4
-repoze.lru == 0.7
+repoze.lru==0.7
 repoze.sphinx.autointerface==0.8
 Sphinx==1.6.7
 sphinx_rtd_theme==0.3.1
--- a/src/source/appextend.rst	Wed May 16 15:10:38 2018 +0200
+++ b/src/source/appextend.rst	Fri May 18 11:27:11 2018 +0200
@@ -1,167 +1,11 @@
 .. _appextend:
 
-How to create a new Portlet ?
------------------------------
-
-**Portlets** are pluggable user interface software components that are managed and displayed in a web portal,
-for example an enterprise portal or a web CMS. A portlet can aggregate (integrate) and personalize content from
-different sources within a web page. A portlet responds to requests from a web client and generates dynamic content
-(*reference:* `Wiki portlet`_).
-
-.. _`wiki portlet`: https://en.wikipedia.org/wiki/Portlet
-
-
-**PyAMS Portal** provides the portal engine but only a very small set of predefined portlets that can be used to compose
-and organize the structure of a web page; additional portlets are provided by other packages, like
-:ref:`pyams_content`.
-
-
-1. Define portlet settings
-''''''''''''''''''''''''''
-
-Portlet settings interface are defined in ``interfaces.py``, you can use :py:class:`pyams_portal.interfaces.IPortletSettings`
-or extend the interface by adding additional properties for example:
-
-.. code-block:: python
-
-    from zope.schema import Text
-
-    NEW_PORTLET_NAME = 'new.portlet'
-
-    class INewPortletSettings(IPortletSettings):
-
-        comment = Text(title=_("Comment"),
-                       required=True)
-
-
-A :py:class:`pyams_portal.portlet.PortletSettings` persistent subclass then implements what IPortletSettings describes:
-
-.. code-block:: python
-
-    @implementer(INewPortletSettings)
-    class NewPortletSettings(PortletSettings):
-        """Portlet settings"""
-
-        comment = FieldProperty(INewPortletSettings['comment'])
-
-
-2. Create Portlet
-'''''''''''''''''
-
-The Portlet component is a utility, which implements the :py:class:`pyams_portal.interfaces.IPortlet` interface and is
-registered by the :py:func:`pyams_portal.portlet.portlet_config` decorator;
-
-.. code-block:: python
-
-    @portlet_config(permission=VIEW_PERMISSION)
-    class ImagePortlet(Portlet):
-        """Image portlet"""
-
-        name = NEW_PORTLET_NAME
-        label = _("New portlet")
-
-        toolbar_image = None
-        toolbar_css_class = 'fa fa-fw fa-2x fa-picture-o'
-
-        settings_class = NewPortletSettings
-
-
-Where:
- - **permission**: permission required to display this portlet content
- - **name**: internal name given to this portlet. This name must be unique between all portlets, so using your own
-   namespace into this name is generally a good option!
- - **label**: user label given to this portlet
- - **toolbar_image**: URL of an image used to display portlet button into ZMI toolbar, if any
- - **toolbar_css_class**: CSS class used to display portlet button into ZMI toolbar, if any
- - **settings_class**: class used to store portlet settings.
-
 
-3. Create portlet settings edit form
-''''''''''''''''''''''''''''''''''''
-
-Portlet settings have to be updated through management interface via a :py:class:`pyams_portal.zmi.portlet.PortletSettingsEditor`
-subform:
-
-.. code-block:: python
-
-    @pagelet_config(name='properties.html', context=INewPortletSettings, layer=IPyAMSLayer,
-                    permission=VIEW_SYSTEM_PERMISSION)
-    class NewPortletSettingsEditor(PortletSettingsEditor):
-        """New portlet settings editor"""
-
-        settings = INewPortletSettings
-
-
-    @adapter_config(name='properties.json', context=(INewPortletSettings, IPyAMSLayer), provides=IPagelet)
-    class NewPortletSettingsAJAXEditor(AJAXEditForm, NewPortletSettingsEditor):
-        """New portlet settings editor, JSON renderer"""
-
-
-4. Previewing portlet content
-'''''''''''''''''''''''''''''
-
-A *previewer* is used into ZMI to display portlet content into portal template definition page:
-
-.. code-block:: python
-
-    @adapter_config(context=(Interface, IPyAMSLayer, Interface, INewPortletSettings), provides=IPortletPreviewer)
-    @template_config(template='my-portlet-preview.pt', layer=IPyAMSLayer)
-    class NewPortletPreviewer(PortletPreviewer):
-        """New portlet previewer"""
-
-
-The previewer template is a Chameleon template:
-
-.. code-block:: genshi
+Extending PyAMS
+===============
 
-    <tal:var define="settings view.settings">
-        <tal:if condition="settings.visible">
-            <div tal:content="settings.comment">Comment</div>
-        </tal:if>
-        <tal:if condition="not settings.visible">
-            <div class="text-center padding-y-5">
-                <span class="fa-stack fa-lg">
-                    <i class="fa fa-eye fa-stack-1x"></i>
-                    <i class="fa fa-ban fa-stack-2x text-danger"></i>
-                </span>
-            </div>
-        </tal:if>
-    </tal:var>
-
-Here we check if portlet is visible or not to display a small icon when hidden; otherwise we display entered comment.
-
-
-5. Rendering portlet content
-''''''''''''''''''''''''''''
-
-A *renderer* is used to display portlet content into rendered page content:
-
-.. code-block:: python
+.. toctree::
+    :maxdepth: 1
 
-    @adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, INewPortletSettings), provides=IPortletRenderer)
-    @template_config(template='my-portlet-render.pt', layer=IPyAMSLayer)
-    class NewPortletRenderer(PortletRenderer):
-        """New portlet renderer"""
-
-        label = _("Default comment renderer")
-
-
-Each renderer template is also a Chameleon template:
-
-.. code-block:: genshi
-
-    <div class="comment" tal:content="view.settings.comment">Comment</div>
-
-
-This is the configuration of a *default* renderer defined for this portlet; you can provide several renderers for a
-given portlet by given distinct names to the adapters:
-
-.. code-block:: python
-
-    @adapter_config(name='another-renderer',
-                    context=(IPortalContext, IPyAMSLayer, Interface, INewPortletSettings), provides=IPortletRenderer)
-    @template_config(template='my-portlet-render-2.pt', layer=IPyAMSLayer)
-    class AnotherNewPortletRenderer(PortletRenderer):
-        """Another new portlet renderer"""
-
-        label = _("Another comment renderer")
+    howto-adapter
+    howto-portlet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/source/howto-adapter.rst	Fri May 18 11:27:11 2018 +0200
@@ -0,0 +1,180 @@
+.. _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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/source/howto-portlet.rst	Fri May 18 11:27:11 2018 +0200
@@ -0,0 +1,168 @@
+.. _portlethowto:
+
+
+How to create a new Portlet?
+============================
+
+**Portlets** are pluggable user interface software components that are managed and displayed in a web portal,
+for example an enterprise portal or a web CMS. A portlet can aggregate (integrate) and personalize content from
+different sources within a web page. A portlet responds to requests from a web client and generates dynamic content
+(*reference:* `Wiki portlet`_).
+
+.. _`wiki portlet`: https://en.wikipedia.org/wiki/Portlet
+
+
+**PyAMS Portal** provides the portal engine but only a very small set of predefined portlets that can be used to compose
+and organize the structure of a web page; additional portlets are provided by other packages, like
+:ref:`pyams_content`.
+
+
+1. Define portlet settings
+''''''''''''''''''''''''''
+
+Portlet settings interface are defined in ``interfaces.py``, you can use :py:class:`pyams_portal.interfaces.IPortletSettings`
+or extend the interface by adding additional properties for example:
+
+.. code-block:: python
+
+    from zope.schema import Text
+
+    NEW_PORTLET_NAME = 'new.portlet'
+
+    class INewPortletSettings(IPortletSettings):
+
+        comment = Text(title=_("Comment"),
+                       required=True)
+
+
+A :py:class:`pyams_portal.portlet.PortletSettings` persistent subclass then implements what IPortletSettings describes:
+
+.. code-block:: python
+
+    @implementer(INewPortletSettings)
+    class NewPortletSettings(PortletSettings):
+        """Portlet settings"""
+
+        comment = FieldProperty(INewPortletSettings['comment'])
+
+
+2. Create Portlet
+'''''''''''''''''
+
+The Portlet component is a utility, which implements the :py:class:`pyams_portal.interfaces.IPortlet` interface and is
+registered by the :py:func:`pyams_portal.portlet.portlet_config` decorator;
+
+.. code-block:: python
+
+    @portlet_config(permission=VIEW_PERMISSION)
+    class ImagePortlet(Portlet):
+        """Image portlet"""
+
+        name = NEW_PORTLET_NAME
+        label = _("New portlet")
+
+        toolbar_image = None
+        toolbar_css_class = 'fa fa-fw fa-2x fa-picture-o'
+
+        settings_class = NewPortletSettings
+
+
+Where:
+ - **permission**: permission required to display this portlet content
+ - **name**: internal name given to this portlet. This name must be unique between all portlets, so using your own
+   namespace into this name is generally a good option!
+ - **label**: user label given to this portlet
+ - **toolbar_image**: URL of an image used to display portlet button into ZMI toolbar, if any
+ - **toolbar_css_class**: CSS class used to display portlet button into ZMI toolbar, if any
+ - **settings_class**: class used to store portlet settings.
+
+
+3. Create portlet settings edit form
+''''''''''''''''''''''''''''''''''''
+
+Portlet settings have to be updated through management interface via a :py:class:`pyams_portal.zmi.portlet.PortletSettingsEditor`
+subform:
+
+.. code-block:: python
+
+    @pagelet_config(name='properties.html', context=INewPortletSettings, layer=IPyAMSLayer,
+                    permission=VIEW_SYSTEM_PERMISSION)
+    class NewPortletSettingsEditor(PortletSettingsEditor):
+        """New portlet settings editor"""
+
+        settings = INewPortletSettings
+
+
+    @adapter_config(name='properties.json', context=(INewPortletSettings, IPyAMSLayer), provides=IPagelet)
+    class NewPortletSettingsAJAXEditor(AJAXEditForm, NewPortletSettingsEditor):
+        """New portlet settings editor, JSON renderer"""
+
+
+4. Previewing portlet content
+'''''''''''''''''''''''''''''
+
+A *previewer* is used into ZMI to display portlet content into portal template definition page:
+
+.. code-block:: python
+
+    @adapter_config(context=(Interface, IPyAMSLayer, Interface, INewPortletSettings), provides=IPortletPreviewer)
+    @template_config(template='my-portlet-preview.pt', layer=IPyAMSLayer)
+    class NewPortletPreviewer(PortletPreviewer):
+        """New portlet previewer"""
+
+
+The previewer template is a Chameleon template:
+
+.. code-block:: genshi
+
+    <tal:var define="settings view.settings">
+        <tal:if condition="settings.visible">
+            <div tal:content="settings.comment">Comment</div>
+        </tal:if>
+        <tal:if condition="not settings.visible">
+            <div class="text-center padding-y-5">
+                <span class="fa-stack fa-lg">
+                    <i class="fa fa-eye fa-stack-1x"></i>
+                    <i class="fa fa-ban fa-stack-2x text-danger"></i>
+                </span>
+            </div>
+        </tal:if>
+    </tal:var>
+
+Here we check if portlet is visible or not to display a small icon when hidden; otherwise we display entered comment.
+
+
+5. Rendering portlet content
+''''''''''''''''''''''''''''
+
+A *renderer* is used to display portlet content into rendered page content:
+
+.. code-block:: python
+
+    @adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, INewPortletSettings), provides=IPortletRenderer)
+    @template_config(template='my-portlet-render.pt', layer=IPyAMSLayer)
+    class NewPortletRenderer(PortletRenderer):
+        """New portlet renderer"""
+
+        label = _("Default comment renderer")
+
+
+Each renderer template is also a Chameleon template:
+
+.. code-block:: genshi
+
+    <div class="comment" tal:content="view.settings.comment">Comment</div>
+
+
+This is the configuration of a *default* renderer defined for this portlet; you can provide several renderers for a
+given portlet by given distinct names to the adapters:
+
+.. code-block:: python
+
+    @adapter_config(name='another-renderer',
+                    context=(IPortalContext, IPyAMSLayer, Interface, INewPortletSettings), provides=IPortletRenderer)
+    @template_config(template='my-portlet-render-2.pt', layer=IPyAMSLayer)
+    class AnotherNewPortletRenderer(PortletRenderer):
+        """Another new portlet renderer"""
+
+        label = _("Another comment renderer")
--- a/src/source/zca.rst	Wed May 16 15:10:38 2018 +0200
+++ b/src/source/zca.rst	Fri May 18 11:27:11 2018 +0200
@@ -289,7 +289,3 @@
         def __init__(self, *args, **kw):
             terms = [SimpleTerm(t, t, t) for t in pytz.all_timezones]
             super(TimezonesVocabulary, self).__init__(terms)
-
-
-
-