src/pyams_content/shared/common/zmi/types.py
changeset 276 78422a1c4228
child 288 3f7f7a1624f0
--- /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>&nbsp;&nbsp;&nbsp;<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)