Created base infrastructure for views
authorThierry Florac <thierry.florac@onf.fr>
Tue, 27 Jun 2017 11:49:01 +0200
changeset 92 3facc843c06f
parent 91 87e08c0f3e3c
child 93 89ac3216c5dd
Created base infrastructure for views
src/pyams_content/shared/view/__init__.py
src/pyams_content/shared/view/interfaces/__init__.py
src/pyams_content/shared/view/manager.py
src/pyams_content/shared/view/reference.py
src/pyams_content/shared/view/theme.py
src/pyams_content/shared/view/zmi/properties.py
src/pyams_content/shared/view/zmi/reference.py
src/pyams_content/shared/view/zmi/summary.py
src/pyams_content/shared/view/zmi/templates/summary.pt
src/pyams_content/shared/view/zmi/theme.py
--- a/src/pyams_content/shared/view/__init__.py	Tue Jun 27 11:47:34 2017 +0200
+++ b/src/pyams_content/shared/view/__init__.py	Tue Jun 27 11:49:01 2017 +0200
@@ -10,22 +10,38 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-
 __docformat__ = 'restructuredtext'
 
 
 # import standard library
+import logging
+logger = logging.getLogger("PyAMS (content)")
 
 # import interfaces
-from pyams_content.component.links.interfaces import ILinkContainerTarget
-from pyams_content.shared.view.interfaces import IWfView, IView, VIEW_CONTENT_TYPE, VIEW_CONTENT_NAME
+from hypatia.interfaces import ICatalog
+from pyams_content.shared.view.interfaces import IView, IWfView, IViewQuery, IViewQueryParamsExtension, \
+    IViewQueryFilterExtension, VIEW_CONTENT_TYPE, VIEW_CONTENT_NAME
+from zope.intid.interfaces import IIntIds
 
 # import packages
+from hypatia.catalog import CatalogQuery
+from hypatia.query import Any
+from pyams_cache.beaker import get_cache
+from pyams_catalog.query import CatalogResultSet, or_
 from pyams_content.shared.common import WfSharedContent, register_content_type, SharedContent
+from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.list import unique
+from pyams_utils.registry import get_utility
+from pyams_workflow.interfaces import IWorkflow
+from pyramid.threadlocal import get_current_registry
 from zope.interface import implementer
 from zope.schema.fieldproperty import FieldProperty
 
 
+VIEWS_CACHE_NAME = 'PyAMS::view'
+VIEWS_CACHE_KEY = 'view_{view}.context_{context}'
+
+
 @implementer(IWfView)
 class WfView(WfSharedContent):
     """Base view"""
@@ -35,7 +51,27 @@
 
     selected_content_types = FieldProperty(IWfView['selected_content_types'])
     order_by = FieldProperty(IWfView['order_by'])
-    reversed_order  =FieldProperty(IWfView['reversed_order'])
+    reversed_order = FieldProperty(IWfView['reversed_order'])
+
+    def get_results(self, context, limit=None):
+        intids = get_utility(IIntIds)
+        views_cache = get_cache('default', VIEWS_CACHE_NAME)
+        cache_key = VIEWS_CACHE_KEY.format(view=intids.queryId(self),
+                                           context=intids.queryId(context))
+        try:
+            results = views_cache.get_value(cache_key)
+        except KeyError:
+            registry = get_current_registry()
+            adapter = registry.queryAdapter(self, IViewQuery, name='es')
+            if adapter is None:
+                adapter = registry.getAdapter(self, IViewQuery)
+            results = adapter.get_results(context)
+            views_cache.set_value(cache_key, [intids.queryId(item) for item in results])
+            logger.debug("Storing view items to cache key {0}".format(cache_key))
+        else:
+            results = CatalogResultSet(results)
+            logger.debug("Retrieving view items from cache key {0}".format(cache_key))
+        return results
 
 register_content_type(WfView)
 
@@ -45,3 +81,38 @@
     """Workflow managed view class"""
 
     content_class = WfView
+
+
+@adapter_config(context=IWfView, provides=IViewQuery)
+class ViewQuery(ContextAdapter):
+    """View query"""
+
+    def get_es_params(self, context):
+        view = self.context
+        catalog = get_utility(ICatalog)
+        registry = get_current_registry()
+        params = Any(catalog['content_type'], view.selected_content_types)
+        wf_params = None
+        for workflow in registry.getAllUtilitiesRegisteredFor(IWorkflow):
+            wf_params = or_(wf_params, Any(catalog['workflow_state'], workflow.published_states))
+        params &= wf_params
+        for name, adapter in sorted(registry.getAdapters((view,), IViewQueryParamsExtension),
+                                    key=lambda x: x[1].weight):
+            new_params = adapter.get_params(context)
+            if new_params:
+                params &= new_params
+        return params
+
+    def get_results(self, context, limit=None):
+        view = self.context
+        catalog = get_utility(ICatalog)
+        registry = get_current_registry()
+        params = self.get_es_params(context)
+        items = CatalogResultSet(CatalogQuery(catalog).query(params,
+                                                             sort_index=view.order_by,
+                                                             reverse=view.reversed_order,
+                                                             limit=limit))
+        for name, adapter in sorted(registry.getAdapters((view,), IViewQueryFilterExtension),
+                                    key=lambda x: x[1].weight):
+            items = adapter.filter(context, items)
+        return unique(items)
--- a/src/pyams_content/shared/view/interfaces/__init__.py	Tue Jun 27 11:47:34 2017 +0200
+++ b/src/pyams_content/shared/view/interfaces/__init__.py	Tue Jun 27 11:49:01 2017 +0200
@@ -16,10 +16,12 @@
 # import standard library
 
 # import interfaces
+from pyams_content.component.links.interfaces import IInternalReferencesList
 from pyams_content.shared.common.interfaces import ISharedContent, IWfSharedContent, ISharedTool
 
 # import packages
 from pyams_sequence.schema import InternalReferencesList
+from pyams_thesaurus.schema import ThesaurusTermsListField
 from zope.interface import Interface, Attribute
 from zope.schema import List, Choice, Bool
 from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
@@ -31,10 +33,10 @@
 VIEW_CONTENT_NAME = _('View')
 
 
-CREATION_DATE_ORDER = 'creation_datetime'
-UPDATE_DATE_ORDER = 'update_datetime'
-PUBLICATION_DATE_ORDER = 'publication_datetime'
-FIRSTPUBLICATION_DATE_ORDER = 'first_publication_datetime'
+CREATION_DATE_ORDER = 'created_date'
+UPDATE_DATE_ORDER = 'modified_date'
+PUBLICATION_DATE_ORDER = 'publication_date'
+FIRSTPUBLICATION_DATE_ORDER = 'first_publication_date'
 
 VIEW_ORDER = {CREATION_DATE_ORDER: _("Creation date"),
               UPDATE_DATE_ORDER: _("Last update date"),
@@ -59,8 +61,8 @@
 
     order_by = Choice(title=_("Order by"),
                       description=_("Property to use to sort results"),
+                      vocabulary=VIEW_ORDER_VOCABULARY,
                       required=True,
-                      vocabulary=VIEW_ORDER_VOCABULARY,
                       default=FIRSTPUBLICATION_DATE_ORDER)
 
     reversed_order = Bool(title=_("Reversed order?"),
@@ -68,21 +70,43 @@
                           required=True,
                           default=True)
 
+    def get_results(self, context):
+        """Get results of catalog query"""
+
 
 class IView(ISharedContent):
     """Workflow managed view interface"""
 
 
-class IViewExtension(Interface):
-    """View query extension interface
+class IViewQuery(Interface):
+    """View query interface"""
+
+    def get_results(self, context):
+        """Get results of catalog query"""
+
 
-    This interface is used to add features to views from external packages
-    """
+class IViewQueryExtension(Interface):
+    """Base view query extension"""
+
+    weight = Attribute("Extension weight")
+
+
+class IViewQueryParamsExtension(IViewQueryExtension):
+    """View query extension interface"""
 
     def get_params(self, context):
         """Add params to catalog query"""
 
-    weight = Attribute("Extension weight")
+
+class IViewQueryEsParamsExtension(IViewQueryExtension):
+    """View query parameters extension for Elasticsearch"""
+
+    def get_es_params(self, context):
+        """Add params to Elasticsearch query"""
+
+
+class IViewQueryFilterExtension(IViewQueryExtension):
+    """View query filter extension"""
 
     def filter(self, context, items):
         """Filter items after catalog query"""
@@ -91,9 +115,42 @@
 VIEW_REFERENCES_SETTINGS_KEY = 'pyams_content.view.references'
 
 
-class IViewInternalReferencesSettings(Interface):
+ALWAYS_REFERENCE_MODE = 'always'
+IFEMPTY_REFERENCE_MODE = 'if_empty'
+
+REFERENCES_MODES = {ALWAYS_REFERENCE_MODE: _("Always include selected internal references"),
+                    IFEMPTY_REFERENCE_MODE: _("Include selected internal references only if empty")}
+
+REFERENCES_MODES_VOCABULARY = SimpleVocabulary([SimpleTerm(v, title=t)
+                                                for v, t in REFERENCES_MODES.items()])
+
+
+class IViewInternalReferencesSettings(IInternalReferencesList):
     """View internal references settings"""
 
-    references = InternalReferencesList(title=_("Internal references"),
-                                        description=_("List of internal references"),
-                                        required=False)
+    references_mode = Choice(title=_("Internal references usage"),
+                             description=_("Specify how selected references are included into view results"),
+                             vocabulary=REFERENCES_MODES_VOCABULARY,
+                             required=True,
+                             default=ALWAYS_REFERENCE_MODE)
+
+
+VIEW_THEMES_SETTINGS_KEY = 'pyams_content.view.themes'
+
+
+class IViewThemesSettings(Interface):
+    """View themess ettings"""
+
+    select_context_themes = Bool(title=_("Select context themes?"),
+                                 description=_("If 'yes', themes will be extracted from context"),
+                                 required=True,
+                                 default=False)
+
+    themes = ThesaurusTermsListField(title=_("Other terms"),
+                                     required=False)
+
+    def get_themes(self, context):
+        """Get all themes for given context"""
+
+    def get_themes_index(self, context):
+        """Get all themes index values for given context"""
--- a/src/pyams_content/shared/view/manager.py	Tue Jun 27 11:47:34 2017 +0200
+++ b/src/pyams_content/shared/view/manager.py	Tue Jun 27 11:49:01 2017 +0200
@@ -16,6 +16,7 @@
 # import standard library
 
 # import interfaces
+from pyams_content.component.theme.interfaces import IThemesManagerTarget
 from pyams_content.shared.view.interfaces import IViewsManager, VIEW_CONTENT_TYPE
 from zope.annotation.interfaces import IAttributeAnnotatable
 from zope.component.interfaces import ISite
@@ -29,7 +30,7 @@
 from zope.interface import implementer
 
 
-@implementer(IViewsManager, IAttributeAnnotatable)
+@implementer(IViewsManager, IThemesManagerTarget, IAttributeAnnotatable)
 class ViewsManager(SharedTool):
     """Views manager class"""
 
--- a/src/pyams_content/shared/view/reference.py	Tue Jun 27 11:47:34 2017 +0200
+++ b/src/pyams_content/shared/view/reference.py	Tue Jun 27 11:49:01 2017 +0200
@@ -17,12 +17,18 @@
 from persistent import Persistent
 
 # import interfaces
+from hypatia.interfaces import ICatalog
 from pyams_content.shared.view.interfaces import IViewInternalReferencesSettings, IWfView, VIEW_REFERENCES_SETTINGS_KEY, \
-    IViewExtension
+    IViewQueryFilterExtension, ALWAYS_REFERENCE_MODE
 from zope.annotation.interfaces import IAnnotations
 
 # import packages
+from hypatia.catalog import CatalogQuery
+from hypatia.query import Any
+from pyams_catalog.query import CatalogResultSet
+from pyams_content.workflow import VISIBLE_STATES
 from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.registry import get_utility
 from pyramid.threadlocal import get_current_registry
 from zope.container.contained import Contained
 from zope.interface import implementer
@@ -36,6 +42,7 @@
     """View internal references settings"""
 
     references = FieldProperty(IViewInternalReferencesSettings['references'])
+    references_mode = FieldProperty(IViewInternalReferencesSettings['references_mode'])
 
 
 @adapter_config(context=IWfView, provides=IViewInternalReferencesSettings)
@@ -50,14 +57,18 @@
     return settings
 
 
-@adapter_config(name='references', context=IWfView, provides=IViewExtension)
-class ViewInternalReferencesExtension(ContextAdapter):
-    """View internal references extension"""
-
-    def get_param(self, context):
-        pass
+@adapter_config(name='references', context=IWfView, provides=IViewQueryFilterExtension)
+class ViewInternalReferencesQueryFilterExtension(ContextAdapter):
+    """View internal references filter extension"""
 
     weight = 999
 
     def filter(self, context, items):
+        settings = IViewInternalReferencesSettings(self.context)
+        if not settings.references:
+            return items
+        if (not items) or (settings.references_mode == ALWAYS_REFERENCE_MODE):
+            catalog = get_utility(ICatalog)
+            params = Any(catalog['oid'], settings.references) & Any(catalog['workflow_state'], VISIBLE_STATES)
+            items.prepend(CatalogResultSet(CatalogQuery(catalog).query(params)))
         return items
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/view/theme.py	Tue Jun 27 11:49:01 2017 +0200
@@ -0,0 +1,64 @@
+#
+# 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.theme.interfaces import IThemesInfo
+from pyams_content.shared.view.interfaces import IViewThemesSettings, IWfView, VIEW_THEMES_SETTINGS_KEY
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from persistent import Persistent
+from pyams_utils.adapter import adapter_config
+from pyramid.threadlocal import get_current_registry
+from zope.container.contained import Contained
+from zope.interface import implementer
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate
+from zope.schema.fieldproperty import FieldProperty
+
+
+@implementer(IViewThemesSettings)
+class ViewThemesSettings(Persistent, Contained):
+    """View themes settings"""
+
+    select_context_themes = FieldProperty(IViewThemesSettings['select_context_themes'])
+    themes = FieldProperty(IViewThemesSettings['themes'])
+
+    def get_themes(self, context):
+        themes = set()
+        if self.select_context_themes:
+            themes_info = IThemesInfo(context, None)
+            if themes_info is not None:
+                themes |= set(themes_info.themes or ())
+        if self.themes:
+            themes |= set(self.themes)
+        return themes
+
+    def get_themes_index(self, context):
+        return [theme.label for theme in self.get_themes(context)]
+
+
+@adapter_config(context=IWfView, provides=IViewThemesSettings)
+def ViewThemesSettingsFactory(view):
+    """View themes settings factory"""
+    annotations = IAnnotations(view)
+    settings = annotations.get(VIEW_THEMES_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[VIEW_THEMES_SETTINGS_KEY] = ViewThemesSettings()
+        get_current_registry().notify(ObjectCreatedEvent(settings))
+        locate(settings, view, '++view:themes++')
+    return settings
--- a/src/pyams_content/shared/view/zmi/properties.py	Tue Jun 27 11:47:34 2017 +0200
+++ b/src/pyams_content/shared/view/zmi/properties.py	Tue Jun 27 11:49:01 2017 +0200
@@ -39,3 +39,4 @@
     fieldset_class = 'bordered no-x-margin margin-y-10'
 
     fields = field.Fields(IWfView).select('selected_content_types', 'order_by', 'reversed_order')
+    weight = 1
--- a/src/pyams_content/shared/view/zmi/reference.py	Tue Jun 27 11:47:34 2017 +0200
+++ b/src/pyams_content/shared/view/zmi/reference.py	Tue Jun 27 11:49:01 2017 +0200
@@ -18,7 +18,7 @@
 # import interfaces
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.view.interfaces import IWfView, IViewInternalReferencesSettings
-from pyams_form.interfaces.form import IWidgetForm
+from pyams_form.interfaces.form import IWidgetForm, IUncheckedEditFormButtons
 from pyams_skin.interfaces import IInnerPage
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
@@ -42,31 +42,24 @@
 
 @viewlet_config(name='references.divider', context=IWfView, layer=IAdminLayer,
                 manager=IPropertiesMenu, permission=VIEW_SYSTEM_PERMISSION, weight=289)
-class ReferencesMenuDivider(MenuDivider):
-    """References menu divider"""
+class ViewReferencesMenuDivider(MenuDivider):
+    """View references menu divider"""
 
 
 @viewlet_config(name='references.menu', context=IWfView, layer=IAdminLayer,
                 manager=IPropertiesMenu, permission=VIEW_SYSTEM_PERMISSION, weight=290)
-class ReferencesMenu(MenuItem):
-    """References menu"""
+class ViewReferencesMenu(MenuItem):
+    """View references menu"""
 
     label = _("References...")
     icon_class = 'fa-link'
     url = '#references.html'
 
 
-class IReferencesEditButtons(Interface):
-    """References settings form buttons"""
-
-    reset = ResetButton(name='reset', title=_("Reset"))
-    submit = button.Button(name='submit', title=_("Submit"))
-
-
 @pagelet_config(name='references.html', context=IWfView, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
-class ReferencesEditForm(AdminEditForm):
-    """References settings edit form"""
+class ViewReferencesEditForm(AdminEditForm):
+    """View references settings edit form"""
 
     legend = _("View internal references settings")
 
@@ -75,7 +68,7 @@
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
-            return button.Buttons(IReferencesEditButtons)
+            return button.Buttons(IUncheckedEditFormButtons)
         else:
             return button.Buttons(Interface)
 
@@ -84,5 +77,5 @@
 
 @view_config(name='references.json', context=IWfView, request_type=IPyAMSLayer,
              permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ReferencesAJAXEditForm(AJAXEditForm, ReferencesEditForm):
+class ViewReferencesAJAXEditForm(AJAXEditForm, ViewReferencesEditForm):
     """References settings edit form, JSON renderer"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/view/zmi/summary.py	Tue Jun 27 11:49:01 2017 +0200
@@ -0,0 +1,61 @@
+#
+# 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.shared.common.interfaces.zmi import IInnerSummaryView
+from pyams_content.shared.view.interfaces import IWfView
+from pyams_form.interfaces.form import IInnerTabForm
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+
+# import packages
+from pyams_content.shared.common.zmi.summary import SharedContentSummaryForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from pyams_zmi.form import InnerAdminDisplayForm
+from z3c.form import field
+from zope.interface import implementer, Interface
+
+from pyams_content import _
+
+
+@adapter_config(name='view-summary',
+                context=(IWfView, IPyAMSLayer, SharedContentSummaryForm),
+                provides=IInnerTabForm)
+class SharedViewSummaryForm(InnerAdminDisplayForm):
+    """Shared view summary"""
+
+    weight = 20
+    tab_label = _("Quick preview")
+    tab_target = 'view-summary.html'
+
+    fields = field.Fields(Interface)
+
+
+@pagelet_config(name='view-summary.html', context=IWfView, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@template_config(template='templates/summary.pt', layer=IPyAMSLayer)
+@implementer(IInnerSummaryView)
+class SharedViewSummaryView(object):
+    """Shared view summary view"""
+
+    def __init__(self, context, request):
+        super(SharedViewSummaryView, self).__init__(context, request)
+
+    @property
+    def items(self):
+        return self.context.get_results(self.context)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/view/zmi/templates/summary.pt	Tue Jun 27 11:49:01 2017 +0200
@@ -0,0 +1,3 @@
+<tal:loop repeat="item view.items">
+	<span tal:content="i18n:item.title" /><br />
+</tal:loop>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/view/zmi/theme.py	Tue Jun 27 11:49:01 2017 +0200
@@ -0,0 +1,82 @@
+#
+# 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.interfaces import MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.view.interfaces import IWfView, IViewThemesSettings, IViewsManager
+from pyams_form.interfaces.form import IWidgetForm, IUncheckedEditFormButtons
+from pyams_skin.interfaces import IInnerPage
+from pyams_skin.layer import IPyAMSLayer
+from pyams_thesaurus.interfaces.thesaurus import IThesaurusContextManager
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_zmi.interfaces.menu import IPropertiesMenu
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import INPUT_MODE
+
+# import packages
+from pyams_form.form import AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_utils.registry import get_utility
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminEditForm
+from pyramid.view import view_config
+from z3c.form import field, button
+from zope.interface import implementer, Interface
+
+from pyams_content import _
+
+
+@viewlet_config(name='themes.menu', context=IWfView, layer=IAdminLayer,
+                manager=IPropertiesMenu, permission=VIEW_SYSTEM_PERMISSION, weight=350)
+class ViewThemesMenu(MenuItem):
+    """View themes menu"""
+
+    label = _("Themes...")
+    icon_class = 'fa-tags'
+    url = '#themes.html'
+
+
+@pagelet_config(name='themes.html', context=IWfView, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@implementer(IWidgetForm, IInnerPage)
+class ViewThemesEditForm(AdminEditForm):
+    """View themes settings edit form"""
+
+    legend = _("View themes settings")
+
+    fields = field.Fields(IViewThemesSettings)
+
+    @property
+    def buttons(self):
+        if self.mode == INPUT_MODE:
+            return button.Buttons(IUncheckedEditFormButtons)
+        else:
+            return button.Buttons(Interface)
+
+    ajax_handler = 'themes.json'
+
+    def updateWidgets(self, prefix=None):
+        super(ViewThemesEditForm, self).updateWidgets(prefix)
+        if 'themes' in self.widgets:
+            manager = get_utility(IViewsManager)
+            self.widgets['themes'].thesaurus_name = IThesaurusContextManager(manager).thesaurus_name
+
+
+@view_config(name='themes.json', context=IWfView, request_type=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+class ViewThemesAJAXEditForm(AJAXEditForm, ViewThemesEditForm):
+    """View themes settings edit form, JSON renderer"""