# HG changeset patch # User Damien Correia # Date 1545402006 -3600 # Node ID d632f8d6140b901f2082ccb22afd4c59896d17e9 # Parent 7205ae7c43dc16c7558087bc3e976839bd9cb939 Updated internals and custom skin diff -r 7205ae7c43dc -r d632f8d6140b src/source/conf.py --- a/src/source/conf.py Thu Dec 20 17:59:43 2018 +0100 +++ b/src/source/conf.py Fri Dec 21 15:20:06 2018 +0100 @@ -137,7 +137,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['source/_static', ] +html_static_path = ['_static', ] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -202,7 +202,6 @@ ] - # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. diff -r 7205ae7c43dc -r d632f8d6140b src/source/dev_guide/custom-skin.rst --- a/src/source/dev_guide/custom-skin.rst Thu Dec 20 17:59:43 2018 +0100 +++ b/src/source/dev_guide/custom-skin.rst Fri Dec 21 15:20:06 2018 +0100 @@ -6,14 +6,204 @@ Understading layers and skins ----------------------------- + +.. _skinhowto: + Creating a new skin ---------------------- +------------------- + +A Skin is a tagging interface for associating media, javascript and CSS resources to a **renderer** + + +1) Create a new Layer to your skin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Build a new interface inherit from `ICustomLayer` + +.. code-block:: python + + class ICustomLayer(ICustomLayer): + """skin layer""" + +Define an utility providing ISkin with the custom label and the layer interface + +.. code-block:: python + + @utility_config(name='Custom skin', provides=ISkin) + class CustomSkin(object): + """custom root skin""" + + label = _("Custom: skin") + layer = ICustomLayer + + +2) Declare the layer adapter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + @adapter_config(context=(Interface, ICustomLayer, Interface), provides=IResources) + class CustomSkinResourcesAdapter(ContextRequestViewAdapter): + """Custom skin resources adapter""" -Adding resources ----------------- + def get_resources(self): + mycms.need() + + +We have defined a Multiadapter with context=(context, request, view). + +.. note:: + + In the ZMI website you can now change the default graphical theme by you custom skin + + .. image:: ../_static/select_skin.png + + + + +Adding resources library +------------------------ + + +.. code-block:: python + + from fanstatic import Library, Resource + from pyams_default_theme import pyams_default_theme + + #Library(name, folder_path) + library = Library('mycms', 'resources') + + #Resource(library, path_to_static) + mycms_css = Resource(library, 'css/mystyle.css',) + + + mycms_js = Resource(library, 'js/pyams-default.js', + depends=(pyams_default_theme, ) + bottom=True + ) + + +:py:class:`Resource` can include others resources already defined with *depends* attribute, here `pyams_default_theme`. + Overriding templates ---------------------- +-------------------- + +The simplest is to create a new class that inherits from the existing **Renderer** and modify this template. +After that all you have to define a new adapter name and a new label. + + +.. code-block:: python + :linenos: + + # New custom contact paragraph renderer + + @adapter_config(name='custom', context=(IContactParagraph, IPyAMSLayer), provides=ISharedContentRenderer) + @template_config(template='templates/contact-custom.pt', layer=IPyAMSLayer) + class ContactParagraphCustomRenderer(ContactParagraphDefaultRenderer): + """Context paragraph custom renderer""" + + label = _("Custom contact renderer") + #settings_interface = IContactParagraphDefaultRendererSettings + + +In this example, we have defined an adapter named 'custom' with :py:class:`IContactParagraph`, +:py:class:`IPyAMSLayer` as context and provides :py:class:`ISharedContentRenderer` interface. + +Using ``@template_config()`` decorator, the renderer will be displayed in html container according to the template +`templates/contact-custom.pt` + +The new renderer inherit of :py:class:`ContactParagraphDefaultRenderer`, have a new **label** (line 8) +and this associated **settings_interface** is not modify(line 9) + +.. tip:: + + You can override the template of a renderer easily with the function :py:func:`pyams_template.template.override_template` + It's takes the context and the new template path as params. + + Creating custom renderer ------------------------ + +We can define a new settings for the renderer, to do that we start by creating an interface: + + +a) Create setting interface for the renderer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + class IPhotoRendererSettings(Interface): + """Custom renderer settings interface""" + + + display_photo = Bool(title=_("Show photo?"), + description=_("Display contact photo"), + required=True, + default=True) + + can_display_photo = Attribute("Check if photo can be displayed") + + +We have created an interface with two attributes *display_photo* and *can_display_photo* +Then we create an implemantation of the interface + +.. code-block:: python + + @implementer(IPhotoRendererSettings) + class PhotoRendererSettings(Persistent, Location): + """Custom renderer settings""" + + display_photo = FieldProperty(IPhotoRendererSettings['display_photo']) + + @property + def can_display_photo(self): + contact = IContactParagraph(self.__parent__) + if not contact.photo: + return False + return self.display_photo + + + +b) Create an adapter for the render setting interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +With :py:func:`@adapter_config()` we declare a new adapter that applies to a context and provide the interface of + renderer settings + +.. code-block:: python + + PHOTO_RENDERER_SETTINGS_KEY = 'pyams_content.contact.renderer:photo' + + @adapter_config(context=IContactParagraph, provides=IPhotoRendererSettings) + def custom_renderer_settings_factory(context): + """Contact paragraph default renderer settings factory""" + return get_annotation_adapter(context, PHOTO_RENDERER_SETTINGS_KEY, + CustomRendererSettings) + + +In this example the settings interface adapter is defined with `IContactParagraph` as context +and provide `IPhotoRendererSettings`. + + + +c) Create an adapter for the render interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + @adapter_config(context=(IContactParagraph, IPyAMSLayer), provides=ISharedContentRenderer) + @template_config(template='templates/contact-custom.pt', layer=IPyAMSLayer) + class PhotoRenderer(BaseContentRenderer): + """Context paragraph custom renderer""" + + label = _("Custom contact renderer") + settings_interface = IPhotoRendererSettings + + +Add settings interface to the renderer `settings_interface = IPhotoRendererSettings` + +.. tip:: + When a setting_interface is associated to a renderer, you can access to `settings` attributes through the template + diff -r 7205ae7c43dc -r d632f8d6140b src/source/dev_guide/howto-renderer.rst --- a/src/source/dev_guide/howto-renderer.rst Thu Dec 20 17:59:43 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -.. _rendererhowto: - - -How to create a Renderer? -========================= - - -**Renderer** are the layout of the utility data content. A renderer combine un context, a skin and -a template to produce the front office html - - To create new renderer you can override an already exist renderer or create a new one from scratch. Steps below - we will create a renderer for a `IContact` paragraph - - -1. Override a Renderer ----------------------- - -The simplest is to create a new class that inherits from the existing **Renderer** and modify this template. -After that all you have to define a new adapter name and a new label. - - -.. code-block:: python - :linenos: - - # New custom contact paragraph renderer - - @adapter_config(name='custom', context=(IContactParagraph, IPyAMSLayer), provides=ISharedContentRenderer) - @template_config(template='templates/contact-custom.pt', layer=IPyAMSLayer) - class ContactParagraphCustomRenderer(ContactParagraphDefaultRenderer): - """Context paragraph custom renderer""" - - label = _("Custom contact renderer") - #settings_interface = IContactParagraphDefaultRendererSettings - - -In this example, we have defined an adapter named 'custom' with :py:class:`IContactParagraph`, -:py:class:`IPyAMSLayer` as context and provides :py:class:`ISharedContentRenderer` interface. - -Using ``@template_config()`` decorator, the renderer will be displayed in html container according to the template -`templates/contact-custom.pt` - -The new renderer inherit of :py:class:`ContactParagraphDefaultRenderer`, have a new **label** (line 8) -and this associated **settings_interface** is not modify(line 9) - -.. tip:: - - You can override the template of a renderer easily with the function :py:func:`pyams_template.template.override_template` - It's takes the context and the new template path as params. - - - -2. Create a new Renderer from scratch -------------------------------------- - -We can define a new settings for the renderer, to do that we start by creating an interface: - - -a) Create setting interface for the renderer -"""""""""""""""""""""""""""""""""""""""""""" - -.. code-block:: python - - class IPhotoRendererSettings(Interface): - """Custom renderer settings interface""" - - - display_photo = Bool(title=_("Show photo?"), - description=_("Display contact photo"), - required=True, - default=True) - - can_display_photo = Attribute("Check if photo can be displayed") - - -We have created an interface with two attributes *display_photo* and *can_display_photo* -Then we create an implemantation of the interface - -.. code-block:: python - - @implementer(IPhotoRendererSettings) - class PhotoRendererSettings(Persistent, Location): - """Custom renderer settings""" - - display_photo = FieldProperty(IPhotoRendererSettings['display_photo']) - - @property - def can_display_photo(self): - contact = IContactParagraph(self.__parent__) - if not contact.photo: - return False - return self.display_photo - - - -b) Create an adapter for the render setting interface -""""""""""""""""""""""""""""""""""""""""""""""""""""" - -With :py:func:`@adapter_config()` we declare a new adapter that applies to a context and provide the interface of - renderer settings - -.. code-block:: python - - PHOTO_RENDERER_SETTINGS_KEY = 'pyams_content.contact.renderer:photo' - - @adapter_config(context=IContactParagraph, provides=IPhotoRendererSettings) - def custom_renderer_settings_factory(context): - """Contact paragraph default renderer settings factory""" - return get_annotation_adapter(context, PHOTO_RENDERER_SETTINGS_KEY, - CustomRendererSettings) - - -In this example the settings interface adapter is defined with `IContactParagraph` as context -and provide `IPhotoRendererSettings`. - - - -c) Create an adapter for the render interface -""""""""""""""""""""""""""""""""""""""""""""" - -.. code-block:: python - - @adapter_config(context=(IContactParagraph, IPyAMSLayer), provides=ISharedContentRenderer) - @template_config(template='templates/contact-custom.pt', layer=IPyAMSLayer) - class PhotoRenderer(BaseContentRenderer): - """Context paragraph custom renderer""" - - label = _("Custom contact renderer") - settings_interface = IPhotoRendererSettings - - -Add settings interface to the renderer `settings_interface = IPhotoRendererSettings` - -.. tip:: - When a setting_interface is associated to a renderer, you can access to `settings` attributes through the template - diff -r 7205ae7c43dc -r d632f8d6140b src/source/dev_guide/howto-skin.rst --- a/src/source/dev_guide/howto-skin.rst Thu Dec 20 17:59:43 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -.. _skinhowto: - - -How to create Skin? -=================== - -A Skin is a tagging interface for associating media, javascript and CSS resources to a **renderer** - -1) Configuring resource library -------------------------------- - - -.. code-block:: python - - from fanstatic import Library, Resource - from pyams_default_theme import pyams_default_theme - - #Library(name, folder_path) - library = Library('mycms', 'resources') - - #Resource(library, path_to_static) - mycms_css = Resource(library, 'css/mystyle.css',) - - - mycms_js = Resource(library, 'js/pyams-default.js', - depends=(pyams_default_theme, ) - bottom=True - ) - - -:py:class:`Resource` can include others resources already defined with *depends* attribute, here `pyams_default_theme`. - - -2) Create a new Layer to your skin ----------------------------------- - -Build a new interface inherit from `ICustomLayer` - -.. code-block:: python - - class ICustomLayer(ICustomLayer): - """skin layer""" - -Define an utility providing ISkin with the custom label and the layer interface - -.. code-block:: python - - @utility_config(name='Custom skin', provides=ISkin) - class CustomSkin(object): - """custom root skin""" - - label = _("Custom: skin") - layer = ICustomLayer - - -3) Declare the layer adapter ----------------------------- - -.. code-block:: python - - @adapter_config(context=(Interface, ICustomLayer, Interface), provides=IResources) - class CustomSkinResourcesAdapter(ContextRequestViewAdapter): - """Custom skin resources adapter""" - - def get_resources(self): - mycms.need() - - -We have defined a Multiadapter with context=(context, request, view). - -.. note:: - - In the ZMI website you can now change the default graphical theme by you custom skin - - .. image:: ../_static/select_skin.png diff -r 7205ae7c43dc -r d632f8d6140b src/source/dev_guide/internals.rst --- a/src/source/dev_guide/internals.rst Thu Dec 20 17:59:43 2018 +0100 +++ b/src/source/dev_guide/internals.rst Fri Dec 21 15:20:06 2018 +0100 @@ -3,6 +3,7 @@ Understanding PyAMS internals ============================= + Annotations ----------- @@ -12,11 +13,334 @@ Namespaces ---------- + +.. _renderer: + +Renderers +--------- + +**Renderer** are the layout of the utility data content. A renderer combine un context, a skin and +a template to produce the front office html + + To create new renderer you can override an already exist renderer or create a new one from scratch. Steps below + we will create a renderer for a `IContact` paragraph + + +HTML renderer +------------- + +Chameleon +^^^^^^^^^ + +To generate html page with dynamic content PyAMS renderer use Chameleon as a template engine + +Once *pyramid_chameleon* been activated **.pt** templates can be loaded either by looking names +that would be found on the Chameleon search path. + + +.. seealso:: + + https://chameleon.readthedocs.io/en/latest/ + https://zope.readthedocs.io/en/latest/zope2book/ZPT.html + + + +PyAMS defines a custom expression for TALES called *extension*. + +This expression are used in the html template (.pt) to ease to display +or manipulate content. + + +.. _tales: + TALES Extensions ---------------- -Renderers ---------- +PyAMS defines a custom expression for TALES called *extension*. + +When this expression is encountered, the renderer is looking for an +:py:class:`ITALESExtension ` +multi-adapter for the current *context*, *request* and *view*, for the current +*context* and *request*, or only for the current *context*, in this order. +If an adapter is found, the renderer call it's :py:func:`render` method with +the expression parameters as input parameters. + +For example, the *metas* extension is an *ITALESExtension* adapter defined into +:py:mod:`pyams_skin.metas` module which can be used to include all required headers in +a page template. Extension is used like this in the page layout template: + +.. code-block:: html + + + +This extension is defined like this: + +.. code-block:: python + + from pyams_skin.interfaces.metas import IHTMLContentMetas + from pyams_utils.interfaces.tales import ITALESExtension + from pyramid.interfaces import IRequest + + from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter + + @adapter_config(name='metas', context=(Interface, IRequest, Interface), provides=ITALESExtension) + class MetasTalesExtension(ContextRequestViewAdapter): + '''extension:metas TALES extension''' + + def render(self, context=None): + if context is None: + context = self.context + result = [] + for name, adapter in sorted(self.request.registry.getAdapters((context, self.request, self.view), + IHTMLContentMetas), + key=lambda x: getattr(x[1], 'order', 9999)): + result.extend([meta.render() for meta in adapter.get_metas()]) + return '\n\t'.join(result) + +Some TALES extensions can require or accept arguments. For example, the *absolute_url* extension can accept +a context and a view name: + +.. code-block:: html + + + + + +The extension is defined like this: + +.. code-block:: python + + from persistent.interfaces import IPersistent + from pyams_utils.interfaces.tales import ITALESExtension + + from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter + from pyramid.url import resource_url + from zope.interface import Interface + + @adapter_config(name='absolute_url', context=(IPersistent, Interface, Interface), provides=ITALESExtension) + class AbsoluteUrlTalesExtension(ContextRequestViewAdapter): + '''extension:absolute_url(context, view_name) TALES extension''' + + def render(self, context=None, view_name=None): + if context is None: + context = self.context + return resource_url(context, self.request, view_name) + + +.. _talesext: + +List of PyAMS TALES extensions +------------------------------ + +Reformat text +^^^^^^^^^^^^^ + +- br + *br*\(value, class) TALES expression + +This expression can be used to context a given character (‘|’ by default) into HTML breaks with given CSS class. + +For example: + + ${tales:br(value, 'hidden-xs')} + +If value is "Raining| cats and dogs", the output will be "Raining cats and dogs". + +---- + +- html + *html*\(value) + +Replaces line breaks in plain text with appropriate HTML (
). + +If value is: + +| "Raining +| cats +| and +| dogs" + +The output will be "Raining
cats
and
dogs". + + +---- + +- truncate + *truncate*\(value, length, max) + +Truncates a string if it is longer than the specified length of characters. Truncated strings will end with ellipsis sequence (“…”). + + +For example: + + ${tales:truncate(value,9, 0)} + +If value is "Raining cats and dogs", the output will be "Raining...". + + ${tales:truncate(value,9, 1)} + +If value is "Raining cats and dogs", the output will be "Raining cats...". + + +---- + +- i18n + *i18n*\(value) + +Return sentence according to the context user’s language, the value is a dict. + + +Utilities +^^^^^^^^^ + +- oid + *oid*(value) + +Return the **oid** of the value + +---- + +- cache_key + *cache_key*\(value) + +Return an unique ID based of the component value + +---- + +- timestamp + +Return incremental number based on the time past in second + + + +Media +^^^^^ + + +- picture + *picture*\(value, lg_thumb='banner', md_thumb='banner', sm_thumb='banner',xs_thumb='banner', css_class='inner-header__img', alt=alt_title) + +Search the illustration associated with the value. +Return the first illustration found, by order: the component definition, content illustration navigation and content illustration + +[lg, md, sm, xs]*_thumb + - banner + - pano + - portrait + - square + +[lg, md, sm, xs]*_width + [1-12] boostrat column size + +css_class + add a css class to the container of this illustration () + +img_class + add a css class to the balise + +alt + alternative title + + +---- + +- thumbnails + *thumbnails*\(value) + +---- + +- thumbnail + *thumbnail*\(value, (value, lg_thumb='banner', md_thumb='banner', sm_thumb='banner',xs_thumb='banner', css_class='inner-header__img', alt=alt_title) + +Search the image associated with the value. + +[lg, md, sm, xs]*_thumb + - banner + - pano + - portrait + - square + +[lg, md, sm, xs]*_width + [1-12] boostrat column size + +css_class + add a css class to the container of this illustration () + +img_class + add a css class to the balise + +alt + alternative title) + +---- + +- conversions + *conversion*\(value) + +Return the list of conversion format supported by the value + +---- + +- audio_type + *audio_type*\(value) + +Return the type of the audio media + +---- + +- video_type* + *video_type*\(value) + +Return the type of the video media + + +Find a resource +^^^^^^^^^^^^^^^ + +- absolute_url + *absolute_url*\(object) + +Used to get access to an object URL + +---- + +- canonical_url + *canonical_url*\(context,request) + +Used to get access to an uniq object URL, based on current request display context + +---- + +- relative_url + *relative_url*\(context, request) + + Used to get an object's relative URL based on current request display context + +---- + +- *resource_path*\(value) + +Generates an URL matching a given Fanstatic resource. +Resource is given as a string made of package name as value (in dotted form) followed by a colon and by the resource name. + +---- + +- *need_resource*\(value) + + +This extension generates a call to Fanstatic resource.need() function to include given resource +into generated HTML code. +Resource is given as a string made of package name as value (in dotted form) followed by a colon and by the resource name. + +For example: + +.. code-block:: + + + + +---- + Sequences --------- diff -r 7205ae7c43dc -r d632f8d6140b src/source/dev_guide/tales.rst --- a/src/source/dev_guide/tales.rst Thu Dec 20 17:59:43 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -.. _tales: - -Custom TALES extensions -======================= - -PyAMS defines a custom expression for TALES called *extension*. - -When this expression is encountered, the renderer is looking for an -:py:class:`ITALESExtension ` -multi-adapter for the current *context*, *request* and *view*, for the current -*context* and *request*, or only for the current *context*, in this order. -If an adapter is found, the renderer call it's :py:func:`render` method with -the expression parameters as input parameters. - -For example, the *metas* extension is an *ITALESExtension* adapter defined into -:py:mod:`pyams_skin.metas` module which can be used to include all required headers in -a page template. Extension is used like this in the page layout template: - -.. code-block:: html - - - -This extension is defined like this: - -.. code-block:: python - - from pyams_skin.interfaces.metas import IHTMLContentMetas - from pyams_utils.interfaces.tales import ITALESExtension - from pyramid.interfaces import IRequest - - from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter - - @adapter_config(name='metas', context=(Interface, IRequest, Interface), provides=ITALESExtension) - class MetasTalesExtension(ContextRequestViewAdapter): - '''extension:metas TALES extension''' - - def render(self, context=None): - if context is None: - context = self.context - result = [] - for name, adapter in sorted(self.request.registry.getAdapters((context, self.request, self.view), - IHTMLContentMetas), - key=lambda x: getattr(x[1], 'order', 9999)): - result.extend([meta.render() for meta in adapter.get_metas()]) - return '\n\t'.join(result) - -Some TALES extensions can require or accept arguments. For example, the *absolute_url* extension can accept -a context and a view name: - -.. code-block:: html - - - - - -The extension is defined like this: - -.. code-block:: python - - from persistent.interfaces import IPersistent - from pyams_utils.interfaces.tales import ITALESExtension - - from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter - from pyramid.url import resource_url - from zope.interface import Interface - - @adapter_config(name='absolute_url', context=(IPersistent, Interface, Interface), provides=ITALESExtension) - class AbsoluteUrlTalesExtension(ContextRequestViewAdapter): - '''extension:absolute_url(context, view_name) TALES extension''' - - def render(self, context=None, view_name=None): - if context is None: - context = self.context - return resource_url(context, self.request, view_name)