Moved skin related features from PyAMS_content package
authorThierry Florac <tflorac@ulthar.net>
Wed, 07 Nov 2018 19:02:46 +0100
changeset 204 713edde7dbf1
parent 203 40bffafce365
child 205 1b2e282d3cf2
Moved skin related features from PyAMS_content package
src/pyams_default_theme/component/association/__init__.py
src/pyams_default_theme/component/gallery/__init__.py
src/pyams_default_theme/component/illustration/__init__.py
src/pyams_default_theme/component/keynumber/__init__.py
src/pyams_default_theme/component/paragraph/audio.py
src/pyams_default_theme/component/paragraph/contact.py
src/pyams_default_theme/component/paragraph/container.py
src/pyams_default_theme/component/paragraph/frame.py
src/pyams_default_theme/component/paragraph/html.py
src/pyams_default_theme/component/paragraph/keypoint.py
src/pyams_default_theme/component/paragraph/map.py
src/pyams_default_theme/component/paragraph/milestone.py
src/pyams_default_theme/component/paragraph/pictogram.py
src/pyams_default_theme/component/paragraph/verbatim.py
src/pyams_default_theme/component/paragraph/video.py
src/pyams_default_theme/component/video/__init__.py
src/pyams_default_theme/features/footer/__init__.py
src/pyams_default_theme/features/footer/skin/__init__.py
src/pyams_default_theme/features/header/__init__.py
src/pyams_default_theme/features/header/skin/__init__.py
src/pyams_default_theme/features/header/skin/interfaces.py
src/pyams_default_theme/features/renderer/__init__.py
src/pyams_default_theme/shared/form/__init__.py
src/pyams_default_theme/shared/imagemap/__init__.py
src/pyams_default_theme/shared/logo/__init__.py
--- a/src/pyams_default_theme/component/association/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/association/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -23,8 +23,8 @@
 from pyams_content.component.links.interfaces import IBaseLink, IInternalLink
 from pyams_content.component.paragraph.interfaces import IParagraphContainer, IParagraphContainerTarget
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_default_theme.component.association.interfaces import IAssociationParagraphRemoteContentRendererSettings
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
--- a/src/pyams_default_theme/component/gallery/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/gallery/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,15 +12,15 @@
 
 __docformat__ = 'restructuredtext'
 
-
 from pyams_content.component.gallery.interfaces import IBaseGallery
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
+from pyams_default_theme import _
+
 
 class BaseGalleryRenderer(BaseContentRenderer):
     """Base gallery renderer"""
--- a/src/pyams_default_theme/component/illustration/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/illustration/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -9,8 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_content.shared.common import ISharedContent
-
 
 __docformat__ = 'restructuredtext'
 
@@ -23,12 +21,13 @@
 from pyams_content.component.illustration.interfaces import IIllustration
 from pyams_content.component.links import IInternalLink
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_content.interfaces import IBaseContent
 from pyams_content.root import ISiteRoot
+from pyams_content.shared.common import ISharedContent
 from pyams_content.shared.site.interfaces import IContentLink
 from pyams_default_theme.component.illustration.interfaces import IIllustrationRenderer, IIllustrationWithZoomSettings, \
     ILLUSTRATION_AFTER_BODY, ILLUSTRATION_BEFORE_BODY
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_default_theme.interfaces import IContentBannerIllustration, IContentHeaderIllustration, \
     IContentNavigationIllustration
 from pyams_skin.layer import IPyAMSLayer
--- a/src/pyams_default_theme/component/keynumber/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/keynumber/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,15 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-# import standard library
-
-# import interfaces
 from pyams_content.component.keynumber.interfaces import IKeyNumberContainer, IKeyNumberParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/paragraph/audio.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/audio.py	Wed Nov 07 19:02:46 2018 +0100
@@ -13,15 +13,10 @@
 
 __docformat__ = 'restructuredtext'
 
-# import standard library
-
-# import interfaces
 from pyams_content.component.paragraph.interfaces.audio import IAudioParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/paragraph/contact.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/contact.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,23 +12,18 @@
 
 __docformat__ = 'restructuredtext'
 
+from persistent import Persistent
+from zope.interface import implementer
+from zope.location import Location
+from zope.schema.fieldproperty import FieldProperty
 
-# import standard library
-from persistent import Persistent
-
-# import interfaces
-from pyams_content.component.paragraph.interfaces.contact import have_gis, IContactParagraph
+from pyams_content.component.paragraph.interfaces.contact import IContactParagraph, have_gis
 from pyams_content.features.renderer.interfaces import IContentRenderer
 from pyams_default_theme.component.paragraph.interfaces.contact import IContactParagraphDefaultRendererSettings
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
-from zope.interface import implementer
-from zope.location import locate, Location
-from zope.schema.fieldproperty import FieldProperty
 
 from pyams_default_theme import _
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/container.py	Wed Nov 07 19:02:46 2018 +0100
@@ -0,0 +1,50 @@
+#
+# 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'
+
+from pyams_content.component.paragraph import IParagraphContainer, IParagraphContainerTarget
+from pyams_content.component.paragraph.interfaces import IParagraphRenderer
+from pyams_content.features.renderer.interfaces import ISharedContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.adapter import adapter_config
+
+
+@adapter_config(name='paragraphs-render', context=(IParagraphContainerTarget, IPyAMSLayer),
+                provides=ISharedContentRenderer)
+class ParagraphsContainerRenderer(BaseContentRenderer):
+    """Paragraphs container renderer"""
+
+    weight = 10
+
+    def __init__(self, context, request):
+        super(ParagraphsContainerRenderer, self).__init__(context, request)
+        paragraphs = [para for para in IParagraphContainer(self.context).values()
+                      if para.visible]
+        registry = self.request.registry
+        self.renderers = [registry.queryMultiAdapter((paragraph, self.request), IParagraphRenderer)
+                          for paragraph in paragraphs]
+
+    def update(self):
+        super(ParagraphsContainerRenderer, self).update()
+        for renderer in self.renderers:
+            if renderer is not None:
+                renderer.language = self.language
+                renderer.update()
+
+    def render(self):
+        result = ''
+        for renderer in self.renderers:
+            if renderer is not None:
+                result += renderer.render()
+        return result
--- a/src/pyams_default_theme/component/paragraph/frame.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/frame.py	Wed Nov 07 19:02:46 2018 +0100
@@ -21,12 +21,11 @@
 from pyams_content.component.illustration.interfaces import IIllustration
 from pyams_content.component.paragraph.interfaces.frame import IFrameParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_content.reference.pictograms.interfaces import IPictogramTable
 from pyams_default_theme.component.paragraph.interfaces.frame import IDefaultFrameParagraphRendererSettings, \
     IFrameParagraphRendererSettings, ILateralFrameParagraphRendererSettings, ILeftFrameParagraphRendererSettings, \
     IRightFrameParagraphRendererSettings
-from pyams_i18n.interfaces import II18n
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
--- a/src/pyams_default_theme/component/paragraph/html.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/html.py	Wed Nov 07 19:02:46 2018 +0100
@@ -15,7 +15,7 @@
 from pyams_content.component.illustration import IIllustration
 from pyams_content.component.paragraph.interfaces.html import IHTMLParagraph, IRawParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
--- a/src/pyams_default_theme/component/paragraph/keypoint.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/keypoint.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,16 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.paragraph.interfaces.keypoint import IKeypointsParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/paragraph/map.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/map.py	Wed Nov 07 19:02:46 2018 +0100
@@ -16,24 +16,20 @@
 from pyams_content.component.paragraph.interfaces.map import have_gis
 if have_gis:
 
-    # import standard library
+    from zope.interface import implementer
+    from zope.schema.fieldproperty import FieldProperty
 
-    # import interfaces
     from pyams_content.component.paragraph.interfaces.map import IMapParagraph
     from pyams_content.features.renderer.interfaces import IContentRenderer
     from pyams_default_theme.component.paragraph.interfaces.map import IMapParagraphDefaultRendererSettings
     from pyams_gis.interfaces.configuration import IMapConfiguration
     from pyams_gis.interfaces.utility import IMapManager
     from pyams_skin.layer import IPyAMSLayer
-
-    # import packages
-    from pyams_content.features.renderer.skin import BaseContentRenderer
+    from pyams_default_theme.features.renderer import BaseContentRenderer
     from pyams_gis.configuration import MapConfiguration
     from pyams_template.template import template_config
     from pyams_utils.adapter import adapter_config, get_annotation_adapter
     from pyams_utils.registry import get_utility
-    from zope.interface import implementer
-    from zope.schema.fieldproperty import FieldProperty
 
     from pyams_default_theme import _
 
--- a/src/pyams_default_theme/component/paragraph/milestone.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/milestone.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,19 +12,15 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.paragraph.interfaces.milestone import IMilestoneParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
+from pyams_default_theme import _
+
 
 #
 # Milestone paragraph default renderer
--- a/src/pyams_default_theme/component/paragraph/pictogram.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/pictogram.py	Wed Nov 07 19:02:46 2018 +0100
@@ -14,7 +14,7 @@
 
 from pyams_content.component.paragraph.interfaces.pictogram import IPictogramContainer, IPictogramParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
--- a/src/pyams_default_theme/component/paragraph/verbatim.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/verbatim.py	Wed Nov 07 19:02:46 2018 +0100
@@ -20,8 +20,8 @@
 from pyams_content.component.illustration.interfaces import IIllustration
 from pyams_content.component.paragraph.interfaces.verbatim import IVerbatimParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_default_theme.component.paragraph.interfaces.verbatim import ILateralVerbatimParagraphRendererSettings
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_i18n.interfaces import II18n
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
--- a/src/pyams_default_theme/component/paragraph/video.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/paragraph/video.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,16 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.paragraph.interfaces.video import IVideoParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/video/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/component/video/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,16 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.video.interfaces import IExternalVideoParagraph, IExternalVideoRenderer
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 from pyams_utils.registry import get_current_registry
--- a/src/pyams_default_theme/features/footer/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/features/footer/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,27 +12,22 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
-from pyams_content.component.association.interfaces import ASSOCIATION_CONTAINER_KEY
-from pyams_content.features.footer.interfaces import IFooterTarget, IFooterSettings
-from pyams_content.features.menu.interfaces import IMenuLinksContainerTarget, IMenuLinksContainer
-from pyams_default_theme.features.footer.interfaces import ISimpleFooterRendererSettings, ISimpleFooterLinksMenu
-from pyams_default_theme.layer import IPyAMSDefaultLayer
-from zope.location.interfaces import ISublocations
-
-# import packages
 from persistent import Persistent
-from pyams_content.features.menu import Menu
-from pyams_utils.adapter import get_annotation_adapter, adapter_config, ContextAdapter
-from pyams_utils.traversing import get_parent
-from pyams_viewlet.viewlet import contentprovider_config, ViewContentProvider
 from zope.interface import implementer
 from zope.location import Location
+from zope.location.interfaces import ISublocations
 from zope.schema.fieldproperty import FieldProperty
 
+from pyams_content.component.association.interfaces import ASSOCIATION_CONTAINER_KEY
+from pyams_content.features.footer.interfaces import IFooterSettings, IFooterTarget
+from pyams_content.features.menu import Menu
+from pyams_content.features.menu.interfaces import IMenuLinksContainer, IMenuLinksContainerTarget
+from pyams_default_theme.features.footer.interfaces import ISimpleFooterLinksMenu, ISimpleFooterRendererSettings
+from pyams_default_theme.layer import IPyAMSDefaultLayer
+from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
+from pyams_utils.traversing import get_parent
+from pyams_viewlet.viewlet import ViewContentProvider, contentprovider_config
+
 
 @contentprovider_config(name='pyams.footer', layer=IPyAMSDefaultLayer)
 class FooterContentProvider(ViewContentProvider):
--- a/src/pyams_default_theme/features/footer/skin/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/features/footer/skin/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,18 +12,25 @@
 
 __docformat__ = 'restructuredtext'
 
+import logging
+logger = logging.getLogger('PyAMS (content)')
 
-# import standard library
+from pyramid.decorator import reify
+from zope.interface import implementer
 
-# import interfaces
+from pyams_cache.beaker import get_cache
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_portal.interfaces import PREVIEW_MODE
+from pyams_portal.portlet import PORTLETS_CACHE_KEY, PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME
+from pyams_utils.interfaces import ICacheKeyValue
+from pyams_utils.interfaces.url import DISPLAY_CONTEXT
+from pyams_utils.traversing import get_parent
 from pyams_content.component.association.interfaces import IAssociationInfo
 from pyams_content.features.footer.interfaces import IFooterTarget, IFooterRenderer, IFooterSettings, \
     IFooterRendererSettings
 from pyams_default_theme.features.footer.interfaces import ISimpleFooterRendererSettings
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.footer.skin import BaseFooterRenderer
 from pyams_default_theme.features.footer import SimpleFooterRendererSettings
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
@@ -31,13 +38,83 @@
 from pyams_default_theme import _
 
 
-SIMPLE_FOOTER_RENDERER_NAME = 'PyAMS simple footer'
+#
+# Simple footer renderer
+#
+
+@implementer(IFooterRenderer)
+class BaseFooterRenderer(BaseContentRenderer):
+    """Base footer renderer"""
+
+    name = None
+    settings_key = None
+
+    @reify
+    def settings_target(self):
+        context = self.request.annotations.get(DISPLAY_CONTEXT)
+        if context is None:
+            context = self.context
+        return get_parent(context, IFooterTarget)
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        target = self.settings_target
+        settings = IFooterSettings(target)
+        while settings.inherit:
+            settings = IFooterSettings(settings.parent)
+        return settings.settings
+
+    @reify
+    def cache_key(self):
+        return PORTLETS_CACHE_KEY.format(portlet=self.name,
+                                         hostname=self.request.host,
+                                         context=ICacheKeyValue(self.settings_target),
+                                         lang=self.request.locale_name)
+
+    def render(self):
+        preview_mode = self.request.annotations.get(PREVIEW_MODE, False)
+        if preview_mode:
+            return super(BaseFooterRenderer, self).render()
+        else:
+            portlets_cache = get_cache(PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME)
+            cache_key = self.cache_key
+            if self.context is not self.request.context:  # display shared content
+                cache_key = '{0}::shared'.format(cache_key)
+            try:
+                result = portlets_cache.get_value(cache_key)
+                logger.debug("Retrieving footer content from cache key {0}".format(cache_key))
+            except KeyError:
+                result = super(BaseFooterRenderer, self).render()
+                portlets_cache.set_value(cache_key, result)
+                logger.debug("Storing footer content to cache key {0}".format(cache_key))
+            return result
+
+
+#
+# Hidden footer renderer
+#
+
+@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 ''
 
 
 #
 # Simple footer renderer
 #
 
+SIMPLE_FOOTER_RENDERER_NAME = 'PyAMS simple footer'
+
+
 @adapter_config(name=SIMPLE_FOOTER_RENDERER_NAME, context=(IFooterTarget, IPyAMSLayer),
                 provides=IFooterRenderer)
 @adapter_config(name=SIMPLE_FOOTER_RENDERER_NAME, context=(IFooterSettings, IPyAMSLayer),
--- a/src/pyams_default_theme/features/header/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/features/header/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,26 +12,21 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
-from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderSettings
-from pyams_content.features.menu.interfaces import IMenuLinksContainerTarget, IMenuLinksContainer
-from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings, ISimpleHeaderTabsMenu
-from pyams_default_theme.layer import IPyAMSDefaultLayer
+from persistent import Persistent
+from zope.interface import implementer
+from zope.location import Location
 from zope.location.interfaces import ISublocations
 
-# import packages
-from persistent import Persistent
 from pyams_content.component.association.interfaces import ASSOCIATION_CONTAINER_KEY
+from pyams_content.features.header.interfaces import IHeaderSettings, IHeaderTarget
 from pyams_content.features.menu import Menu
+from pyams_content.features.menu.interfaces import IMenuLinksContainer, IMenuLinksContainerTarget
+from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings, ISimpleHeaderTabsMenu
+from pyams_default_theme.layer import IPyAMSDefaultLayer
 from pyams_file.property import FileProperty
-from pyams_utils.adapter import get_annotation_adapter, adapter_config, ContextAdapter
+from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.traversing import get_parent
-from pyams_viewlet.viewlet import contentprovider_config, ViewContentProvider
-from zope.interface import implementer
-from zope.location import Location
+from pyams_viewlet.viewlet import ViewContentProvider, contentprovider_config
 
 
 @contentprovider_config(name='pyams.header', layer=IPyAMSDefaultLayer)
--- a/src/pyams_default_theme/features/header/skin/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/features/header/skin/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,34 +12,116 @@
 
 __docformat__ = 'restructuredtext'
 
+import logging
+logger = logging.getLogger('PyAMS (content)')
 
-# import standard library
+from pyramid.decorator import reify
+from pyramid.location import lineage
+from zope.interface import implementer
 
-# import interfaces
+from pyams_cache.beaker import get_cache
 from pyams_content.component.association.interfaces import IAssociationInfo
 from pyams_content.component.links.interfaces import IInternalLink
-from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderRenderer, IHeaderSettings, \
-    IHeaderRendererSettings
+from pyams_content.features.header.interfaces import IHeaderRenderer, IHeaderRendererSettings, IHeaderSettings, \
+    IHeaderTarget
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_default_theme.features.header import SimpleHeaderRendererSettings
 from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings
+from pyams_default_theme.features.header.skin.interfaces import IHeaderClass
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_portal.interfaces import PREVIEW_MODE
+from pyams_portal.portlet import PORTLETS_CACHE_KEY, PORTLETS_CACHE_NAME, PORTLETS_CACHE_REGION
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.header.skin import BaseHeaderRenderer
-from pyams_default_theme.features.header import SimpleHeaderRendererSettings
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
-from pyramid.location import lineage
+from pyams_utils.interfaces import ICacheKeyValue
+from pyams_utils.traversing import get_parent
 
 from pyams_default_theme import _
 
 
-SIMPLE_HEADER_RENDERER_NAME = 'PyAMS simple header'
+#
+# Base header renderer
+#
+
+@implementer(IHeaderRenderer)
+class BaseHeaderRenderer(BaseContentRenderer):
+    """Base header renderer"""
+
+    name = None
+    settings_key = None
+
+    @reify
+    def settings_target(self):
+        context = self.request.display_context
+        if context is None:
+            context = self.context
+        return get_parent(context, IHeaderTarget)
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        target = self.settings_target
+        settings = IHeaderSettings(target)
+        while settings.inherit:
+            settings = IHeaderSettings(settings.parent)
+        return settings.settings
+
+    @reify
+    def cache_key(self):
+        return PORTLETS_CACHE_KEY.format(portlet=self.name,
+                                         hostname=self.request.host,
+                                         context=ICacheKeyValue(self.settings_target),
+                                         lang=self.request.locale_name)
+
+    @property
+    def main_header_class(self):
+        request = self.request
+        return request.registry.queryMultiAdapter((request.context, request), IHeaderClass, default='')
+
+    def render(self):
+        preview_mode = self.request.annotations.get(PREVIEW_MODE, False)
+        if preview_mode:
+            return super(BaseHeaderRenderer, self).render()
+        else:
+            portlets_cache = get_cache(PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME)
+            cache_key = self.cache_key
+            if self.context is not self.request.context:  # display shared content
+                cache_key = '{0}::shared'.format(cache_key)
+            try:
+                result = portlets_cache.get_value(cache_key)
+                logger.debug("Retrieving header content from cache key {0}".format(cache_key))
+            except KeyError:
+                result = super(BaseHeaderRenderer, self).render()
+                portlets_cache.set_value(cache_key, result)
+                logger.debug("Storing header content to cache key {0}".format(cache_key))
+            return result
+
+
+#
+# Hidden header renderer
+#
+
+@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 ''
 
 
 #
 # Simple header renderer
 #
 
+SIMPLE_HEADER_RENDERER_NAME = 'PyAMS simple header'
+
+
 @adapter_config(name=SIMPLE_HEADER_RENDERER_NAME, context=(IHeaderTarget, IPyAMSLayer),
                 provides=IHeaderRenderer)
 @adapter_config(name=SIMPLE_HEADER_RENDERER_NAME, context=(IHeaderSettings, IPyAMSLayer),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/header/skin/interfaces.py	Wed Nov 07 19:02:46 2018 +0100
@@ -0,0 +1,19 @@
+#
+# 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'
+
+from zope.interface import Interface
+
+
+class IHeaderClass(Interface):
+    """Custom header CSS class"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/renderer/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -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'
+
+from pyramid.decorator import reify
+from zope.interface import implementer
+
+from pyams_content.features.renderer import IContentRenderer, IRenderedContent, IRendererSettings
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from pyams_template.template import get_view_template
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+
+from pyams_content import _
+
+
+@implementer(IContentRenderer)
+class BaseContentRenderer(ContextRequestAdapter):
+    """Base content renderer"""
+
+    label = None
+    weight = 0
+
+    settings_interface = None
+    resources = ()
+
+    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 resource in self.resources:
+            resource.need()
+        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.query_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
+
+    @staticmethod
+    def render():
+        return ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/form/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -0,0 +1,89 @@
+#
+# 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'
+
+from z3c.form import button, field
+from zope.interface import Interface
+
+from pyams_content.features.renderer.interfaces import ISharedContentRenderer
+from pyams_content.shared.form import IFormFieldContainer, IFormFieldContainerTarget
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_form.form import InnerAddForm
+from pyams_form.help import FormHelp
+from pyams_form.interfaces.form import IFormHelp
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.adapter import adapter_config
+
+
+class FormFieldContainerDisplayForm(InnerAddForm):
+    """Form fields container display form"""
+
+    @property
+    def legend(self):
+        return II18n(self.context).query_attribute('user_title', request=self.request)
+
+    @property
+    def fields(self):
+        fields = field.Fields(*IFormFieldContainer(self.context).get_fields())
+        if self.context.use_captcha:
+            # TODO: add captcha
+            # fields += field.Fields(Captcha(title='', description='', required=True))
+            pass
+        return fields
+
+    buttons = button.Buttons(Interface)
+
+    def updateWidgets(self, prefix=None):
+        super(FormFieldContainerDisplayForm, self).updateWidgets(prefix)
+        for widget in self.widgets.values():
+            field = IFormFieldContainer(self.context).get(widget.field.__name__)
+            if field is not None:
+                widget.placeholder = field.placeholder
+
+
+@adapter_config(context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldContainerDisplayForm),
+                provides=IFormHelp)
+class FormFieldContainerDisplayFormHelp(FormHelp):
+    """Form field container display form help adapter"""
+
+    def __new__(cls, context, request, view):
+        if not context.header:
+            return None
+        return FormHelp.__new__(cls)
+
+    @property
+    def message(self):
+        return II18n(self.context).query_attribute('header', request=self.request)
+
+    message_format = 'text'
+
+
+@adapter_config(name='form-render', context=(IFormFieldContainerTarget, IPyAMSLayer),
+                provides=ISharedContentRenderer)
+class FormFieldContainerRenderer(BaseContentRenderer):
+    """Form field container renderer"""
+
+    weight = 20
+    display_form = None
+
+    def __init__(self, context, request):
+        super(FormFieldContainerRenderer, self).__init__(context, request)
+        self.display_form = FormFieldContainerDisplayForm(context, self.request)
+
+    def update(self):
+        super(FormFieldContainerRenderer, self).update()
+        self.display_form.update()
+
+    def render(self):
+        return self.display_form.render()
--- a/src/pyams_default_theme/shared/imagemap/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/shared/imagemap/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,22 +12,16 @@
 
 __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, IImageMapParagraph
-from pyams_skin.layer import IPyAMSLayer, IPyAMSUserLayer
-from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_content.shared.imagemap.interfaces import IImageMapParagraph, IWfImageMap
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_default_theme.page import BasePreviewPage
 from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.layer import IPyAMSLayer, IPyAMSUserLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 
 from pyams_default_theme import _
 
@@ -57,7 +51,7 @@
 
     label = _("Default imagemap renderer")
 
-    i18n_context_attrs = ('title', )
+    i18n_context_attrs = ('title',)
 
     def get_item_info(self, item):
         return IAssociationInfo(item, None)
--- a/src/pyams_default_theme/shared/logo/__init__.py	Wed Nov 07 18:25:36 2018 +0100
+++ b/src/pyams_default_theme/shared/logo/__init__.py	Wed Nov 07 19:02:46 2018 +0100
@@ -12,15 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-# import standard library
-
-# import interfaces
 from pyams_content.features.renderer.interfaces import IContentRenderer
 from pyams_content.shared.logo.interfaces import ILogosParagraph
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config