--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/common/zmi/types.py Fri Nov 10 12:07:43 2017 +0100
@@ -0,0 +1,577 @@
+#
+# 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 json
+
+# import interfaces
+from pyams_content.interfaces import MANAGE_TOOL_PERMISSION
+from pyams_content.shared.common.interfaces.types import ITypedSharedTool, ITypedDataManager, \
+ IBaseDataType, IDataType, ISubType
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces.container import ITableElementName
+from pyams_skin.interfaces.viewlet import IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_viewlet.interfaces import IViewletManager
+from pyams_zmi.interfaces.menu import IPropertiesMenu
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import DISPLAY_MODE, IDataExtractedEvent
+from z3c.table.interfaces import IValues, IColumn
+
+# import packages
+from pyams_content.shared.common.types import DataType, SubType
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.security import ProtectedFormObjectMixin
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.table import BaseTable, SorterColumn, TrashColumn, NameColumn, ActionColumn
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_skin.viewlet.toolbar import ToolbarAction
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextRequestAdapter
+from pyams_utils.traversing import get_parent
+from pyams_utils.unicode import translate_string
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.view import ContainerAdminView
+from pyramid.decorator import reify
+from pyramid.events import subscriber
+from pyramid.exceptions import NotFound
+from pyramid.view import view_config
+from z3c.form import field
+from zope.interface import Invalid
+
+from pyams_content import _
+
+
+@viewlet_config(name='data-types.menu', context=ITypedSharedTool, layer=IAdminLayer,
+ manager=IPropertiesMenu, permission=MANAGE_TOOL_PERMISSION, weight=20)
+class TypedSharedToolTypesMenu(MenuItem):
+ """Typed shared tool types menu"""
+
+ label = _("Data types")
+ icon_class = 'fa-folder-o'
+ url = '#data-types.html'
+
+
+#
+# Typed shared data types manager target views
+#
+
+class TypedSharedToolTypesTable(ProtectedFormObjectMixin, BaseTable):
+ """Typed shared tool types table"""
+
+ id = 'types_list'
+ hide_header = True
+ sortOn = None
+
+ @property
+ def cssClasses(self):
+ classes = ['table', 'table-bordered', 'table-striped', 'table-hover', 'table-tight']
+ permission = self.permission
+ if (not permission) or self.request.has_permission(permission, self.context):
+ classes.append('table-dnd')
+ return {'table': ' '.join(classes)}
+
+ @property
+ def data_attributes(self):
+ attributes = super(TypedSharedToolTypesTable, self).data_attributes
+ attributes['table'] = {'id': self.id,
+ 'data-ams-plugins': 'pyams_content',
+ 'data-ams-plugin-pyams_content-src':
+ '/--static--/pyams_content/js/pyams_content{MyAMS.devext}.js',
+ 'data-ams-location': absolute_url(ITypedDataManager(self.context), self.request),
+ 'data-ams-tablednd-drag-handle': 'td.sorter',
+ 'data-ams-tablednd-drop-target': 'set-types-order.json'}
+ return attributes
+
+ @reify
+ def values(self):
+ return list(super(TypedSharedToolTypesTable, self).values)
+
+ def render(self):
+ if not self.values:
+ translate = self.request.localizer.translate
+ return translate(_("No currently defined data type."))
+ return super(TypedSharedToolTypesTable, self).render()
+
+
+@adapter_config(context=(ITypedSharedTool, IPyAMSLayer, TypedSharedToolTypesTable), provides=IValues)
+class TypedSharedToolTypesValues(ContextRequestViewAdapter):
+ """Typed shared tool types table values adapter"""
+
+ @property
+ def values(self):
+ return ITypedDataManager(self.context).values()
+
+
+@adapter_config(name='sorter', context=(ITypedSharedTool, IPyAMSLayer, TypedSharedToolTypesTable),
+ provides=IColumn)
+class TypedSharedToolTypesSorterColumn(ProtectedFormObjectMixin, SorterColumn):
+ """Typed shared tool types sorter column"""
+
+
+@adapter_config(name='name', context=(ITypedSharedTool, IPyAMSLayer, TypedSharedToolTypesTable),
+ provides=IColumn)
+class TypedSharedToolTypesNameColumn(NameColumn):
+ """Typed shared tool types name column"""
+
+ _header = _("Data type label")
+
+ def renderCell(self, item):
+ return '<span data-ams-stop-propagation="true" ' \
+ ' data-ams-click-handler="PyAMS_content.types.switchSubtypes">' \
+ ' <span class="small hint" title="{hint}" data-ams-hint-gravity="e">' \
+ ' <i class="fa fa-plus-square-o switch"></i>' \
+ ' </span>' \
+ '</span> <span class="title">{title}</span>' \
+ '<div class="inner-table-form subtypes margin-x-10 margin-bottom-0 padding-left-5"></div>'.format(
+ hint=self.request.localizer.translate(_("Click to see subtypes")),
+ title=super(TypedSharedToolTypesNameColumn, self).renderCell(item))
+
+
+@adapter_config(name='paragraphs', context=(ITypedSharedTool, IPyAMSLayer, TypedSharedToolTypesTable),
+ provides=IColumn)
+class TypedSharedToolTypesParagraphsColumn(ActionColumn):
+ """Typed shared tool types paragraphs column"""
+
+ weight = 100
+
+ icon_class = 'fa fa-fw fa-paragraph'
+ icon_hint = _("Default paragraphs")
+
+ url = 'paragraphs-dialog.html'
+ modal_target = True
+
+ permission = MANAGE_TOOL_PERMISSION
+
+
+@adapter_config(name='associations', context=(ITypedSharedTool, IPyAMSLayer, TypedSharedToolTypesTable),
+ provides=IColumn)
+class TypedSharedToolTypesAssociationsColumn(ActionColumn):
+ """Typed shared tool types associations column"""
+
+ weight = 110
+
+ icon_class = 'fa fa-fw fa-link'
+ icon_hint = _("Default associations")
+
+ url = 'associations-dialog.html'
+ modal_target = True
+
+ permission = MANAGE_TOOL_PERMISSION
+
+
+@adapter_config(name='trash', context=(ITypedSharedTool, IPyAMSLayer, TypedSharedToolTypesTable),
+ provides=IColumn)
+class TypedSharedToolTypesTrashColumn(TrashColumn):
+ """Typed shared tool types trash column"""
+
+ permission = MANAGE_TOOL_PERMISSION
+
+
+@pagelet_config(name='data-types.html', context=ITypedSharedTool, layer=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION)
+class TypedSharedToolTypesView(ContainerAdminView):
+ """Typed shared tool types view"""
+
+ title = _("Content data types")
+ table_class = TypedSharedToolTypesTable
+
+
+#
+# Typed shared data types manager views
+#
+
+@view_config(name='delete-element.json', context=ITypedDataManager, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+def delete_data_type(request):
+ """Data type delete view"""
+ translate = request.localizer.translate
+ name = request.params.get('object_name')
+ if not name:
+ return {'status': 'message',
+ 'messagebox': {'status': 'error',
+ 'content': translate(_("No provided object_name argument!"))}}
+ if name not in request.context:
+ return {'status': 'message',
+ 'messagebox': {'status': 'error',
+ 'content': translate(_("Given data type doesn't exist!"))}}
+ del request.context[name]
+ return {'status': 'success'}
+
+
+@view_config(name='set-types-order.json', context=ITypedDataManager, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+def set_data_types_order(request):
+ """Update data types order"""
+ order = list(map(str, json.loads(request.params.get('names'))))
+ request.context.updateOrder(order)
+ return {'status': 'success'}
+
+
+#
+# Data type views
+#
+
+@adapter_config(context=(IBaseDataType, IPyAMSLayer), provides=ITableElementName)
+class DataTypeElementNameAdapter(ContextRequestAdapter):
+ """Types shared tool types name adapter"""
+
+ @property
+ def name(self):
+ return II18n(self.context).query_attribute('label', request=self.request)
+
+
+@viewlet_config(name='add-data-type.action', context=ITypedSharedTool, layer=IAdminLayer,
+ view=TypedSharedToolTypesView, manager=IWidgetTitleViewletManager,
+ permission=MANAGE_TOOL_PERMISSION, weight=1)
+class DataTypeAddAction(ToolbarAction):
+ """Data type adding action"""
+
+ label = _("Add data type")
+ label_css_class = 'fa fa-fw fa-plus'
+ url = 'add-data-type.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-data-type.html', context=ITypedSharedTool, layer=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION)
+class DataTypeAddForm(AdminDialogAddForm):
+ """Data type add form"""
+
+ legend = _("Add new data type")
+ icon_css_class = 'fa fa-fw fa-folder-o'
+ label_css_class = 'control-label col-md-4'
+ input_css_class = 'col-md-8'
+
+ fields = field.Fields(IDataType).omit('__parent__', '__name__')
+
+ ajax_handler = 'add-data-type.json'
+ edit_permission = MANAGE_TOOL_PERMISSION
+
+ def create(self, data):
+ return DataType()
+
+ def add(self, object):
+ name = translate_string(object.name, spaces='-')
+ ITypedDataManager(self.context)[name] = object
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'admin#data-types.html')
+
+
+@subscriber(IDataExtractedEvent, form_selector=DataTypeAddForm)
+def handle_datatype_add_form_data_extraction(event):
+ """Check new data type for existing name"""
+ context = event.form.context
+ manager = ITypedDataManager(context)
+ name = event.data.get('name')
+ if translate_string(name, spaces='-') in manager:
+ event.form.widgets.errors += (Invalid(_("Specified type name is already used!")),)
+
+
+@view_config(name='add-data-type.json', context=ITypedSharedTool, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+class DataTypeAJAXAddForm(AJAXAddForm, DataTypeAddForm):
+ """Data type add form, JSON renderer"""
+
+ def nextURL(self):
+ return '#data-types.html'
+
+
+@pagelet_config(name='properties.html', context=IDataType, layer=IPyAMSLayer, permission=MANAGE_TOOL_PERMISSION)
+class DataTypeEditForm(AdminDialogEditForm):
+ """Data type edit form"""
+
+ legend = _("Data type properties")
+ icon_css_class = 'fa fa-fw fa-folder-o'
+ label_css_class = 'control-label col-md-4'
+ input_css_class = 'col-md-8'
+
+ fields = field.Fields(IDataType).omit('__parent__', '__name__')
+
+ ajax_handler = 'properties.json'
+ edit_permission = MANAGE_TOOL_PERMISSION
+
+ def updateWidgets(self, prefix=None):
+ super(DataTypeEditForm, self).updateWidgets(prefix)
+ if 'name' in self.widgets:
+ self.widgets['name'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=IDataType, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+class DataTypeAJAXEditForm(AJAXEditForm, DataTypeEditForm):
+ """Data type edit form, JSON renderer"""
+
+
+#
+# Subtypes views
+#
+
+class DatatypeSubtypesTable(BaseTable):
+ """Data type subtypes table"""
+
+ @property
+ def id(self):
+ return 'subtypes_{0}_list'.format(self.context.__name__)
+
+ hide_header = True
+ sortOn = None
+
+ widget_class = 'ams-widget margin-top-5'
+ cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight table-dnd'}
+
+ @property
+ def data_attributes(self):
+ attributes = super(DatatypeSubtypesTable, self).data_attributes
+ attributes['table'] = {'id': self.id,
+ 'data-ams-plugins': 'pyams_content',
+ 'data-ams-plugin-pyams_content-src':
+ '/--static--/pyams_content/js/pyams_content{MyAMS.devext}.js',
+ 'data-ams-location': absolute_url(self.context, self.request),
+ 'data-ams-tablednd-drag-handle': 'td.sorter',
+ 'data-ams-tablednd-drop-target': 'set-subtypes-order.json'}
+ attributes.setdefault('tr', {}).setdefault('data-ams-stop-propagation', 'true')
+ return attributes
+
+ @reify
+ def values(self):
+ return list(super(DatatypeSubtypesTable, self).values)
+
+
+@adapter_config(context=(IDataType, IPyAMSLayer, DatatypeSubtypesTable), provides=IValues)
+class DatatypeSubtypesTableValues(ContextRequestViewAdapter):
+ """Data type subtypes table values adapter"""
+
+ @property
+ def values(self):
+ return self.context.values()
+
+
+@adapter_config(name='sorter', context=(IDataType, IPyAMSLayer, DatatypeSubtypesTable), provides=IColumn)
+class DatatypeSubtypesTableSorterColumn(SorterColumn):
+ """Data type subtypes table sorter column"""
+
+
+@adapter_config(name='name', context=(IDataType, IPyAMSLayer, DatatypeSubtypesTable), provides=IColumn)
+class DatatypeSubtypesTableNameColumn(NameColumn):
+ """Data type subtypes table name column"""
+
+ _header = _("Subtype label")
+
+ def renderHeadCell(self):
+ result = super(DatatypeSubtypesTableNameColumn, self).renderHeadCell()
+ registry = self.request.registry
+ viewlet = registry.queryMultiAdapter((self.context, self.request, self.table), IViewletManager,
+ name='pyams.widget_title')
+ if viewlet is not None:
+ viewlet.update()
+ result += viewlet.render()
+ return result
+
+
+@adapter_config(name='paragraphs', context=(IDataType, IPyAMSLayer, DatatypeSubtypesTable),
+ provides=IColumn)
+class DatatypeSubtypesTableParagraphsColumn(ActionColumn):
+ """Data type subtypes paragraphs column"""
+
+ weight = 100
+
+ icon_class = 'fa fa-fw fa-paragraph'
+ icon_hint = _("Default paragraphs")
+
+ url = 'paragraphs-dialog.html'
+ modal_target = True
+
+ permission = MANAGE_TOOL_PERMISSION
+
+
+@adapter_config(name='associations', context=(IDataType, IPyAMSLayer, DatatypeSubtypesTable),
+ provides=IColumn)
+class DatatypeSubtypesTableAssociationsColumn(ActionColumn):
+ """Data type subtypes associations column"""
+
+ weight = 110
+
+ icon_class = 'fa fa-fw fa-link'
+ icon_hint = _("Default associations")
+
+ url = 'associations-dialog.html'
+ modal_target = True
+
+ permission = MANAGE_TOOL_PERMISSION
+
+
+@adapter_config(name='trash', context=(IDataType, IPyAMSLayer, DatatypeSubtypesTable), provides=IColumn)
+class DatatypeSubtypesTableTrashColumn(TrashColumn):
+ """Data type subtypes table trash column"""
+
+ permission = MANAGE_TOOL_PERMISSION
+
+
+@view_config(name='get-subtypes-table.json', context=ITypedDataManager, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+def get_subtypes_table(request):
+ """Get subtypes table"""
+ datatype = request.context.get(str(request.params.get('object_name')))
+ if datatype is None:
+ raise NotFound()
+ table = DatatypeSubtypesTable(datatype, request)
+ table.update()
+ return table.render()
+
+
+@view_config(name='set-subtypes-order.json', context=IDataType, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+def set_subtypes_order(request):
+ """Update subtypes order"""
+ order = list(map(str, json.loads(request.params.get('names'))))
+ request.context.updateOrder(order)
+ return {'status': 'success'}
+
+
+@view_config(name='delete-element.json', context=IDataType, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+def delete_subtype(request):
+ """Data subtype delete view"""
+ translate = request.localizer.translate
+ name = request.params.get('object_name')
+ if not name:
+ return {'status': 'message',
+ 'messagebox': {'status': 'error',
+ 'content': translate(_("No provided object_name argument!"))}}
+ if name not in request.context:
+ return {'status': 'message',
+ 'messagebox': {'status': 'error',
+ 'content': translate(_("Given data subtype doesn't exist!"))}}
+ del request.context[name]
+ return {'status': 'success'}
+
+
+#
+# Data sub-types views
+#
+
+
+@viewlet_config(name='add-data-subtype.action', context=IDataType, layer=IPyAMSLayer,
+ view=DatatypeSubtypesTable, manager=IWidgetTitleViewletManager,
+ permission=MANAGE_TOOL_PERMISSION, weight=1)
+class DataSubtypeAddAction(ToolbarAction):
+ """Data subtype adding action"""
+
+ label = _("Add subtype")
+ label_css_class = 'fa fa-fw fa-plus'
+ url = 'add-data-subtype.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-data-subtype.html', context=IDataType, layer=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION)
+class DataSubtypeAddForm(AdminDialogAddForm):
+ """Data subtype add form"""
+
+ legend = _("Add new subtype")
+ icon_css_class = 'fa fa-fw fa-folder-o'
+ label_css_class = 'control-label col-md-4'
+ input_css_class = 'col-md-8'
+
+ fields = field.Fields(ISubType).omit('__parent__', '__name__')
+
+ ajax_handler = 'add-data-subtype.json'
+ edit_permission = MANAGE_TOOL_PERMISSION
+
+ def create(self, data):
+ return SubType()
+
+ def add(self, object):
+ name = translate_string(object.name, spaces='-')
+ IDataType(self.context)[name] = object
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'admin#data-types.html')
+
+
+@subscriber(IDataExtractedEvent, form_selector=DataSubtypeAddForm)
+def handle_subtype_add_form_data_extraction(event):
+ """Check new data subtype for existing name"""
+ context = event.form.context
+ manager = IDataType(context)
+ name = event.data.get('name')
+ if translate_string(name, spaces='-') in manager:
+ event.form.widgets.errors += (Invalid(_("Specified subtype name is already used!")),)
+
+
+@view_config(name='add-data-subtype.json', context=IDataType, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+class DataSubtypeAJAXAddForm(AJAXAddForm, DataSubtypeAddForm):
+ """Data subtype add form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ subtypes_table = DatatypeSubtypesTable(self.context, self.request)
+ subtypes_table.update()
+ return {'status': 'success',
+ 'message': self.request.localizer.translate(_("Subtype was correctly added.")),
+ 'events': [{
+ 'event': 'PyAMS_content.changed_item',
+ 'options': {
+ 'handler': 'PyAMS_content.types.refreshSubtypes',
+ 'object_name': subtypes_table.id,
+ 'table': subtypes_table.render()
+ }
+ }]}
+
+
+@pagelet_config(name='properties.html', context=ISubType, layer=IPyAMSLayer, permission=MANAGE_TOOL_PERMISSION)
+class DataSubtypeEditForm(AdminDialogEditForm):
+ """Data subtype edit form"""
+
+ legend = _("Data subtype properties")
+ icon_css_class = 'fa fa-fw fa-folder-o'
+ label_css_class = 'control-label col-md-4'
+ input_css_class = 'col-md-8'
+
+ fields = field.Fields(ISubType).omit('__parent__', '__name__')
+
+ ajax_handler = 'properties.json'
+ edit_permission = MANAGE_TOOL_PERMISSION
+
+ def updateWidgets(self, prefix=None):
+ super(DataSubtypeEditForm, self).updateWidgets(prefix)
+ if 'name' in self.widgets:
+ self.widgets['name'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=ISubType, request_type=IPyAMSLayer,
+ permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
+class DataSubtypeAJAXEditForm(AJAXEditForm, DataSubtypeEditForm):
+ """Data subtype edit form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ if 'label' in changes.get(IBaseDataType, ()):
+ target = get_parent(self.context, IDataType)
+ subtypes_table = DatatypeSubtypesTable(target, self.request)
+ subtypes_table.update()
+ return {'status': 'success',
+ 'message': self.request.localizer.translate(self.successMessage),
+ 'events': [{
+ 'event': 'PyAMS_content.changed_item',
+ 'options': {
+ 'handler': 'PyAMS_content.types.refreshSubtypes',
+ 'object_name': subtypes_table.id,
+ 'table': subtypes_table.render()
+ }
+ }]}
+ else:
+ return super(DataSubtypeAJAXEditForm, self).get_ajax_output(changes)