merge default doc-dc
authorDamien Correia
Wed, 23 May 2018 15:30:41 +0200
branchdoc-dc
changeset 651 26a58877d1aa
parent 650 927afb26b1ce (current diff)
parent 553 781e3958f6bf (diff)
child 652 b438528e5bb3
merge default
src/pyams_content/shared/imagemap/zmi/render.py
--- a/src/pyams_content/component/association/container.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/association/container.py	Wed May 23 15:30:41 2018 +0200
@@ -19,20 +19,18 @@
 from pyams_content.component.association.interfaces import IAssociationContainer, IAssociationTarget, \
     ASSOCIATION_CONTAINER_KEY, IAssociationItem, IAssociationInfo
 from pyams_content.features.checker.interfaces import IContentChecker
-from zope.annotation.interfaces import IAnnotations
 from zope.location.interfaces import ISublocations
 from zope.traversing.interfaces import ITraversable
 
 # import packages
 from pyams_catalog.utils import index_object
 from pyams_content.features.checker import BaseContentChecker
-from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.adapter import adapter_config, get_annotation_adapter, ContextAdapter
+from pyams_utils.registry import get_current_registry
 from pyams_utils.traversing import get_parent
 from pyams_utils.vocabulary import vocabulary_config
-from pyramid.threadlocal import get_current_registry
 from zope.container.ordered import OrderedContainer
 from zope.interface import implementer
-from zope.lifecycleevent import ObjectCreatedEvent
 from zope.location import locate
 from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
 
@@ -63,13 +61,7 @@
 @adapter_config(context=IAssociationTarget, provides=IAssociationContainer)
 def association_container_factory(target):
     """Associations container factory"""
-    annotations = IAnnotations(target)
-    container = annotations.get(ASSOCIATION_CONTAINER_KEY)
-    if container is None:
-        container = annotations[ASSOCIATION_CONTAINER_KEY] = AssociationContainer()
-        get_current_registry().notify(ObjectCreatedEvent(container))
-        locate(container, target, '++ass++')
-    return container
+    return get_annotation_adapter(target, ASSOCIATION_CONTAINER_KEY, AssociationContainer, name='++ass++')
 
 
 @adapter_config(name='ass', context=IAssociationTarget, provides=ITraversable)
@@ -77,7 +69,8 @@
     """Associations container ++ass++ namespace"""
 
     def traverse(self, name, furtherpath=None):
-        return IAssociationContainer(self.context)
+        registry = get_current_registry()
+        return registry.queryAdapter(self.context, IAssociationContainer, name=name or '')
 
 
 @adapter_config(name='associations', context=IAssociationTarget, provides=ISublocations)
--- a/src/pyams_content/component/association/zmi/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/association/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -36,6 +36,7 @@
 from pyams_skin.event import get_json_switched_table_refresh_event, get_json_table_row_refresh_event
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.registry import get_current_registry
 from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
@@ -100,6 +101,7 @@
     """Associations view inner table"""
 
     prefix = 'associations'
+    associations_name = ''
 
     hide_header = True
     sortOn = None
@@ -114,10 +116,13 @@
 
     @property
     def data_attributes(self):
+        registry = get_current_registry()
+        target = get_parent(self.context, IAssociationTarget)
+        container = registry.getAdapter(target, IAssociationContainer, name=self.associations_name)
         attributes = super(AssociationsTable, self).data_attributes
         attributes['table'] = {
             'id': self.id,
-            'data-ams-location': absolute_url(IAssociationContainer(self.context), self.request),
+            'data-ams-location': absolute_url(container, self.request),
             'data-ams-tablednd-drag-handle': 'td.sorter',
             'data-ams-tablednd-drop-target': 'set-associations-order.json',
             'data-ams-visibility-switcher': 'switch-association-visibility.json'
@@ -135,10 +140,12 @@
 
     @property
     def values(self):
-        return IAssociationContainer(self.context).values()
+        registry = get_current_registry()
+        return registry.getAdapter(self.context, IAssociationContainer, name=self.view.associations_name).values()
 
 
 @adapter_config(name='sorter', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable), provides=IColumn)
+@adapter_config(name='sorter', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable), provides=IColumn)
 class AssociationsTableSorterColumn(ProtectedFormObjectMixin, SorterColumn):
     """Associations table sorter column"""
 
@@ -146,7 +153,7 @@
 @view_config(name='set-associations-order.json', context=IAssociationContainer, request_type=IPyAMSLayer,
              permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 def set_associations_order(request):
-    """Update asociations order"""
+    """Update associations order"""
     order = list(map(str, json.loads(request.params.get('names'))))
     request.context.updateOrder(order)
     return {'status': 'success'}
@@ -154,6 +161,8 @@
 
 @adapter_config(name='show-hide', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable),
                 provides=IColumn)
+@adapter_config(name='show-hide', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable),
+                provides=IColumn)
 class AssociationsTableShowHideColumn(ProtectedFormObjectMixin, VisibilitySwitcherColumn):
     """Associations container visibility switcher column"""
 
@@ -166,6 +175,7 @@
 
 
 @adapter_config(name='pictogram', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable), provides=IColumn)
+@adapter_config(name='pictogram', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable), provides=IColumn)
 class AssociationsTablePictogramColumn(ImageColumn):
     """Associations table pictogram column"""
 
@@ -181,6 +191,7 @@
 
 
 @adapter_config(name='name', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable), provides=IColumn)
+@adapter_config(name='name', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable), provides=IColumn)
 class AssociationsTablePublicNameColumn(NameColumn):
     """Associations table name column"""
 
@@ -198,6 +209,7 @@
 
 
 @adapter_config(name='inner_name', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable), provides=IColumn)
+@adapter_config(name='inner_name', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable), provides=IColumn)
 class AssociationsTableInnerNameColumn(I18nColumn, GetAttrColumn):
     """Associations table inner name column"""
 
@@ -213,6 +225,7 @@
 
 
 @adapter_config(name='size', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable), provides=IColumn)
+@adapter_config(name='size', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable), provides=IColumn)
 class AssociationsTableSizeColumn(I18nColumn, GetAttrColumn):
     """Associations table size column"""
 
@@ -228,6 +241,7 @@
 
 
 @adapter_config(name='trash', context=(IAssociationTarget, IPyAMSLayer, AssociationsTable), provides=IColumn)
+@adapter_config(name='trash', context=(IAssociationContainer, IPyAMSLayer, AssociationsTable), provides=IColumn)
 class AssociationsTableTrashColumn(ProtectedFormObjectMixin, TrashColumn):
     """Associations table trash column"""
 
--- a/src/pyams_content/component/association/zmi/paragraph.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/association/zmi/paragraph.py	Wed May 23 15:30:41 2018 +0200
@@ -23,7 +23,7 @@
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common.interfaces import IWfSharedContent
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
@@ -32,7 +32,7 @@
 # import packages
 from pyams_content.component.association.paragraph import AssociationParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
@@ -123,7 +123,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/gallery/zmi/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -105,7 +105,8 @@
     show_widget_title = True
 
 
-@viewlet_config(name='gallery-medias', context=IGallery, view=IGalleryContentsView, manager=IWidgetsPrefixViewletsManager)
+@viewlet_config(name='gallery-medias', context=IGallery, view=IGalleryContentsView,
+                manager=IWidgetsPrefixViewletsManager)
 @template_config(template='templates/gallery-medias.pt', layer=IPyAMSLayer)
 @implementer(IGalleryContentsView)
 class GalleryMediasViewlet(Viewlet):
--- a/src/pyams_content/component/gallery/zmi/paragraph.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/paragraph.py	Wed May 23 15:30:41 2018 +0200
@@ -22,7 +22,7 @@
     IParagraphRenderer
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons, IInnerSubForm
+from pyams_form.interfaces.form import IInnerForm, IInnerSubForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager
 from pyams_skin.layer import IPyAMSLayer
@@ -32,7 +32,7 @@
 # import packages
 from pyams_content.component.gallery.paragraph import Gallery
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
@@ -139,7 +139,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/illustration/zmi/paragraph.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/illustration/zmi/paragraph.py	Wed May 23 15:30:41 2018 +0200
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from zope.schema._schema import getFieldNamesInOrder
 
 __docformat__ = 'restructuredtext'
 
@@ -23,7 +22,7 @@
     ILLUSTRATION_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from transaction.interfaces import ITransactionManager
@@ -32,7 +31,7 @@
 # import packages
 from pyams_content.component.illustration.paragraph import Illustration
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
@@ -141,7 +140,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/links/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/links/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -19,7 +19,8 @@
 from pyams_content.component.association.interfaces import IAssociationInfo, IAssociationTarget, IAssociationContainer
 from pyams_content.component.links.interfaces import IBaseLink, IInternalLink, IExternalLink, IMailtoLink
 from pyams_content.features.checker.interfaces import IContentChecker, ERROR_VALUE
-from pyams_content.interfaces import IBaseContent
+from pyams_content.interfaces import IBaseContent, MANAGE_CONTENT_PERMISSION
+from pyams_form.interfaces.form import IFormContextPermissionChecker
 from pyams_i18n.interfaces import II18n
 from pyams_sequence.interfaces import ISequentialIdInfo
 from pyams_workflow.interfaces import IWorkflow
@@ -120,6 +121,13 @@
             return ''
 
 
+@adapter_config(context=IInternalLink, provides=IFormContextPermissionChecker)
+class InternalLinkPermissionChecker(ContextAdapter):
+    """Internal link permission checker"""
+
+    edit_permission = MANAGE_CONTENT_PERMISSION
+
+
 @adapter_config(context=IInternalLink, provides=IAssociationInfo)
 class InternalLinkAssociationInfoAdapter(BaseLinkInfoAdapter):
     """Internal link association info adapter"""
@@ -187,6 +195,13 @@
         return self.url
 
 
+@adapter_config(context=IExternalLink, provides=IFormContextPermissionChecker)
+class ExternalLinkPermissionChecker(ContextAdapter):
+    """External link permission checker"""
+
+    edit_permission = MANAGE_CONTENT_PERMISSION
+
+
 @adapter_config(context=IExternalLink, provides=IAssociationInfo)
 class ExternalLinkAssociationInfoAdapter(BaseLinkInfoAdapter):
     """External link association info adapter"""
@@ -233,6 +248,13 @@
         return 'mailto:{0} <{1}>'.format(self.address_name, self.address)
 
 
+@adapter_config(context=IMailtoLink, provides=IFormContextPermissionChecker)
+class MailtoLinkPermissionChecker(ContextAdapter):
+    """Mailto link permission checker"""
+
+    edit_permission = MANAGE_CONTENT_PERMISSION
+
+
 @adapter_config(context=IMailtoLink, provides=IAssociationInfo)
 class MailtoLinkAssociationInfoAdapter(BaseLinkInfoAdapter):
     """Mailto link association info adapter"""
--- a/src/pyams_content/component/links/zmi/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/links/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_content.component.paragraph.zmi import get_json_paragraph_markers_refresh_event
 
 __docformat__ = 'restructuredtext'
 
@@ -30,6 +29,7 @@
 # import packages
 from pyams_content.component.association.zmi import AssociationItemAJAXAddForm, AssociationItemAJAXEditForm
 from pyams_content.component.links import InternalLink, ExternalLink, MailtoLink
+from pyams_content.component.paragraph.zmi import get_json_paragraph_markers_refresh_event
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerCounterBase
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
@@ -124,7 +124,7 @@
 
     fields = field.Fields(IInternalLink).select('reference', 'title', 'description')
     ajax_handler = 'properties.json'
-    edit_permission = MANAGE_CONTENT_PERMISSION
+    edit_permission = None  # defined by IFormContextPermissionChecker adapter
 
     def updateWidgets(self, prefix=None):
         super(InternalLinkPropertiesEditForm, self).updateWidgets(prefix)
@@ -227,7 +227,7 @@
 
     fields = field.Fields(IExternalLink).select('url', 'title', 'description', 'language')
     ajax_handler = 'properties.json'
-    edit_permission = MANAGE_CONTENT_PERMISSION
+    edit_permission = None  # defined by IFormContextPermissionChecker adapter
 
     def updateWidgets(self, prefix=None):
         super(ExternalLinkPropertiesEditForm, self).updateWidgets(prefix)
@@ -330,7 +330,7 @@
 
     fields = field.Fields(IMailtoLink).select('address', 'address_name', 'title', 'description')
     ajax_handler = 'properties.json'
-    edit_permission = MANAGE_CONTENT_PERMISSION
+    edit_permission = None  # defined by IFormContextPermissionChecker adapter
 
     def updateWidgets(self, prefix=None):
         super(MailtoLinkPropertiesEditForm, self).updateWidgets(prefix)
--- a/src/pyams_content/component/paragraph/container.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/container.py	Wed May 23 15:30:41 2018 +0200
@@ -19,18 +19,14 @@
 from pyams_content.component.paragraph.interfaces import IParagraphContainer, IParagraphContainerTarget, \
     PARAGRAPH_CONTAINER_KEY
 from pyams_content.features.checker.interfaces import IContentChecker
-from zope.annotation.interfaces import IAnnotations
 from zope.location.interfaces import ISublocations
 from zope.traversing.interfaces import ITraversable
 
 # import packages
 from pyams_content.features.checker import BaseContentChecker
-from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
 from pyams_utils.container import BTreeOrderedContainer
-from pyramid.threadlocal import get_current_registry
 from zope.interface import implementer
-from zope.lifecycleevent import ObjectCreatedEvent
-from zope.location import locate
 
 from pyams_content import _
 
@@ -50,13 +46,8 @@
 @adapter_config(context=IParagraphContainerTarget, provides=IParagraphContainer)
 def paragraph_container_factory(target):
     """Paragraphs container factory"""
-    annotations = IAnnotations(target)
-    container = annotations.get(PARAGRAPH_CONTAINER_KEY)
-    if container is None:
-        container = annotations[PARAGRAPH_CONTAINER_KEY] = ParagraphContainer()
-        get_current_registry().notify(ObjectCreatedEvent(container))
-        locate(container, target, '++paras++')
-    return container
+    return get_annotation_adapter(target, PARAGRAPH_CONTAINER_KEY, ParagraphContainer,
+                                  name='++paras++')
 
 
 @adapter_config(name='paras', context=IParagraphContainerTarget, provides=ITraversable)
--- a/src/pyams_content/component/paragraph/interfaces/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/interfaces/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -58,7 +58,7 @@
         """Add given value to container"""
 
 
-class IParagraphContainerTarget(Interface):
+class IParagraphContainerTarget(IAttributeAnnotatable):
     """Paragraphs container marker interface"""
 
 
--- a/src/pyams_content/component/paragraph/zmi/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -21,7 +21,7 @@
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphContainerView
 from pyams_content.interfaces import MANAGE_TOOL_PERMISSION
 from pyams_content.shared.common.interfaces import IWfSharedContent
-from pyams_form.interfaces.form import IFormHelp
+from pyams_form.interfaces.form import IFormHelp, check_submit_button
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.container import ITableElementName
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
@@ -33,6 +33,7 @@
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerTable, ParagraphContainerBaseTable
 from pyams_form.form import AJAXEditForm, AJAXAddForm
 from pyams_form.help import FormHelp
+from pyams_form.schema import ActionButton, CloseButton
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_switched_table_refresh_event
@@ -46,7 +47,8 @@
 from pyams_zmi.form import AdminDialogEditForm
 from pyramid.location import lineage
 from pyramid.view import view_config
-from z3c.form import field
+from z3c.form import field, button
+from zope.interface import Interface
 
 from pyams_content import _
 
@@ -103,15 +105,16 @@
 def get_json_paragraph_refresh_event(context, request):
     """Get JSON response value for paragraph refresh event"""
     parent = get_parent(context, IParagraphContainerTarget)
-    return {
-        'event': 'myams.refresh',
-        'options': {
-            'handler': 'PyAMS_content.paragraphs.refreshParagraph',
-            'object_id': get_element_id(ParagraphContainerBaseTable, context, parent),
-            'title': II18n(context).query_attribute('title', request=request),
-            'visible': context.visible
+    if parent is not None:
+        return {
+            'event': 'myams.refresh',
+            'options': {
+                'handler': 'PyAMS_content.paragraphs.refreshParagraph',
+                'object_id': get_element_id(ParagraphContainerBaseTable, context, parent),
+                'title': II18n(context).query_attribute('title', request=request),
+                'visible': context.visible
+            }
         }
-    }
 
 
 def get_json_paragraph_toolbar_refresh_event(context, request, table_factory=None, viewlet_factory=None):
@@ -122,34 +125,36 @@
         from pyams_content.component.paragraph.zmi.container import ParagraphTitleToolbarViewletManager as viewlet_factory
 
     parent = get_parent(context, IParagraphContainerTarget)
-    table = table_factory(context, request)
-    viewlet = viewlet_factory(context, request, table)
-    viewlet.update()
-    return {
-        'event': 'myams.refresh',
-        'options': {
-            'handler': 'PyAMS_content.paragraphs.updateToolbar',
-            'object_id': get_element_id(ParagraphContainerBaseTable, context, parent),
-            'toolbar_tag': viewlet.render()
+    if parent is not None:
+        table = table_factory(context, request)
+        viewlet = viewlet_factory(context, request, table)
+        viewlet.update()
+        return {
+            'event': 'myams.refresh',
+            'options': {
+                'handler': 'PyAMS_content.paragraphs.updateToolbar',
+                'object_id': get_element_id(ParagraphContainerBaseTable, context, parent),
+                'toolbar_tag': viewlet.render()
+            }
         }
-    }
 
 
 def get_json_paragraph_markers_refresh_event(context, request, form, viewlet_factory, marker_type=None):
     """Get JSON response value for paragraph markers refresh event"""
     parent = get_parent(context, IParagraphContainerTarget)
-    marker = viewlet_factory(context, request, form, None)
-    if marker is not None:
-        marker.update()
-        return {
-            'event': 'myams.refresh',
-            'options': {
-                'handler': 'PyAMS_content.paragraphs.updateMarkers',
-                'object_id': get_element_id(ParagraphContainerBaseTable, context, parent),
-                'marker_type': marker_type or marker.marker_type,
-                'marker_tag': marker.render()
+    if parent is not None:
+        marker = viewlet_factory(context, request, form, None)
+        if marker is not None:
+            marker.update()
+            return {
+                'event': 'myams.refresh',
+                'options': {
+                    'handler': 'PyAMS_content.paragraphs.updateMarkers',
+                    'object_id': get_element_id(ParagraphContainerBaseTable, context, parent),
+                    'marker_type': marker_type or marker.marker_type,
+                    'marker_tag': marker.render()
+                }
             }
-        }
 
 
 class BaseParagraphAddMenu(ProtectedFormObjectMixin, ToolbarMenuItem):
@@ -196,10 +201,24 @@
         }
 
 
+class IParagraphEditFormButtons(Interface):
+    """Paragraph edit form buttons"""
+
+    preview = ActionButton(name='preview', title=_("Preview"),
+                           label_css_class='fa fa-fw fa-eye',
+                           url='preview.html',
+                           modal_target=True)
+
+    close = CloseButton(name='close', title=_("Cancel"))
+
+    submit = button.Button(name='submit', title=_("Submit"), condition=check_submit_button)
+
+
 class BaseParagraphPropertiesEditForm(AdminDialogEditForm):
     """Base paragraph edit form"""
 
     prefix = 'paragraph.'
+    buttons = button.Buttons(IParagraphEditFormButtons)
 
     @property
     def title(self):
--- a/src/pyams_content/component/paragraph/zmi/contact.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/contact.py	Wed May 23 15:30:41 2018 +0200
@@ -21,7 +21,7 @@
 from pyams_content.component.paragraph.interfaces.contact import CONTACT_PARAGRAPH_TYPE, IContactParagraph
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphContainerView, IParagraphInnerEditor
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from transaction.interfaces import ITransactionManager
@@ -30,7 +30,8 @@
 # import packages
 from pyams_content.component.paragraph.contact import ContactParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAddMenu, BaseParagraphAJAXAddForm, \
-    BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, get_json_paragraph_refresh_event
+    BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, get_json_paragraph_refresh_event, \
+    IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
@@ -126,7 +127,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/frame.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/frame.py	Wed May 23 15:30:41 2018 +0200
@@ -23,7 +23,7 @@
 from pyams_content.component.paragraph.interfaces.frame import IFrameParagraph, FRAME_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.tinymce import ITinyMCEConfiguration
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
@@ -34,7 +34,8 @@
 from pyams_content.component.association.zmi import AssociationsTable
 from pyams_content.component.paragraph.frame import FrameParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_toolbar_refresh_event
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_toolbar_refresh_event, \
+    IParagraphEditFormButtons
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerTable, \
     ParagraphTitleToolbarViewletManager
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
@@ -182,7 +183,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/header.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/header.py	Wed May 23 15:30:41 2018 +0200
@@ -21,7 +21,7 @@
 from pyams_content.component.paragraph.interfaces.header import IHeaderParagraph, HEADER_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from z3c.form.interfaces import INPUT_MODE
@@ -29,7 +29,7 @@
 # import packages
 from pyams_content.component.paragraph.header import HeaderParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
@@ -133,7 +133,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/html.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/html.py	Wed May 23 15:30:41 2018 +0200
@@ -25,7 +25,7 @@
     HTML_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
@@ -36,7 +36,8 @@
 from pyams_content.component.association.zmi import AssociationsTable
 from pyams_content.component.paragraph.html import HTMLParagraph, RawParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_toolbar_refresh_event
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_toolbar_refresh_event, \
+    IParagraphEditFormButtons
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_switched_table_refresh_event
@@ -150,7 +151,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
@@ -286,7 +287,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/keynumber.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/keynumber.py	Wed May 23 15:30:41 2018 +0200
@@ -21,11 +21,11 @@
     IParagraphRenderer
 from pyams_content.component.paragraph.interfaces.keynumber import KEYNUMBER_PARAGRAPH_TYPE, IKeyNumberParagraph, \
     IKeyNumberContainer, IKeyNumberContainerTarget, IKeyNumber
-from pyams_content.component.paragraph.zmi import IParagraphContainerView
+from pyams_content.component.paragraph.zmi import IParagraphContainerView, IParagraphEditFormButtons
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common import IWfSharedContent
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons, IInnerSubForm
+from pyams_form.interfaces.form import IInnerForm, IInnerSubForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager
 from pyams_skin.layer import IPyAMSLayer
@@ -145,7 +145,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/keypoint.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/keypoint.py	Wed May 23 15:30:41 2018 +0200
@@ -21,7 +21,7 @@
 from pyams_content.component.paragraph.interfaces.keypoint import IKeypointsParagraph, KEYPOINTS_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from z3c.form.interfaces import INPUT_MODE
@@ -29,7 +29,7 @@
 # import packages
 from pyams_content.component.paragraph.keypoint import KeypointsParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
@@ -134,7 +134,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/milestone.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/milestone.py	Wed May 23 15:30:41 2018 +0200
@@ -21,11 +21,11 @@
     IParagraphRenderer
 from pyams_content.component.paragraph.interfaces.milestone import MILESTONE_PARAGRAPH_TYPE, IMilestoneParagraph, \
     IMilestoneContainer, IMilestoneContainerTarget, IMilestone
-from pyams_content.component.paragraph.zmi import IParagraphContainerView
+from pyams_content.component.paragraph.zmi import IParagraphContainerView, IParagraphEditFormButtons
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common import IWfSharedContent
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons, IInnerSubForm
+from pyams_form.interfaces.form import IInnerForm, IInnerSubForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager
 from pyams_skin.layer import IPyAMSLayer
@@ -146,7 +146,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/pictogram.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/pictogram.py	Wed May 23 15:30:41 2018 +0200
@@ -21,11 +21,11 @@
     IParagraphRenderer
 from pyams_content.component.paragraph.interfaces.pictogram import PICTOGRAM_PARAGRAPH_TYPE, IPictogramParagraph, \
     IPictogramContainer, IPictogramContainerTarget, IPictogramItem
-from pyams_content.component.paragraph.zmi import IParagraphContainerView
+from pyams_content.component.paragraph.zmi import IParagraphContainerView, IParagraphEditFormButtons
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common import IWfSharedContent
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons, IInnerSubForm
+from pyams_form.interfaces.form import IInnerForm, IInnerSubForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager
 from pyams_skin.layer import IPyAMSLayer
@@ -148,7 +148,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/preview.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/preview.py	Wed May 23 15:30:41 2018 +0200
@@ -22,7 +22,7 @@
 from pyams_skin.layer import IPyAMSLayer
 
 # import packages
-from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_utils.adapter import adapter_config
 
 
--- a/src/pyams_content/component/paragraph/zmi/verbatim.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/verbatim.py	Wed May 23 15:30:41 2018 +0200
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_skin.event import get_json_widget_refresh_event
 
 __docformat__ = 'restructuredtext'
 
@@ -23,7 +22,7 @@
 from pyams_content.component.paragraph.interfaces.verbatim import IVerbatimParagraph, VERBATIM_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from pyams_zmi.interfaces import IPropertiesEditForm
@@ -32,10 +31,11 @@
 # import packages
 from pyams_content.component.paragraph.verbatim import VerbatimParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.event import get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
@@ -130,7 +130,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/paragraph/zmi/video.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/video.py	Wed May 23 15:30:41 2018 +0200
@@ -22,7 +22,7 @@
 from pyams_content.component.paragraph.interfaces.video import IVideoParagraph, VIDEO_PARAGRAPH_TYPE
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from pyams_zmi.interfaces import IPropertiesEditForm
@@ -33,7 +33,7 @@
 from pyams_content.component.association.zmi import AssociationsTable
 from pyams_content.component.paragraph.video import VideoParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_form.group import NamedWidgetsGroup
@@ -172,7 +172,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/component/video/zmi/paragraph.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/component/video/zmi/paragraph.py	Wed May 23 15:30:41 2018 +0200
@@ -22,7 +22,7 @@
 from pyams_content.component.video.interfaces import IExternalVideoProvider, IExternalVideoSettings, \
     IExternalVideoParagraph, IExternalVideoRenderer, EXTERNAL_VIDEO_PARAGRAPH_TYPE
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_form.interfaces.form import IWidgetsSuffixViewletsManager, IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IWidgetsSuffixViewletsManager, IInnerForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
@@ -31,7 +31,8 @@
 
 # import packages
 from pyams_content.component.paragraph.zmi import BaseParagraphAddMenu, BaseParagraphAJAXAddForm, \
-    BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, get_json_paragraph_refresh_event
+    BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, get_json_paragraph_refresh_event, \
+    IParagraphEditFormButtons
 from pyams_content.component.video.paragraph import ExternalVideoParagraph
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
@@ -285,7 +286,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/footer/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.footer.interfaces import FOOTER_RENDERERS, IFooterRenderer, IFooterSettings, IFooterTarget, \
+    FOOTER_SETTINGS_KEY, IFooterRendererSettings, FOOTER_RENDERER_SETTINGS_KEY
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from persistent import Persistent
+from pyams_content.features.renderer import RenderedContentMixin
+from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
+from pyams_utils.inherit import BaseInheritInfo, InheritedFieldProperty
+from pyams_utils.request import check_request
+from pyams_utils.traversing import get_parent
+from pyams_utils.vocabulary import vocabulary_config
+from zope.interface import implementer, noLongerProvides, alsoProvides
+from zope.location import Location, locate
+from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+
+@implementer(IFooterSettings)
+class FooterSettings(BaseInheritInfo, RenderedContentMixin, Persistent, Location):
+    """Footer settings persistent class"""
+
+    target_interface = IFooterTarget
+    adapted_interface = IFooterSettings
+
+    _renderer = FieldProperty(IFooterSettings['renderer'])
+    renderer = InheritedFieldProperty(IFooterSettings['renderer'])
+
+    renderer_interface = IFooterRenderer
+
+    def get_renderer(self, request=None, renderer_name=None):
+        if request is None:
+            request = check_request()
+        if not renderer_name:
+            renderer_name = request.params.get('form.widgets.renderer')
+            if renderer_name is None:
+                renderer_name = self.renderer or 'hidden'
+        parent = get_parent(self, IFooterTarget)
+        return request.registry.queryMultiAdapter((parent, request), self.renderer_interface,
+                                                  name=renderer_name)
+
+    def get_settings(self, renderer_name=None):
+        renderer = self.get_renderer(renderer_name=renderer_name)
+        if (renderer is None) or (renderer.settings_interface is None):
+            return None
+        settings_key = FOOTER_RENDERER_SETTINGS_KEY.format(renderer.settings_key)
+        return get_annotation_adapter(self, settings_key,
+                                      factory=lambda: IFooterRendererSettings(renderer),
+                                      name='++settings++{0}'.format(renderer.name))
+
+    @property
+    def settings(self):
+        return self.get_settings()
+
+
+@adapter_config(context=IFooterTarget, provides=IFooterSettings)
+def footer_settings_factory(context):
+    """Footer settings factory"""
+    return get_annotation_adapter(context, FOOTER_SETTINGS_KEY, FooterSettings, name='++footer++')
+
+
+@adapter_config(name='footer', context=IFooterTarget, provides=ITraversable)
+class FooterTargetNamespace(ContextAdapter):
+    """Footer target '++footer++' namespace traverser"""
+
+    def traverse(self, name, furtherpath=None):
+        return IFooterSettings(self.context)
+
+
+@adapter_config(context=IFooterSettings, provides=IFooterRendererSettings)
+def footer_settings_renderer_settings_factory(context):
+    """Footer settings renderer settings factory"""
+    return context.settings
+
+
+@adapter_config(name='settings', context=IFooterSettings, provides=ITraversable)
+class FooterSettingsRendererSettingsNamespace(ContextAdapter):
+    """Footer settings '++settings++' namespace traverser"""
+
+    def traverse(self, name, furtherpath=None):
+        if name:
+            return self.context.get_settings(renderer_name=name)
+        else:
+            return IFooterRendererSettings(self.context)
+
+
+@adapter_config(context=IFooterTarget, provides=IFooterRendererSettings)
+def footer_target_renderer_settings_factory(context):
+    """Footer target renderer settings factory"""
+    settings = IFooterSettings(context)
+    return IFooterRendererSettings(settings, None)
+
+
+@vocabulary_config(name=FOOTER_RENDERERS)
+class FooterRendererVocabulary(SimpleVocabulary):
+    """Footer renderers vocabulary"""
+
+    def __init__(self, context=None):
+        request = check_request()
+        if context is None:
+            context = request.context
+        translate = request.localizer.translate
+        registry = request.registry
+        target = get_parent(context, IFooterTarget)
+        terms = [SimpleTerm(name, title=translate(adapter.label))
+                 for name, adapter in sorted(registry.getAdapters((target, request), IFooterRenderer),
+                                             key=lambda x: x[1].weight)]
+        super(FooterRendererVocabulary, self).__init__(terms)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/footer/interfaces/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,60 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.renderer.interfaces import IRenderedContent, IContentRenderer, IRendererSettings
+from pyams_utils.interfaces.inherit import IInheritInfo
+from zope.annotation.interfaces import IAttributeAnnotatable
+
+# import packages
+from zope.interface import Attribute
+from zope.schema import Choice
+
+from pyams_content import _
+
+
+FOOTER_SETTINGS_KEY = 'pyams_content.footer'
+FOOTER_RENDERER_SETTINGS_KEY = 'pyams_content.footer::{0}'
+
+FOOTER_RENDERERS = 'PyAMS.footer.renderers'
+
+
+class IFooterSettings(IInheritInfo, IRenderedContent):
+    """Footer settings interface"""
+
+    renderer = Choice(title=_("Footer template"),
+                      description=_("Presentation template used for this footer"),
+                      vocabulary=FOOTER_RENDERERS,
+                      required=False,
+                      default='hidden')
+
+    settings = Attribute("Renderer settings")
+
+
+class IFooterTarget(IAttributeAnnotatable):
+    """Footer target marker interface"""
+
+
+class IFooterRenderer(IContentRenderer):
+    """Footer renderer interface"""
+
+    name = Attribute("Renderer name")
+    settings_key = Attribute("Renderer settings key")
+
+
+class IFooterRendererSettings(IRendererSettings):
+    """Footer renderer settings interface"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/footer/skin/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.footer.interfaces import IFooterTarget, IFooterRenderer, IFooterSettings
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_utils.adapter import adapter_config
+from pyramid.decorator import reify
+
+from pyams_content import _
+
+
+class BaseFooterRenderer(BaseContentRenderer):
+    """Base footer renderer"""
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        settings = IFooterSettings(self.context)
+        while settings.inherit:
+            settings = IFooterSettings(settings.parent)
+        return settings.settings
+
+
+@adapter_config(name=HIDDEN_RENDERER_NAME, context=(IFooterTarget, IPyAMSLayer), provides=IFooterRenderer)
+class HiddenFooterRenderer(BaseFooterRenderer):
+    """Hidden footer renderer"""
+
+    name = HIDDEN_RENDERER_NAME
+    label = _("Hidden footer")
+    weight = -999
+
+    def render(self):
+        return ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/footer/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,218 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.footer.interfaces import IFooterTarget, IFooterSettings, IFooterRenderer, \
+    IFooterRendererSettings
+from pyams_form.interfaces.form import IWidgetForm, IUncheckedEditFormButtons, IInnerSubForm, \
+    IWidgetsSuffixViewletsManager
+from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
+from pyams_portal.zmi.interfaces import IPortalContextTemplatePropertiesMenu
+from pyams_skin.interfaces import IInnerPage
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces.data import IObjectData
+from pyams_utils.interfaces.inherit import IInheritInfo
+from z3c.form.interfaces import INPUT_MODE
+
+# import packages
+from pyams_form.form import AJAXEditForm
+from pyams_form.group import NamedWidgetsGroup
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.menu import MenuItem, MenuDivider
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config, Viewlet
+from pyams_zmi.form import AdminEditForm, InnerAdminEditForm
+from pyramid.exceptions import NotFound
+from pyramid.response import Response
+from pyramid.view import view_config
+from z3c.form import field, button
+from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
+from zope.interface import implementer, alsoProvides, Interface
+
+from pyams_content import _
+
+
+@viewlet_config(name='footer-settings.menu', context=IFooterTarget, layer=IPyAMSLayer,
+                manager=IPortalContextTemplatePropertiesMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=102)
+class FooterSettingsMenu(MenuItem):
+    """Footer settings menu"""
+
+    label = _("Page footer")
+    url = '#footer-settings.html'
+
+
+class IFooterSettingsGroup(Interface):
+    """Footer settings group marker interface"""
+
+
+@pagelet_config(name='footer-settings.html', context=IFooterTarget, layer=IPyAMSLayer,
+                permission=MANAGE_TEMPLATE_PERMISSION)
+@implementer(IWidgetForm, IInnerPage)
+class FooterSettingsEditForm(AdminEditForm):
+    """Footer settings edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit footer settings")
+
+    def getContent(self):
+        return IFooterSettings(self.context)
+
+    @property
+    def fields(self):
+        if self.getContent().can_inherit:
+            fields = field.Fields(IFooterSettings).select('no_inherit')
+            fields['no_inherit'].widgetFactory = SingleCheckBoxFieldWidget
+        else:
+            fields = field.Fields(Interface)
+        return fields
+
+    @property
+    def buttons(self):
+        if self.mode == INPUT_MODE:
+            return button.Buttons(IUncheckedEditFormButtons)
+        else:
+            return button.Buttons(Interface)
+
+    ajax_handler = 'footer-settings.json'
+
+    def updateGroups(self):
+        if self.getContent().can_inherit:
+            group = NamedWidgetsGroup(self, 'footer', self.widgets,
+                                      ('no_inherit', ),
+                                      legend=_("Don't inherit parent footer"),
+                                      css_class='inner',
+                                      switch=True,
+                                      checkbox_switch=True,
+                                      checkbox_mode='disable',
+                                      checkbox_field=IFooterSettings['no_inherit'])
+        else:
+            group = NamedWidgetsGroup(self, 'footer', self.widgets, (), css_class='inner')
+        alsoProvides(group, IFooterSettingsGroup)
+        self.add_group(group)
+        super(FooterSettingsEditForm, self).updateGroups()
+
+
+@view_config(name='footer-settings.json', context=IFooterTarget, request_type=IPyAMSLayer,
+             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class FooterSettingsAJAXEditForm(AJAXEditForm, FooterSettingsEditForm):
+    """Footer settings edit form, JSON renderer"""
+
+    def get_ajax_output(self, changes):
+        output = super(FooterSettingsAJAXEditForm, self).get_ajax_output(changes) or {}
+        if 'no_inherit' in changes.get(IInheritInfo, ()):
+            output['status'] = 'reload'
+        return output
+
+
+@adapter_config(name='renderer', context=(IFooterTarget, IPyAMSLayer, IFooterSettingsGroup), provides=IInnerSubForm)
+class FooterSettingsRendererEditSubform(InnerAdminEditForm):
+    """Footer settings renderer edit form"""
+
+    legend = None
+
+    fields = field.Fields(IFooterSettings).select('renderer')
+    weight = 1
+
+    def __init__(self, context, request, group):
+        context = IFooterSettings(context)
+        super(FooterSettingsRendererEditSubform, self).__init__(context, request, group)
+
+    def updateWidgets(self, prefix=None):
+        super(FooterSettingsRendererEditSubform, self).updateWidgets(prefix)
+        if 'renderer' in self.widgets:
+            widget = self.widgets['renderer']
+            widget.object_data = {
+                'ams-change-handler': 'MyAMS.helpers.select2ChangeHelper',
+                'ams-change-stop-propagation': 'true',
+                'ams-select2-helper-type': 'html',
+                'ams-select2-helper-url': absolute_url(self.context, self.request,
+                                                       'get-footer-settings-renderer-form.html'),
+                'ams-select2-helper-argument': 'form.widgets.renderer',
+                'ams-select2-helper-target': '#renderer-settings-helper'
+            }
+            alsoProvides(widget, IObjectData)
+
+    def get_forms(self, include_self=True):
+        if include_self and self.request.method == 'POST':
+            data, errors = self.extractData()
+            if not errors:
+                self.applyChanges(data)
+        for form in super(FooterSettingsRendererEditSubform, self).get_forms(include_self):
+            yield form
+
+
+@adapter_config(name='footer-renderer-settings-form',
+                context=(IFooterRendererSettings, IPyAMSLayer, FooterSettingsRendererEditSubform),
+                provides=IInnerSubForm)
+@adapter_config(name='footer-renderer-settings-form',
+                context=(IFooterTarget, IPyAMSLayer, FooterSettingsAJAXEditForm),
+                provides=IInnerSubForm)
+class FooterSettingsRendererSettingsEditForm(InnerAdminEditForm):
+    """Footer settings renderer settings edit form"""
+
+    legend = _("Footer renderer settings")
+
+    def __new__(cls, context, request, view=None):
+        settings = IFooterRendererSettings(context, None)
+        if settings is None:
+            return None
+        return InnerAdminEditForm.__new__(cls)
+
+    def __init__(self, context, request, view=None):
+        context = IFooterRendererSettings(context)
+        super(FooterSettingsRendererSettingsEditForm, self).__init__(context, request, view)
+
+
+@viewlet_config(name='footer-renderer-settings', context=IFooterSettings, layer=IPyAMSLayer,
+                view=FooterSettingsRendererEditSubform, manager=IWidgetsSuffixViewletsManager)
+@template_config(template='templates/renderer-settings.pt', layer=IPyAMSLayer)
+class FooterSettingsRendererSettingsWidgetsSuffix(Viewlet):
+    """Footer settings renderer settings viewlet"""
+
+    def render_edit_form(self):
+        settings = IFooterSettings(self.context)
+        renderer = settings.get_renderer(self.request)
+        if (renderer is None) or (renderer.settings_interface is None):
+            return ''
+        renderer_settings = IFooterRendererSettings(settings)
+        form = FooterSettingsRendererSettingsEditForm(renderer_settings, self.request)
+        form.update()
+        return form.render()
+
+
+@view_config(name='get-footer-settings-renderer-form.html', context=IFooterSettings,
+             request_type=IPyAMSLayer, permission=MANAGE_TEMPLATE_PERMISSION, xhr=True)
+def get_footer_settings_renderer_form(request):
+    """Footer settings renderer settings form"""
+    renderer_name = request.params.get('form.widgets.renderer')
+    if renderer_name is None:
+        raise NotFound("No provided renderer argument")
+    if not renderer_name:
+        renderer_name = ''
+    renderer = request.registry.queryMultiAdapter((request.context, request), IFooterRenderer, name=renderer_name)
+    if (renderer is None) or (renderer.settings_interface is None):
+        return Response('')
+    settings = IFooterSettings(request.context)
+    renderer_settings = IFooterRendererSettings(settings)
+    form = FooterSettingsRendererSettingsEditForm(renderer_settings, request)
+    form.update()
+    return Response(form.render())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/footer/zmi/templates/renderer-settings.pt	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,3 @@
+<div id="renderer-settings-helper">
+	<tal:var replace="structure view.render_edit_form()">Edit form</tal:var>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/header/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.header.interfaces import HEADER_RENDERERS, IHeaderRenderer, IHeaderSettings, IHeaderTarget, \
+    HEADER_SETTINGS_KEY, IHeaderRendererSettings, HEADER_RENDERER_SETTINGS_KEY
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from persistent import Persistent
+from pyams_content.features.renderer import RenderedContentMixin
+from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
+from pyams_utils.inherit import BaseInheritInfo, InheritedFieldProperty
+from pyams_utils.request import check_request
+from pyams_utils.traversing import get_parent
+from pyams_utils.vocabulary import vocabulary_config
+from zope.interface import implementer, noLongerProvides, alsoProvides
+from zope.location import Location, locate
+from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+
+@implementer(IHeaderSettings)
+class HeaderSettings(BaseInheritInfo, RenderedContentMixin, Persistent, Location):
+    """Header settings persistent class"""
+
+    target_interface = IHeaderTarget
+    adapted_interface = IHeaderSettings
+
+    _renderer = FieldProperty(IHeaderSettings['renderer'])
+    renderer = InheritedFieldProperty(IHeaderSettings['renderer'])
+
+    renderer_interface = IHeaderRenderer
+
+    def get_renderer(self, request=None, renderer_name=None):
+        if request is None:
+            request = check_request()
+        if not renderer_name:
+            renderer_name = request.params.get('form.widgets.renderer')
+            if renderer_name is None:
+                renderer_name = self.renderer or 'hidden'
+        parent = get_parent(self, IHeaderTarget)
+        return request.registry.queryMultiAdapter((parent, request), self.renderer_interface,
+                                                  name=renderer_name)
+
+    def get_settings(self, renderer_name=None):
+        renderer = self.get_renderer(renderer_name=renderer_name)
+        if (renderer is None) or (renderer.settings_interface is None):
+            return None
+        settings_key = HEADER_RENDERER_SETTINGS_KEY.format(renderer.settings_key)
+        return get_annotation_adapter(self, settings_key,
+                                      factory=lambda: IHeaderRendererSettings(renderer),
+                                      name='++settings++{0}'.format(renderer.name))
+
+    @property
+    def settings(self):
+        return self.get_settings()
+
+
+@adapter_config(context=IHeaderTarget, provides=IHeaderSettings)
+def header_settings_factory(context):
+    """Header settings factory"""
+    return get_annotation_adapter(context, HEADER_SETTINGS_KEY, HeaderSettings, name='++header++')
+
+
+@adapter_config(name='header', context=IHeaderTarget, provides=ITraversable)
+class HeaderTargetNamespace(ContextAdapter):
+    """Header target '++header++' namespace traverser"""
+
+    def traverse(self, name, furtherpath=None):
+        return IHeaderSettings(self.context)
+
+
+@adapter_config(context=IHeaderSettings, provides=IHeaderRendererSettings)
+def header_settings_renderer_settings_factory(context):
+    """Header settings renderer settings factory"""
+    return context.settings
+
+
+@adapter_config(name='settings', context=IHeaderSettings, provides=ITraversable)
+class HeaderSettingsRendererSettingsNamespace(ContextAdapter):
+    """Header settings '++settings++' namespace traverser"""
+
+    def traverse(self, name, furtherpath=None):
+        if name:
+            return self.context.get_settings(renderer_name=name)
+        else:
+            return IHeaderRendererSettings(self.context)
+
+
+@adapter_config(context=IHeaderTarget, provides=IHeaderRendererSettings)
+def header_target_renderer_settings_factory(context):
+    """Header target renderer settings factory"""
+    settings = IHeaderSettings(context)
+    return IHeaderRendererSettings(settings, None)
+
+
+@vocabulary_config(name=HEADER_RENDERERS)
+class HeaderRendererVocabulary(SimpleVocabulary):
+    """Header renderers vocabulary"""
+
+    def __init__(self, context=None):
+        request = check_request()
+        if context is None:
+            context = request.context
+        translate = request.localizer.translate
+        registry = request.registry
+        target = get_parent(context, IHeaderTarget)
+        terms = [SimpleTerm(name, title=translate(adapter.label))
+                 for name, adapter in sorted(registry.getAdapters((target, request), IHeaderRenderer),
+                                             key=lambda x: x[1].weight)]
+        super(HeaderRendererVocabulary, self).__init__(terms)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/header/interfaces/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,60 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.renderer import IContentRenderer, IRendererSettings, IRenderedContent
+from pyams_utils.interfaces.inherit import IInheritInfo
+from zope.annotation.interfaces import IAttributeAnnotatable
+
+# import packages
+from zope.interface import Attribute
+from zope.schema import Choice
+
+from pyams_content import _
+
+
+HEADER_SETTINGS_KEY = 'pyams_content.header'
+HEADER_RENDERER_SETTINGS_KEY = 'pyams_content.header::{0}'
+
+HEADER_RENDERERS = 'PyAMS.header.renderers'
+
+
+class IHeaderSettings(IInheritInfo, IRenderedContent):
+    """Header settings interface"""
+
+    renderer = Choice(title=_("Header template"),
+                      description=_("Presentation template used for this header"),
+                      vocabulary=HEADER_RENDERERS,
+                      required=False,
+                      default='hidden')
+
+    settings = Attribute("Renderer settings")
+
+
+class IHeaderTarget(IAttributeAnnotatable):
+    """Header target marker interface"""
+
+
+class IHeaderRenderer(IContentRenderer):
+    """Header renderer interface"""
+
+    name = Attribute("Renderer name")
+    settings_key = Attribute("Renderer settings key")
+
+
+class IHeaderRendererSettings(IRendererSettings):
+    """Header renderer settings interface"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/header/skin/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderRenderer, IHeaderSettings
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_utils.adapter import adapter_config
+from pyramid.decorator import reify
+
+from pyams_content import _
+
+
+class BaseHeaderRenderer(BaseContentRenderer):
+    """Base header renderer"""
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        settings = IHeaderSettings(self.context)
+        while settings.inherit:
+            settings = IHeaderSettings(settings.parent)
+        return settings.settings
+
+
+@adapter_config(name=HIDDEN_RENDERER_NAME, context=(IHeaderTarget, IPyAMSLayer), provides=IHeaderRenderer)
+class HiddenHeaderRenderer(BaseHeaderRenderer):
+    """Hidden header renderer"""
+
+    name = HIDDEN_RENDERER_NAME
+    label = _("Hidden header")
+    weight = -999
+
+    def render(self):
+        return ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/header/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,224 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderSettings, IHeaderRenderer, \
+    IHeaderRendererSettings
+from pyams_form.interfaces.form import IWidgetForm, IUncheckedEditFormButtons, IInnerSubForm, \
+    IWidgetsSuffixViewletsManager
+from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
+from pyams_portal.zmi.interfaces import IPortalContextTemplatePropertiesMenu
+from pyams_skin.interfaces import IInnerPage
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces.data import IObjectData
+from pyams_utils.interfaces.inherit import IInheritInfo
+from z3c.form.interfaces import INPUT_MODE
+
+# import packages
+from pyams_form.form import AJAXEditForm
+from pyams_form.group import NamedWidgetsGroup
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.menu import MenuItem, MenuDivider
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config, Viewlet
+from pyams_zmi.form import AdminEditForm, InnerAdminEditForm
+from pyramid.exceptions import NotFound
+from pyramid.response import Response
+from pyramid.view import view_config
+from z3c.form import field, button
+from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
+from zope.interface import implementer, alsoProvides, Interface
+
+from pyams_content import _
+
+
+@viewlet_config(name='header-settings.divider', context=IHeaderTarget, layer=IPyAMSLayer,
+                manager=IPortalContextTemplatePropertiesMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=99)
+class HeaderSettingsDivider(MenuDivider):
+    """Header settings menu divider"""
+
+
+@viewlet_config(name='header-settings.menu', context=IHeaderTarget, layer=IPyAMSLayer,
+                manager=IPortalContextTemplatePropertiesMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=100)
+class HeaderSettingsMenu(MenuItem):
+    """Header settings menu"""
+
+    label = _("Page header")
+    url = '#header-settings.html'
+
+
+class IHeaderSettingsGroup(Interface):
+    """Header settings group marker interface"""
+
+
+@pagelet_config(name='header-settings.html', context=IHeaderTarget, layer=IPyAMSLayer,
+                permission=MANAGE_TEMPLATE_PERMISSION)
+@implementer(IWidgetForm, IInnerPage)
+class HeaderSettingsEditForm(AdminEditForm):
+    """Header settings edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit header settings")
+
+    def getContent(self):
+        return IHeaderSettings(self.context)
+
+    @property
+    def fields(self):
+        if self.getContent().can_inherit:
+            fields = field.Fields(IHeaderSettings).select('no_inherit')
+            fields['no_inherit'].widgetFactory = SingleCheckBoxFieldWidget
+        else:
+            fields = field.Fields(Interface)
+        return fields
+
+    @property
+    def buttons(self):
+        if self.mode == INPUT_MODE:
+            return button.Buttons(IUncheckedEditFormButtons)
+        else:
+            return button.Buttons(Interface)
+
+    ajax_handler = 'header-settings.json'
+
+    def updateGroups(self):
+        if self.getContent().can_inherit:
+            group = NamedWidgetsGroup(self, 'header', self.widgets,
+                                      ('no_inherit', ),
+                                      legend=_("Don't inherit parent header"),
+                                      css_class='inner',
+                                      switch=True,
+                                      checkbox_switch=True,
+                                      checkbox_mode='disable',
+                                      checkbox_field=IHeaderSettings['no_inherit'])
+        else:
+            group = NamedWidgetsGroup(self, 'header', self.widgets, (), css_class='inner')
+        alsoProvides(group, IHeaderSettingsGroup)
+        self.add_group(group)
+        super(HeaderSettingsEditForm, self).updateGroups()
+
+
+@view_config(name='header-settings.json', context=IHeaderTarget, request_type=IPyAMSLayer,
+             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class HeaderSettingsAJAXEditForm(AJAXEditForm, HeaderSettingsEditForm):
+    """Header settings edit form, JSON renderer"""
+
+    def get_ajax_output(self, changes):
+        output = super(HeaderSettingsAJAXEditForm, self).get_ajax_output(changes) or {}
+        if 'no_inherit' in changes.get(IInheritInfo, ()):
+            output['status'] = 'reload'
+        return output
+
+
+@adapter_config(name='renderer', context=(IHeaderTarget, IPyAMSLayer, IHeaderSettingsGroup), provides=IInnerSubForm)
+class HeaderSettingsRendererEditSubform(InnerAdminEditForm):
+    """Header settings renderer edit form"""
+
+    legend = None
+
+    fields = field.Fields(IHeaderSettings).select('renderer')
+    weight = 1
+
+    def __init__(self, context, request, group):
+        context = IHeaderSettings(context)
+        super(HeaderSettingsRendererEditSubform, self).__init__(context, request, group)
+
+    def updateWidgets(self, prefix=None):
+        super(HeaderSettingsRendererEditSubform, self).updateWidgets(prefix)
+        if 'renderer' in self.widgets:
+            widget = self.widgets['renderer']
+            widget.object_data = {
+                'ams-change-handler': 'MyAMS.helpers.select2ChangeHelper',
+                'ams-change-stop-propagation': 'true',
+                'ams-select2-helper-type': 'html',
+                'ams-select2-helper-url': absolute_url(self.context, self.request,
+                                                       'get-header-settings-renderer-form.html'),
+                'ams-select2-helper-argument': 'form.widgets.renderer',
+                'ams-select2-helper-target': '#renderer-settings-helper'
+            }
+            alsoProvides(widget, IObjectData)
+
+    def get_forms(self, include_self=True):
+        if include_self and self.request.method == 'POST':
+            data, errors = self.extractData()
+            if not errors:
+                self.applyChanges(data)
+        for form in super(HeaderSettingsRendererEditSubform, self).get_forms(include_self):
+            yield form
+
+
+@adapter_config(name='header-renderer-settings-form',
+                context=(IHeaderRendererSettings, IPyAMSLayer, HeaderSettingsRendererEditSubform),
+                provides=IInnerSubForm)
+@adapter_config(name='header-renderer-settings-form',
+                context=(IHeaderTarget, IPyAMSLayer, HeaderSettingsAJAXEditForm),
+                provides=IInnerSubForm)
+class HeaderSettingsRendererSettingsEditForm(InnerAdminEditForm):
+    """Header settings renderer settings edit form"""
+
+    legend = _("Header renderer settings")
+
+    def __new__(cls, context, request, view=None):
+        settings = IHeaderRendererSettings(context, None)
+        if settings is None:
+            return None
+        return InnerAdminEditForm.__new__(cls)
+
+    def __init__(self, context, request, view=None):
+        context = IHeaderRendererSettings(context)
+        super(HeaderSettingsRendererSettingsEditForm, self).__init__(context, request, view)
+
+
+@viewlet_config(name='header-renderer-settings', context=IHeaderSettings, layer=IPyAMSLayer,
+                view=HeaderSettingsRendererEditSubform, manager=IWidgetsSuffixViewletsManager)
+@template_config(template='templates/renderer-settings.pt', layer=IPyAMSLayer)
+class HeaderSettingsRendererSettingsWidgetsSuffix(Viewlet):
+    """Header settings renderer settings viewlet"""
+
+    def render_edit_form(self):
+        settings = IHeaderSettings(self.context)
+        renderer = settings.get_renderer(self.request)
+        if (renderer is None) or (renderer.settings_interface is None):
+            return ''
+        renderer_settings = IHeaderRendererSettings(settings)
+        form = HeaderSettingsRendererSettingsEditForm(renderer_settings, self.request)
+        form.update()
+        return form.render()
+
+
+@view_config(name='get-header-settings-renderer-form.html', context=IHeaderSettings,
+             request_type=IPyAMSLayer, permission=MANAGE_TEMPLATE_PERMISSION, xhr=True)
+def get_header_settings_renderer_form(request):
+    """Header settings renderer settings form"""
+    renderer_name = request.params.get('form.widgets.renderer')
+    if renderer_name is None:
+        raise NotFound("No provided renderer argument")
+    if not renderer_name:
+        renderer_name = ''
+    renderer = request.registry.queryMultiAdapter((request.context, request), IHeaderRenderer, name=renderer_name)
+    if (renderer is None) or (renderer.settings_interface is None):
+        return Response('')
+    settings = IHeaderSettings(request.context)
+    renderer_settings = IHeaderRendererSettings(settings)
+    form = HeaderSettingsRendererSettingsEditForm(renderer_settings, request)
+    form.update()
+    return Response(form.render())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/header/zmi/templates/renderer-settings.pt	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,3 @@
+<div id="renderer-settings-helper">
+	<tal:var replace="structure view.render_edit_form()">Edit form</tal:var>
+</div>
--- a/src/pyams_content/features/renderer/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/features/renderer/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -29,11 +29,12 @@
     """Renderer mixin interface"""
 
     renderer = None
+    renderer_interface = IContentRenderer
 
     def get_renderer(self, request=None):
         if request is None:
             request = check_request()
-        return request.registry.queryMultiAdapter((self, request), IContentRenderer, name=self.renderer)
+        return request.registry.queryMultiAdapter((self, request), self.renderer_interface, name=self.renderer)
 
 
 @adapter_config(context=IRenderedContent, provides=IContentRenderer)
--- a/src/pyams_content/features/renderer/interfaces/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/features/renderer/interfaces/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -23,6 +23,9 @@
 from zope.interface import Interface, Attribute
 
 
+HIDDEN_RENDERER_NAME = 'hidden'
+
+
 class IRenderedContent(IAttributeAnnotatable):
     """Generic interface for any rendered content"""
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/renderer/skin/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -0,0 +1,71 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.renderer.interfaces import IContentRenderer, IRendererSettings, IRenderedContent, \
+    HIDDEN_RENDERER_NAME
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_template.template import get_view_template
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyramid.decorator import reify
+from zope.interface import implementer
+
+from pyams_content import _
+
+
+@implementer(IContentRenderer)
+class BaseContentRenderer(ContextRequestAdapter):
+    """Base content renderer"""
+
+    label = None
+    weight = 0
+    settings_interface = None
+
+    language = None
+    context_attrs = ()
+    i18n_context_attrs = ()
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        return IRendererSettings(self.context)
+
+    def update(self):
+        for attr in self.context_attrs:
+            setattr(self, attr, getattr(self.context, attr, None))
+        if self.i18n_context_attrs:
+            i18n = II18n(self.context, None)
+            if i18n is not None:
+                for attr in self.i18n_context_attrs:
+                    setattr(self, attr, i18n.get_attribute(attr, lang=self.language, request=self.request))
+
+    render = get_view_template()
+
+
+@adapter_config(name=HIDDEN_RENDERER_NAME, context=(IRenderedContent, IPyAMSLayer), provides=IContentRenderer)
+class HiddenContentRenderer(BaseContentRenderer):
+    """Hidden content renderer"""
+
+    label = _("Hidden content")
+    weight = -999
+
+    def render(self):
+        return ''
--- a/src/pyams_content/features/renderer/zmi/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/features/renderer/zmi/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -18,20 +18,16 @@
 # import interfaces
 from pyams_content.features.renderer.interfaces import IRenderedContent, IContentRenderer, IRendererSettings
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_i18n.interfaces import II18n
 from pyams_skin.layer import IPyAMSLayer
 
 # import packages
 from pyams_form.form import AJAXEditForm
 from pyams_pagelet.pagelet import pagelet_config
-from pyams_template.template import get_view_template
-from pyams_utils.adapter import ContextRequestAdapter, adapter_config
 from pyams_viewlet.viewlet import BaseContentProvider
 from pyams_zmi.form import AdminDialogEditForm
-from pyramid.decorator import reify
 from pyramid.view import view_config
 from z3c.form import field
-from zope.interface import implementer, Interface
+from zope.interface import Interface
 
 from pyams_content import _
 
@@ -65,35 +61,6 @@
 # Base content renderer
 #
 
-@implementer(IContentRenderer)
-class BaseContentRenderer(ContextRequestAdapter):
-    """Base content renderer"""
-
-    label = None
-    weight = 0
-    settings_interface = None
-
-    language = None
-    context_attrs = ()
-    i18n_context_attrs = ()
-
-    @reify
-    def settings(self):
-        if self.settings_interface is None:
-            return None
-        return IRendererSettings(self.context)
-
-    def update(self):
-        for attr in self.context_attrs:
-            setattr(self, attr, getattr(self.context, attr, None))
-        if self.i18n_context_attrs:
-            i18n = II18n(self.context, None)
-            if i18n is not None:
-                for attr in self.i18n_context_attrs:
-                    setattr(self, attr, i18n.get_attribute(attr, lang=self.language, request=self.request))
-
-    render = get_view_template()
-
 
 @pagelet_config(name='renderer-properties.html', context=IRenderedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
@@ -121,18 +88,3 @@
              permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class RendererPropertiesAJAXEditForm(AJAXEditForm, RendererPropertiesEditForm):
     """Renderer properties edit form, JSON renderer"""
-
-
-#
-# Default common renderers
-#
-
-@adapter_config(name='hidden', context=(IRenderedContent, IPyAMSLayer), provides=IContentRenderer)
-class HiddenContentRenderer(BaseContentRenderer):
-    """Hidden content renderer"""
-
-    label = _("Hidden content")
-    weight = -999
-
-    def render(self):
-        return ''
Binary file src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo has changed
--- a/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po	Wed May 23 15:30:41 2018 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-04-26 16:39+0200\n"
+"POT-Creation-Date: 2018-05-15 16:31+0200\n"
 "PO-Revision-Date: 2015-09-10 10:42+0200\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -95,7 +95,7 @@
 msgid "Update media properties"
 msgstr "Propriétés du média"
 
-#: src/pyams_content/component/gallery/zmi/file.py:246
+#: src/pyams_content/component/gallery/zmi/file.py:249
 msgid "Remove media..."
 msgstr "Supprimer le média"
 
@@ -103,7 +103,7 @@
 msgid "Show/hide media"
 msgstr "Cliquez pour rendre le média visible ou non"
 
-#: src/pyams_content/component/gallery/zmi/file.py:214
+#: src/pyams_content/component/gallery/zmi/file.py:216
 msgid "Audio content"
 msgstr "Contenu audio associé"
 
@@ -700,7 +700,7 @@
 "REMARQUE : supprimer des types de la liste des types de blocs autorisés sera "
 "sans effet sur les contenus existants."
 
-#: src/pyams_content/component/paragraph/zmi/__init__.py:194
+#: src/pyams_content/component/paragraph/zmi/__init__.py:197
 msgid "Paragraph was correctly added."
 msgstr "Le bloc a été ajouté."
 
@@ -712,15 +712,15 @@
 msgid "Add new video paragraph"
 msgstr "Ajout d'une vidéo"
 
-#: src/pyams_content/component/paragraph/zmi/video.py:112
-#: src/pyams_content/component/video/zmi/paragraph.py:193
+#: src/pyams_content/component/paragraph/zmi/video.py:115
+#: src/pyams_content/component/video/zmi/paragraph.py:208
 msgid "Edit video properties"
 msgstr "Propriétés de la vidéo"
 
-#: src/pyams_content/component/paragraph/zmi/video.py:84
-#: src/pyams_content/component/paragraph/zmi/video.py:131
-#: src/pyams_content/component/video/zmi/paragraph.py:87
-#: src/pyams_content/component/video/zmi/paragraph.py:216
+#: src/pyams_content/component/paragraph/zmi/video.py:86
+#: src/pyams_content/component/paragraph/zmi/video.py:136
+#: src/pyams_content/component/video/zmi/paragraph.py:102
+#: src/pyams_content/component/video/zmi/paragraph.py:236
 msgid "HTML content"
 msgstr "Contenu HTML"
 
@@ -1186,10 +1186,12 @@
 msgstr "Coordonnées GPS de situation du contact"
 
 #: src/pyams_content/component/paragraph/interfaces/header.py:43
+#: src/pyams_content/features/header/interfaces/__init__.py:39
 msgid "Header template"
 msgstr "Mode de rendu"
 
 #: src/pyams_content/component/paragraph/interfaces/header.py:44
+#: src/pyams_content/features/header/interfaces/__init__.py:40
 msgid "Presentation template used for this header"
 msgstr "Mode de rendu utilisé par ce chapô"
 
@@ -1225,13 +1227,13 @@
 msgid "Associations paragraph"
 msgstr "Liens et pièces jointes"
 
-#: src/pyams_content/component/association/container.py:95
-#: src/pyams_content/component/association/zmi/__init__.py:282
+#: src/pyams_content/component/association/container.py:88
+#: src/pyams_content/component/association/zmi/__init__.py:288
 msgid "Associations"
 msgstr "Liens et pièces jointes"
 
 #: src/pyams_content/component/association/zmi/paragraph.py:56
-#: src/pyams_content/component/association/zmi/__init__.py:94
+#: src/pyams_content/component/association/zmi/__init__.py:95
 msgid "Associations..."
 msgstr "Liens et pièces jointes"
 
@@ -1243,24 +1245,24 @@
 msgid "Edit association paragraph properties"
 msgstr "Propriétés du bloc « liens et pièces jointes »"
 
-#: src/pyams_content/component/association/zmi/__init__.py:187
+#: src/pyams_content/component/association/zmi/__init__.py:193
 msgid "Public title"
 msgstr "Libellé public"
 
-#: src/pyams_content/component/association/zmi/__init__.py:204
+#: src/pyams_content/component/association/zmi/__init__.py:210
 msgid "Inner title"
 msgstr "Contenu interne"
 
-#: src/pyams_content/component/association/zmi/__init__.py:219
+#: src/pyams_content/component/association/zmi/__init__.py:225
 msgid "Size"
 msgstr "Taille"
 
-#: src/pyams_content/component/association/zmi/__init__.py:259
-#: src/pyams_content/component/association/zmi/__init__.py:269
+#: src/pyams_content/component/association/zmi/__init__.py:265
+#: src/pyams_content/component/association/zmi/__init__.py:275
 msgid "Associations list"
 msgstr "Liste des liens et pièces jointes"
 
-#: src/pyams_content/component/association/zmi/__init__.py:64
+#: src/pyams_content/component/association/zmi/__init__.py:65
 msgid "Association was correctly added."
 msgstr "L'association a été ajoutée."
 
@@ -1276,19 +1278,19 @@
 msgid "Presentation template used for associations"
 msgstr "Modèle de présentation utilisé par ce bloc de contenu"
 
-#: src/pyams_content/component/links/__init__.py:103
+#: src/pyams_content/component/links/__init__.py:104
 msgid "Internal link"
 msgstr "Lien interne"
 
-#: src/pyams_content/component/links/__init__.py:178
+#: src/pyams_content/component/links/__init__.py:186
 msgid "External link"
 msgstr "Lien externe"
 
-#: src/pyams_content/component/links/__init__.py:224
+#: src/pyams_content/component/links/__init__.py:239
 msgid "Mailto link"
 msgstr "Lien mailto"
 
-#: src/pyams_content/component/links/__init__.py:165
+#: src/pyams_content/component/links/__init__.py:173
 msgid "target is not published"
 msgstr "le contenu ciblé n'est pas publié"
 
@@ -1410,11 +1412,11 @@
 msgid "Dailymotion settings"
 msgstr "Paramètres Dailymotion"
 
-#: src/pyams_content/component/video/provider/__init__.py:66
+#: src/pyams_content/component/video/provider/__init__.py:70
 msgid "Other provider"
 msgstr "Autre fournisseur"
 
-#: src/pyams_content/component/video/provider/__init__.py:90
+#: src/pyams_content/component/video/provider/__init__.py:94
 msgid "Custom video settings"
 msgstr "Paramètres spécifiques"
 
@@ -1431,7 +1433,8 @@
 "Please select integration code provided by your video provider, and paste it "
 "here"
 msgstr ""
-"Veuillez sélectionner le code d'intégration proposé par votre fournisseur et le coller ici"
+"Veuillez sélectionner le code d'intégration proposé par votre fournisseur et "
+"le coller ici"
 
 #: src/pyams_content/component/video/provider/interfaces.py:40
 #: src/pyams_content/component/video/provider/interfaces.py:112
@@ -1607,8 +1610,7 @@
 
 #: src/pyams_content/component/video/provider/interfaces.py:143
 msgid "If 'yes', Dailymotion branding will be displayed"
-msgstr ""
-"Si 'oui', la marque 'Dailymotion' est affichée en bas de la vidéo"
+msgstr "Si 'oui', la marque 'Dailymotion' est affichée en bas de la vidéo"
 
 #: src/pyams_content/component/video/provider/interfaces.py:147
 msgid "Show end screen?"
@@ -1672,16 +1674,16 @@
 msgid "Add new external video..."
 msgstr "Ajout d'une vidéo externe"
 
-#: src/pyams_content/component/video/zmi/paragraph.py:140
+#: src/pyams_content/component/video/zmi/paragraph.py:144
 msgid "Video provider is required"
 msgstr "Le nom du fournisseur est obligatoire"
 
-#: src/pyams_content/component/video/zmi/paragraph.py:163
-#: src/pyams_content/component/video/zmi/paragraph.py:238
+#: src/pyams_content/component/video/zmi/paragraph.py:193
+#: src/pyams_content/component/video/zmi/paragraph.py:257
 msgid "Video provider settings"
 msgstr "Paramètres liés au fournisseur"
 
-#: src/pyams_content/component/video/zmi/paragraph.py:179
+#: src/pyams_content/component/video/zmi/paragraph.py:173
 msgid "Other settings"
 msgstr "Autres paramètres"
 
@@ -2400,11 +2402,11 @@
 msgid "Edit manager restrictions for « {0} »"
 msgstr "Gérer le périmètre d'intervention de « {0} »"
 
-#: src/pyams_content/shared/common/zmi/security.py:216
+#: src/pyams_content/shared/common/zmi/security.py:220
 msgid "Apply contents restrictions"
 msgstr "Appliquer des restrictions d'accès"
 
-#: src/pyams_content/shared/common/zmi/security.py:218
+#: src/pyams_content/shared/common/zmi/security.py:222
 msgid ""
 "You can specify which contents this manager will be able to manage. If you "
 "specify several criteria, the manager will be able to manage contents for "
@@ -4315,12 +4317,12 @@
 msgid "List of selected pictograms which will be available to shared contents"
 msgstr "Liste des pictogrammes proposés dans les contenus partagés"
 
-#: src/pyams_content/features/renderer/zmi/__init__.py:105
+#: src/pyams_content/features/renderer/zmi/__init__.py:72
 #: src/pyams_content/features/renderer/zmi/templates/renderer-input.pt:4
 msgid "Edit renderer properties"
 msgstr "Propriétés du mode de rendu"
 
-#: src/pyams_content/features/renderer/zmi/__init__.py:134
+#: src/pyams_content/features/renderer/skin/__init__.py:67
 msgid "Hidden content"
 msgstr "Contenu non affiché"
 
@@ -4480,6 +4482,34 @@
 msgid "No currently defined alert."
 msgstr "Aucune alerte n'est définie actuellement."
 
+#: src/pyams_content/features/footer/zmi/__init__.py:56
+msgid "Page footer"
+msgstr "Pied de pages"
+
+#: src/pyams_content/features/footer/zmi/__init__.py:74
+msgid "Edit footer settings"
+msgstr "Paramétrage des pieds de pages"
+
+#: src/pyams_content/features/footer/zmi/__init__.py:172
+msgid "Footer renderer settings"
+msgstr "Propriétés du mode de rendu"
+
+#: src/pyams_content/features/footer/zmi/__init__.py:101
+msgid "Don't inherit parent footer"
+msgstr "Ne pas hériter du pied de pages du parent"
+
+#: src/pyams_content/features/footer/skin/__init__.py:49
+msgid "Hidden footer"
+msgstr "Ne pas afficher de pied de pages"
+
+#: src/pyams_content/features/footer/interfaces/__init__.py:39
+msgid "Footer template"
+msgstr "Mode de rendu"
+
+#: src/pyams_content/features/footer/interfaces/__init__.py:40
+msgid "Presentation template used for this footer"
+msgstr "Mode de rendu utilisé par ce pied de page"
+
 #: src/pyams_content/features/review/__init__.py:186
 #, python-format
 msgid "Request comment: {comment}"
@@ -4676,6 +4706,26 @@
 msgid "Thank you."
 msgstr "Merci."
 
+#: src/pyams_content/features/header/zmi/__init__.py:62
+msgid "Page header"
+msgstr "En-tête de pages"
+
+#: src/pyams_content/features/header/zmi/__init__.py:80
+msgid "Edit header settings"
+msgstr "Paramétrage des en-têtes de pages"
+
+#: src/pyams_content/features/header/zmi/__init__.py:178
+msgid "Header renderer settings"
+msgstr "Propriétés du mode de rendu"
+
+#: src/pyams_content/features/header/zmi/__init__.py:107
+msgid "Don't inherit parent header"
+msgstr "Ne pas hériter de l'en-tête de pages du parent"
+
+#: src/pyams_content/features/header/skin/__init__.py:49
+msgid "Hidden header"
+msgstr "Ne pas afficher d'en-tête de pages"
+
 #~ msgid "Paragraphs types..."
 #~ msgstr "Types de paragraphes"
 
--- a/src/pyams_content/locales/pyams_content.pot	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/locales/pyams_content.pot	Wed May 23 15:30:41 2018 +0200
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-04-26 16:39+0200\n"
+"POT-Creation-Date: 2018-05-15 16:31+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -96,7 +96,7 @@
 msgid "Update media properties"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:246
+#: ./src/pyams_content/component/gallery/zmi/file.py:249
 msgid "Remove media..."
 msgstr ""
 
@@ -104,7 +104,7 @@
 msgid "Show/hide media"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:214
+#: ./src/pyams_content/component/gallery/zmi/file.py:216
 msgid "Audio content"
 msgstr ""
 
@@ -663,7 +663,7 @@
 "NOTICE: removing types from allowed types list will have no effect on already created contents!"
 msgstr ""
 
-#: ./src/pyams_content/component/paragraph/zmi/__init__.py:194
+#: ./src/pyams_content/component/paragraph/zmi/__init__.py:197
 msgid "Paragraph was correctly added."
 msgstr ""
 
@@ -675,15 +675,15 @@
 msgid "Add new video paragraph"
 msgstr ""
 
-#: ./src/pyams_content/component/paragraph/zmi/video.py:112
-#: ./src/pyams_content/component/video/zmi/paragraph.py:193
+#: ./src/pyams_content/component/paragraph/zmi/video.py:115
+#: ./src/pyams_content/component/video/zmi/paragraph.py:208
 msgid "Edit video properties"
 msgstr ""
 
-#: ./src/pyams_content/component/paragraph/zmi/video.py:84
-#: ./src/pyams_content/component/paragraph/zmi/video.py:131
-#: ./src/pyams_content/component/video/zmi/paragraph.py:87
-#: ./src/pyams_content/component/video/zmi/paragraph.py:216
+#: ./src/pyams_content/component/paragraph/zmi/video.py:86
+#: ./src/pyams_content/component/paragraph/zmi/video.py:136
+#: ./src/pyams_content/component/video/zmi/paragraph.py:102
+#: ./src/pyams_content/component/video/zmi/paragraph.py:236
 msgid "HTML content"
 msgstr ""
 
@@ -1135,10 +1135,12 @@
 msgstr ""
 
 #: ./src/pyams_content/component/paragraph/interfaces/header.py:43
+#: ./src/pyams_content/features/header/interfaces/__init__.py:39
 msgid "Header template"
 msgstr ""
 
 #: ./src/pyams_content/component/paragraph/interfaces/header.py:44
+#: ./src/pyams_content/features/header/interfaces/__init__.py:40
 msgid "Presentation template used for this header"
 msgstr ""
 
@@ -1174,13 +1176,13 @@
 msgid "Associations paragraph"
 msgstr ""
 
-#: ./src/pyams_content/component/association/container.py:95
-#: ./src/pyams_content/component/association/zmi/__init__.py:282
+#: ./src/pyams_content/component/association/container.py:88
+#: ./src/pyams_content/component/association/zmi/__init__.py:288
 msgid "Associations"
 msgstr ""
 
 #: ./src/pyams_content/component/association/zmi/paragraph.py:56
-#: ./src/pyams_content/component/association/zmi/__init__.py:94
+#: ./src/pyams_content/component/association/zmi/__init__.py:95
 msgid "Associations..."
 msgstr ""
 
@@ -1192,24 +1194,24 @@
 msgid "Edit association paragraph properties"
 msgstr ""
 
-#: ./src/pyams_content/component/association/zmi/__init__.py:187
+#: ./src/pyams_content/component/association/zmi/__init__.py:193
 msgid "Public title"
 msgstr ""
 
-#: ./src/pyams_content/component/association/zmi/__init__.py:204
+#: ./src/pyams_content/component/association/zmi/__init__.py:210
 msgid "Inner title"
 msgstr ""
 
-#: ./src/pyams_content/component/association/zmi/__init__.py:219
+#: ./src/pyams_content/component/association/zmi/__init__.py:225
 msgid "Size"
 msgstr ""
 
-#: ./src/pyams_content/component/association/zmi/__init__.py:259
-#: ./src/pyams_content/component/association/zmi/__init__.py:269
+#: ./src/pyams_content/component/association/zmi/__init__.py:265
+#: ./src/pyams_content/component/association/zmi/__init__.py:275
 msgid "Associations list"
 msgstr ""
 
-#: ./src/pyams_content/component/association/zmi/__init__.py:64
+#: ./src/pyams_content/component/association/zmi/__init__.py:65
 msgid "Association was correctly added."
 msgstr ""
 
@@ -1225,19 +1227,19 @@
 msgid "Presentation template used for associations"
 msgstr ""
 
-#: ./src/pyams_content/component/links/__init__.py:103
+#: ./src/pyams_content/component/links/__init__.py:104
 msgid "Internal link"
 msgstr ""
 
-#: ./src/pyams_content/component/links/__init__.py:178
+#: ./src/pyams_content/component/links/__init__.py:186
 msgid "External link"
 msgstr ""
 
-#: ./src/pyams_content/component/links/__init__.py:224
+#: ./src/pyams_content/component/links/__init__.py:239
 msgid "Mailto link"
 msgstr ""
 
-#: ./src/pyams_content/component/links/__init__.py:165
+#: ./src/pyams_content/component/links/__init__.py:173
 msgid "target is not published"
 msgstr ""
 
@@ -1351,11 +1353,11 @@
 msgid "Dailymotion settings"
 msgstr ""
 
-#: ./src/pyams_content/component/video/provider/__init__.py:66
+#: ./src/pyams_content/component/video/provider/__init__.py:70
 msgid "Other provider"
 msgstr ""
 
-#: ./src/pyams_content/component/video/provider/__init__.py:90
+#: ./src/pyams_content/component/video/provider/__init__.py:94
 msgid "Custom video settings"
 msgstr ""
 
@@ -1591,16 +1593,16 @@
 msgid "Add new external video..."
 msgstr ""
 
-#: ./src/pyams_content/component/video/zmi/paragraph.py:140
+#: ./src/pyams_content/component/video/zmi/paragraph.py:144
 msgid "Video provider is required"
 msgstr ""
 
-#: ./src/pyams_content/component/video/zmi/paragraph.py:163
-#: ./src/pyams_content/component/video/zmi/paragraph.py:238
+#: ./src/pyams_content/component/video/zmi/paragraph.py:193
+#: ./src/pyams_content/component/video/zmi/paragraph.py:257
 msgid "Video provider settings"
 msgstr ""
 
-#: ./src/pyams_content/component/video/zmi/paragraph.py:179
+#: ./src/pyams_content/component/video/zmi/paragraph.py:173
 msgid "Other settings"
 msgstr ""
 
@@ -2305,11 +2307,11 @@
 msgid "Edit manager restrictions for « {0} »"
 msgstr ""
 
-#: ./src/pyams_content/shared/common/zmi/security.py:216
+#: ./src/pyams_content/shared/common/zmi/security.py:220
 msgid "Apply contents restrictions"
 msgstr ""
 
-#: ./src/pyams_content/shared/common/zmi/security.py:218
+#: ./src/pyams_content/shared/common/zmi/security.py:222
 msgid ""
 "You can specify which contents this manager will be able to manage. If you "
 "specify several criteria, the manager will be able to manage contents for "
@@ -4099,12 +4101,12 @@
 msgid "List of selected pictograms which will be available to shared contents"
 msgstr ""
 
-#: ./src/pyams_content/features/renderer/zmi/__init__.py:105
+#: ./src/pyams_content/features/renderer/zmi/__init__.py:72
 #: ./src/pyams_content/features/renderer/zmi/templates/renderer-input.pt:4
 msgid "Edit renderer properties"
 msgstr ""
 
-#: ./src/pyams_content/features/renderer/zmi/__init__.py:134
+#: ./src/pyams_content/features/renderer/skin/__init__.py:67
 msgid "Hidden content"
 msgstr ""
 
@@ -4254,6 +4256,34 @@
 msgid "No currently defined alert."
 msgstr ""
 
+#: ./src/pyams_content/features/footer/zmi/__init__.py:56
+msgid "Page footer"
+msgstr ""
+
+#: ./src/pyams_content/features/footer/zmi/__init__.py:74
+msgid "Edit footer settings"
+msgstr ""
+
+#: ./src/pyams_content/features/footer/zmi/__init__.py:172
+msgid "Footer renderer settings"
+msgstr ""
+
+#: ./src/pyams_content/features/footer/zmi/__init__.py:101
+msgid "Don't inherit parent footer"
+msgstr ""
+
+#: ./src/pyams_content/features/footer/skin/__init__.py:49
+msgid "Hidden footer"
+msgstr ""
+
+#: ./src/pyams_content/features/footer/interfaces/__init__.py:39
+msgid "Footer template"
+msgstr ""
+
+#: ./src/pyams_content/features/footer/interfaces/__init__.py:40
+msgid "Presentation template used for this footer"
+msgstr ""
+
 #: ./src/pyams_content/features/review/__init__.py:186
 #, python-format
 msgid "Request comment: {comment}"
@@ -4435,3 +4465,23 @@
 #: ./src/pyams_content/features/review/zmi/templates/review-notification.pt:47
 msgid "Thank you."
 msgstr ""
+
+#: ./src/pyams_content/features/header/zmi/__init__.py:62
+msgid "Page header"
+msgstr ""
+
+#: ./src/pyams_content/features/header/zmi/__init__.py:80
+msgid "Edit header settings"
+msgstr ""
+
+#: ./src/pyams_content/features/header/zmi/__init__.py:178
+msgid "Header renderer settings"
+msgstr ""
+
+#: ./src/pyams_content/features/header/zmi/__init__.py:107
+msgid "Don't inherit parent header"
+msgstr ""
+
+#: ./src/pyams_content/features/header/skin/__init__.py:49
+msgid "Hidden header"
+msgstr ""
--- a/src/pyams_content/root/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/root/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -19,6 +19,8 @@
 
 # import interfaces
 from pyams_content.features.alert.interfaces import IAlertTarget
+from pyams_content.features.footer.interfaces import IFooterTarget
+from pyams_content.features.header.interfaces import IHeaderTarget
 from pyams_content.features.preview.interfaces import IPreviewTarget
 from pyams_content.interfaces import WEBMASTER_ROLE, OPERATOR_ROLE
 from pyams_content.root.interfaces import ISiteRootRoles, ISiteRootConfiguration, ISiteRoot, \
@@ -45,7 +47,7 @@
 
 
 @implementer(IDefaultProtectionPolicy, ISiteRoot, ISiteRootRoles, IPortalContext,
-             IAlertTarget, IPreviewTarget)
+             IHeaderTarget, IFooterTarget, IAlertTarget, IPreviewTarget)
 class SiteRoot(ProtectedObject, BaseSiteRoot, UserSkinnableContent):
     """Main site root"""
 
--- a/src/pyams_content/shared/blog/manager.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/blog/manager.py	Wed May 23 15:30:41 2018 +0200
@@ -19,6 +19,8 @@
 from pyams_content.component.illustration import IIllustrationTarget
 from pyams_content.component.paragraph.interfaces import IParagraphFactorySettings
 from pyams_content.component.theme.interfaces import IThemesManagerTarget
+from pyams_content.features.footer.interfaces import IFooterTarget
+from pyams_content.features.header.interfaces import IHeaderTarget
 from pyams_content.features.preview.interfaces import IPreviewTarget
 from pyams_content.reference.pictograms.interfaces import IPictogramManagerTarget
 from pyams_content.root.interfaces import ISiteRoot
@@ -53,7 +55,7 @@
 
 
 @implementer(IBlogManager, IParagraphFactorySettings, IThemesManagerTarget, IPictogramManagerTarget,
-             IIllustrationTarget, IPortalContext, IPreviewTarget, IAttributeAnnotatable)
+             IIllustrationTarget, IPortalContext, IHeaderTarget, IFooterTarget, IPreviewTarget, IAttributeAnnotatable)
 class BlogManager(Folder, BaseSharedTool, UserSkinnableContent):
     """Nlog manager class"""
 
--- a/src/pyams_content/shared/form/zmi/render.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/form/zmi/render.py	Wed May 23 15:30:41 2018 +0200
@@ -23,7 +23,7 @@
 from pyams_skin.layer import IPyAMSLayer
 
 # import packages
-from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_form.form import InnerAddForm
 from pyams_form.help import FormHelp
 from pyams_utils.adapter import adapter_config
--- a/src/pyams_content/shared/imagemap/zmi/paragraph.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/imagemap/zmi/paragraph.py	Wed May 23 15:30:41 2018 +0200
@@ -16,9 +16,7 @@
 # import standard library
 
 # import interfaces
-from pyams_content.component.association.interfaces import IAssociationInfo
-from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer, \
-    IParagraphRenderer
+from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer
 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common.interfaces import IWfSharedContent
@@ -38,7 +36,7 @@
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 from pyams_utils.traversing import get_parent
-from pyams_viewlet.viewlet import viewlet_config, BaseContentProvider, Viewlet
+from pyams_viewlet.viewlet import viewlet_config, Viewlet
 from pyams_zmi.form import AdminDialogAddForm
 from pyramid.view import view_config
 from z3c.form import field, button
@@ -143,25 +141,3 @@
 @template_config(template='templates/imagemap-preview.pt', layer=IPyAMSLayer)
 class ImagemapParagraphPreviewWidgetsSuffix(Viewlet):
     """Image map paragraph preview widgets suffix"""
-
-
-#
-# Image map paragraph renderer
-#
-
-@adapter_config(context=(IImageMapParagraph, IPyAMSLayer), provides=IParagraphRenderer)
-@template_config(template='templates/paragraph-render.pt', layer=IPyAMSLayer)
-class ImagemapParagraphRenderer(BaseContentProvider):
-    """Image map paragraph renderer"""
-
-    language = None
-
-    def update(self):
-        i18n = II18n(self.context)
-        if self.language:
-            setattr(self, 'title', i18n.get_attribute('title', self.language, request=self.request))
-        else:
-            setattr(self, 'title', i18n.query_attribute('title', request=self.request))
-
-    def get_item_info(self, item):
-        return IAssociationInfo(item, None)
--- a/src/pyams_content/shared/imagemap/zmi/render.py	Wed May 16 14:06:28 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-
-# import interfaces
-from pyams_content.component.association.interfaces import IAssociationInfo
-from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.shared.imagemap.interfaces import IWfImageMap
-from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.zmi import BaseContentRenderer
-from pyams_template.template import template_config
-from pyams_utils.adapter import adapter_config
-
-
-@adapter_config(name='imagemap-render', context=(IWfImageMap, IPyAMSLayer), provides=IContentRenderer)
-@template_config(template='templates/render.pt', layer=IPyAMSLayer)
-class ImagemapRenderer(BaseContentRenderer):
-    """Image map renderer"""
-
-    weight = 20
-
-    def get_item_info(self, item):
-        return IAssociationInfo(item, None)
--- a/src/pyams_content/shared/logo/zmi/paragraph.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/logo/zmi/paragraph.py	Wed May 23 15:30:41 2018 +0200
@@ -22,7 +22,7 @@
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common import IWfSharedContent
 from pyams_content.shared.logo.interfaces import ILogosParagraph, LOGOS_PARAGRAPH_TYPE
-from pyams_form.interfaces.form import IInnerForm, IEditFormButtons
+from pyams_form.interfaces.form import IInnerForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
@@ -30,7 +30,7 @@
 
 # import packages
 from pyams_content.component.paragraph.zmi import IParagraphContainerView, BaseParagraphAddMenu, \
-    BaseParagraphAJAXAddForm, BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm
+    BaseParagraphAJAXAddForm, BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_content.shared.logo.paragraph import LogosParagraph
@@ -122,7 +122,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IEditFormButtons)
+            return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
 
--- a/src/pyams_content/shared/site/manager.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/site/manager.py	Wed May 23 15:30:41 2018 +0200
@@ -19,6 +19,8 @@
 from pyams_content.component.illustration import IIllustrationTarget
 from pyams_content.component.paragraph.interfaces import IParagraphFactorySettings
 from pyams_content.component.theme.interfaces import IThemesManagerTarget
+from pyams_content.features.footer.interfaces import IFooterTarget
+from pyams_content.features.header.interfaces import IHeaderTarget
 from pyams_content.features.preview.interfaces import IPreviewTarget
 from pyams_content.interfaces import MANAGE_SITE_PERMISSION
 from pyams_content.reference.pictograms.interfaces import IPictogramManagerTarget
@@ -51,7 +53,7 @@
 
 
 @implementer(ISiteManager, IParagraphFactorySettings, IThemesManagerTarget, IPictogramManagerTarget,
-             IIllustrationTarget, IPortalContext, IPreviewTarget, IAttributeAnnotatable)
+             IIllustrationTarget, IPortalContext, IHeaderTarget, IFooterTarget, IPreviewTarget, IAttributeAnnotatable)
 class SiteManager(SiteContainerMixin, OrderedContainer, BaseSharedTool, UserSkinnableContent):
     """Site manager persistent class"""
 
--- a/src/pyams_content/shared/view/portlet/__init__.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/view/portlet/__init__.py	Wed May 23 15:30:41 2018 +0200
@@ -17,17 +17,13 @@
 
 # import interfaces
 from pyams_content.shared.view.portlet.interfaces import IViewItemsPortletSettings
-from pyams_portal.interfaces import IPortalContext, IPortletRenderer
-from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.interfaces import VIEW_PERMISSION
 
 # import packages
 from pyams_content.workflow import PUBLISHED_STATES
-from pyams_portal.portlet import PortletSettings, portlet_config, Portlet, PortletRenderer
+from pyams_portal.portlet import PortletSettings, portlet_config, Portlet
 from pyams_sequence.utility import get_sequence_target
-from pyams_template.template import template_config
-from pyams_utils.adapter import adapter_config
-from zope.interface import implementer, Interface
+from zope.interface import implementer
 from zope.schema.fieldproperty import FieldProperty
 
 from pyams_content import _
@@ -63,11 +59,3 @@
     toolbar_css_class = 'fa fa-fw fa-2x fa-th-list'
 
     settings_class = ViewItemsPortletSettings
-
-
-@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, IViewItemsPortletSettings), provides=IPortletRenderer)
-@template_config(template='templates/view-items-list.pt', layer=IPyAMSLayer)
-class ViewItemsPortletRenderer(PortletRenderer):
-    """View items portlet renderer"""
-
-    label = _("Simple list view")
--- a/src/pyams_content/shared/view/zmi/render.py	Wed May 16 14:06:28 2018 +0200
+++ b/src/pyams_content/shared/view/zmi/render.py	Wed May 23 15:30:41 2018 +0200
@@ -21,7 +21,7 @@
 from pyams_skin.layer import IPyAMSLayer
 
 # import packages
-from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config