# HG changeset patch # User Thierry Florac # Date 1498556941 -7200 # Node ID 3facc843c06f72ed4b995754c0341c71af9b771a # Parent 87e08c0f3e3c1c6f4047f3792b97ea712ab31b4b Created base infrastructure for views diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/__init__.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) diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/interfaces/__init__.py --- 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""" diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/manager.py --- 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""" diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/reference.py --- 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 diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/theme.py --- /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 +# 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 diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/zmi/properties.py --- 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 diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/zmi/reference.py --- 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""" diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/zmi/summary.py --- /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 +# 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) diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/zmi/templates/summary.pt --- /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 @@ + +
+
diff -r 87e08c0f3e3c -r 3facc843c06f src/pyams_content/shared/view/zmi/theme.py --- /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 +# 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"""