--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/layout.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,519 @@
+#
+# 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_pagelet.interfaces import IPagelet, PageletCreatedEvent
+from pyams_portal.interfaces import IPortalTemplate, IPortalTemplateConfiguration, ISlot, \
+ IPortletAddingInfo, IPortlet, ISlotConfiguration, IPortletPreviewer, IPortalTemplateContainer, \
+ IPortalTemplateContainerConfiguration, IPortalPortletsConfiguration, IPortalContext, IPortalPage, \
+ MANAGE_TEMPLATE_PERMISSION
+from pyams_skin.interfaces import IInnerPage, IPageHeader
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IMenuHeader
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_zmi.interfaces.menu import IPropertiesMenu, IContentManagementMenu
+from pyams_zmi.layer import IAdminLayer
+from transaction.interfaces import ITransactionManager
+from z3c.form.interfaces import IDataExtractedEvent, HIDDEN_MODE
+
+# import packages
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.zmi.template import PortalTemplateHeaderAdapter
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_skin.viewlet.toolbar import JsToolbarMenuItem, ToolbarMenuDivider, ToolbarMenuItem
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import query_utility
+from pyams_utils.traversing import get_parent
+from pyams_viewlet.manager import viewletmanager_config
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.view import AdminView
+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 implementer, Invalid, Interface
+
+from pyams_portal import _
+
+
+@adapter_config(context=(IPortalTemplate, IContentManagementMenu), provides=IMenuHeader)
+class PortalTemplateMenuHeader(object):
+ """Portal template menu header"""
+
+ def __init__(self, context, menu):
+ self.context = context
+ self.menu = menu
+
+ @property
+ def header(self):
+ return _("Template management")
+
+
+@viewlet_config(name='template-properties.menu', context=IPortalTemplate, layer=IAdminLayer,
+ manager=IContentManagementMenu, permission=VIEW_SYSTEM_PERMISSION, weight=1)
+@viewletmanager_config(name='template-properties.menu', layer=IAdminLayer, provides=IPropertiesMenu)
+@implementer(IPropertiesMenu)
+class PortalTemplatePropertiesMenu(MenuItem):
+ """Portal template properties menu"""
+
+ label = _("Properties")
+ icon_class = 'fa-twitch'
+ url = '#properties.html'
+
+
+@pagelet_config(name='properties.html', context=IPortalTemplate, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@template_config(template='templates/layout.pt', layer=IAdminLayer)
+@implementer(IInnerPage)
+class PortalTemplateLayoutView(AdminView):
+ """Portal template main layout configuration view"""
+
+ @property
+ def title(self):
+ container = get_parent(self.context, IPortalTemplateContainer)
+ if container is None:
+ context = get_parent(self.context, IPortalContext)
+ page = IPortalPage(context)
+ if page.use_local_template:
+ return _("Local template configuration")
+ else:
+ translate = self.request.localizer.translate
+ return translate(_("Shared template configuration ({0})")).format(page.template.name)
+ else:
+ return _("Template configuration")
+
+ def get_template(self):
+ return self.context
+
+ def get_context(self):
+ return self.context
+
+ @property
+ def can_change(self):
+ return self.request.has_permission(MANAGE_TEMPLATE_PERMISSION)
+
+ @reify
+ def template_configuration(self):
+ return IPortalTemplateConfiguration(self.get_template())
+
+ @reify
+ def portlet_configuration(self):
+ return IPortalPortletsConfiguration(self.get_context())
+
+ @property
+ def selected_portlets(self):
+ container = query_utility(IPortalTemplateContainer)
+ configuration = IPortalTemplateContainerConfiguration(container)
+ return filter(lambda x: x is not None,
+ [query_utility(IPortlet, name=portlet_name)
+ for portlet_name in configuration.toolbar_portlets or ()])
+
+ def get_portlet(self, name):
+ return self.request.registry.getUtility(IPortlet, name=name)
+
+ def get_portlet_label(self, name):
+ return self.request.localizer.translate(self.get_portlet(name).label)
+
+ def get_portlet_preview(self, portlet_id):
+ settings = self.portlet_configuration.get_portlet_configuration(portlet_id).settings
+ previewer = self.request.registry.queryMultiAdapter((self.get_context(), self.request, self, settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ return previewer.render()
+ else:
+ return ''
+
+
+@adapter_config(context=(IPortalTemplate, IAdminLayer, Interface), provides=IPageHeader)
+class PortalTemplateLayoutHeaderAdapter(PortalTemplateHeaderAdapter):
+ """Portal template configuration header adapter"""
+
+ back_url = '/admin.html#portal-templates.html'
+ back_target = None
+
+
+#
+# Rows views
+#
+
+@viewlet_config(name='add-template-row.menu', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=1)
+class PortalTemplateRowAddMenu(JsToolbarMenuItem):
+ """Portal template row add menu"""
+
+ label = _("Add row...")
+ label_css_class = 'fa fa-fw fa-indent'
+ url = 'PyAMS_portal.template.addRow'
+
+
+@view_config(name='add-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def add_template_row(request):
+ """Add template raw"""
+ config = IPortalTemplateConfiguration(request.context)
+ return {'row_id': config.add_row()}
+
+
+@view_config(name='set-template-row-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_row_order(request):
+ """Set template rows order"""
+ config = IPortalTemplateConfiguration(request.context)
+ row_ids = map(int, json.loads(request.params.get('rows')))
+ config.set_row_order(row_ids)
+ return {'status': 'success'}
+
+
+@view_config(name='delete-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_template_row(request):
+ """Delete template row"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.delete_row(int(request.params.get('row_id')))
+ return {'status': 'success'}
+
+
+#
+# Slots views
+#
+
+@viewlet_config(name='add-template-slot.menu', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=2)
+class PortalTemplateSlotAddMenu(ToolbarMenuItem):
+ """Portal template slot add menu"""
+
+ label = _("Add slot...")
+ label_css_class = 'fa fa-fw fa-columns'
+ url = 'add-template-slot.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-template-slot.html', context=IPortalTemplate, layer=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION)
+class PortalTemplateSlotAddForm(AdminDialogAddForm):
+ """Portal template slot add form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("« {0} » portal template")).format(self.context.name)
+
+ legend = _("Add slot")
+ icon_css_class = 'fa fa-fw fa-columns'
+
+ fields = field.Fields(ISlot)
+ ajax_handler = 'add-template-slot.json'
+ edit_permission = None
+
+ def updateWidgets(self, prefix=None):
+ super(PortalTemplateSlotAddForm, self).updateWidgets()
+ self.widgets['row_id'].value = self.request.params.get('form.widgets.row_id')
+ if self.widgets['row_id'].value:
+ self.widgets['row_id'].mode = HIDDEN_MODE
+
+ def createAndAdd(self, data):
+ config = IPortalTemplateConfiguration(self.context)
+ return config.add_slot(data.get('name'), data.get('row_id'))
+
+
+@subscriber(IDataExtractedEvent, form_selector=PortalTemplateSlotAddForm)
+def handle_new_slot_data_extraction(event):
+ """Handle new slot form data extraction"""
+ config = IPortalTemplateConfiguration(event.form.context)
+ name = event.data.get('name')
+ if name in config.slot_names:
+ event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
+
+
+@view_config(name='add-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplateSlotAJAXAddForm(AJAXAddForm, PortalTemplateSlotAddForm):
+ """Portal template slot add form, AJAX handler"""
+
+ def get_ajax_output(self, changes):
+ return {'status': 'callback',
+ 'callback': 'PyAMS_portal.template.addSlotCallback',
+ 'options': {'row_id': changes[0],
+ 'slot_name': changes[1]}}
+
+
+@view_config(name='set-template-slot-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_slot_order(request):
+ """Set template slots order"""
+ config = IPortalTemplateConfiguration(request.context)
+ order = json.loads(request.params.get('order'))
+ for key in order.copy().keys():
+ order[int(key)] = order.pop(key)
+ config.set_slot_order(order)
+ return {'status': 'success'}
+
+
+@view_config(name='get-slots-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def get_template_slots_width(request):
+ """Get template slots width"""
+ config = IPortalTemplateConfiguration(request.context)
+ return config.get_slots_width(request.params.get('device'))
+
+
+@view_config(name='set-slot-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_slot_width(request):
+ """Set template slot width"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.set_slot_width(request.params.get('slot_name'),
+ request.params.get('device'),
+ int(request.params.get('width')))
+ return config.get_slots_width(request.params.get('device'))
+
+
+@pagelet_config(name='slot-properties.html', context=IPortalTemplate, layer=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+class PortalTemplateSlotPropertiesEditForm(AdminDialogEditForm):
+ """Slot properties edit form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("« {0} » portal template - {1} slot")).format(self.context.name,
+ self.getContent().slot_name)
+
+ legend = _("Edit slot properties")
+ fields = field.Fields(ISlotConfiguration).omit('portlet_ids')
+
+ label_css_class = 'control-label col-md-5'
+ input_css_class = 'col-md-7'
+
+ ajax_handler = 'slot-properties.json'
+ edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+ def __init__(self, context, request):
+ super(PortalTemplateSlotPropertiesEditForm, self).__init__(context, request)
+ self.config = IPortalTemplateConfiguration(context)
+
+ def getContent(self):
+ slot_name = self.request.params.get('form.widgets.slot_name')
+ return self.config.slot_config[slot_name]
+
+ def updateWidgets(self, prefix=None):
+ super(PortalTemplateSlotPropertiesEditForm, self).updateWidgets(prefix)
+ self.widgets['slot_name'].mode = HIDDEN_MODE
+
+
+@view_config(name='slot-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplateSlotPropertiesAJAXEditForm(AJAXEditForm, PortalTemplateSlotPropertiesEditForm):
+ """Slot properties edit form, AJAX renderer"""
+
+ def get_ajax_output(self, changes):
+ if changes:
+ slot_name = self.widgets['slot_name'].value
+ slot_config = self.config.slot_config[slot_name]
+ return {'status': 'success',
+ 'callback': 'PyAMS_portal.template.editSlotCallback',
+ 'options': {'slot_name': slot_name,
+ 'width': slot_config.get_width()}}
+ else:
+ return super(PortalTemplateSlotPropertiesAJAXEditForm, self).get_ajax_output(changes)
+
+
+@view_config(name='delete-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_template_slot(request):
+ """Delete template slot"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.delete_slot(request.params.get('slot_name'))
+ return {'status': 'success'}
+
+
+#
+# Portlet views
+#
+
+@viewlet_config(name='add-template-portlet.divider', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=10)
+class PortalTemplateAddMenuDivider(ToolbarMenuDivider):
+ """Portal template menu divider"""
+
+
+@viewlet_config(name='add-template-portlet.menu', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=20)
+class PortalTemplatePortletAddMenu(ToolbarMenuItem):
+ """Portal template portlet add menu"""
+
+ label = _("Add portlet...")
+ label_css_class = 'fa fa-fw fa-columns'
+ url = 'add-template-portlet.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-template-portlet.html', context=IPortalTemplate, layer=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION)
+class PortalTemplatePortletAddForm(AdminDialogAddForm):
+ """Portal template portlet add form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("« {0} » portal template")).format(self.context.name)
+
+ legend = _("Add portlet")
+ icon_css_class = 'fa fa-fw fa-columns'
+
+ fields = field.Fields(IPortletAddingInfo)
+ ajax_handler = 'add-template-portlet.json'
+ edit_permission = None
+
+ def createAndAdd(self, data):
+ config = IPortalTemplateConfiguration(self.context)
+ return config.add_portlet(data.get('portlet_name'), data.get('slot_name'))
+
+
+@view_config(name='add-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplatePortletAJAXAddForm(AJAXAddForm, PortalTemplatePortletAddForm):
+ """Portal template portlet add form, AJAX handler"""
+
+ def get_ajax_output(self, changes):
+ configuration = IPortalPortletsConfiguration(self.context)
+ settings = configuration.get_portlet_configuration(changes['portlet_id']).settings
+ previewer = self.request.registry.queryMultiAdapter((self.context, self.request, self, settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ changes['preview'] = previewer.render()
+ return {'status': 'callback',
+ 'callback': 'PyAMS_portal.template.addPortletCallback',
+ 'options': changes}
+
+
+@view_config(name='drag-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def drag_template_portlet(request):
+ """Drag portlet icon to slot"""
+ tmpl_config = IPortalTemplateConfiguration(request.context)
+ portlets_config = IPortalPortletsConfiguration(request.context)
+ portlet_name = request.params.get('portlet_name')
+ slot_name = request.params.get('slot_name')
+ changes = tmpl_config.add_portlet(portlet_name, slot_name)
+ settings = portlets_config.get_portlet_configuration(changes['portlet_id']).settings
+ previewer = request.registry.queryMultiAdapter((request.context, request, request, settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ changes['preview'] = previewer.render()
+ return {'status': 'callback',
+ 'close_form': False,
+ 'callback': 'PyAMS_portal.template.addPortletCallback',
+ 'options': changes}
+
+
+@view_config(name='set-template-portlet-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_portlet_order(request):
+ """Set template portlet order"""
+ order = json.loads(request.params.get('order'))
+ order['from'] = int(order['from'])
+ order['to']['portlet_ids'] = list(map(int, order['to']['portlet_ids']))
+ IPortalTemplateConfiguration(request.context).set_portlet_order(order)
+ return {'status': 'success'}
+
+
+@view_config(name='portlet-properties.html', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+class PortalTemplatePortletEditForm(AdminDialogEditForm):
+ """Portal template portlet edit form"""
+
+ dialog_class = 'modal-large'
+
+ def __call__(self):
+ request = self.request
+ request.registry.notify(PageletCreatedEvent(self))
+ portlet_id = int(request.params.get('form.widgets.portlet_id'))
+ portlet_config = IPortalPortletsConfiguration(self.context).get_portlet_configuration(portlet_id)
+ if portlet_config is None:
+ raise NotFound()
+ editor = self.request.registry.queryMultiAdapter((portlet_config.editor_settings, request),
+ IPagelet, name='properties.html')
+ if editor is None:
+ raise NotFound()
+ request.registry.notify(PageletCreatedEvent(editor))
+ editor.ajax_handler = 'portlet-properties.json'
+ editor.update()
+ return editor()
+
+
+@view_config(name='portlet-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplatePortletAJAXEditForm(AJAXEditForm, PortalTemplatePortletEditForm):
+ """Portal template portlet edit form, AJAX renderer"""
+
+ def __call__(self):
+ request = self.request
+ request.registry.notify(PageletCreatedEvent(self))
+ # load portlet config
+ portlet_id = int(request.params.get('form.widgets.portlet_id'))
+ portlet_config = IPortalPortletsConfiguration(self.context).get_portlet_configuration(portlet_id)
+ if portlet_config is None:
+ raise NotFound()
+ # check inheritance
+ old_override = portlet_config.inherit_parent
+ new_override = request.params.get('form.widgets.override_parent')
+ if new_override:
+ portlet_config.inherit_parent = False
+ else:
+ portlet_config.inherit_parent = True
+ changed_override = portlet_config.inherit_parent != old_override
+ # update settings
+ editor = request.registry.queryMultiAdapter((portlet_config.editor_settings, request),
+ IPagelet, name='properties.json')
+ if editor is None:
+ raise NotFound()
+ changes = editor()
+ translate = self.request.localizer.translate
+ if changed_override or changes:
+ # we commit before loading previewer to avoid BLOBs "uncommited changes" error
+ ITransactionManager(self.context).commit()
+ previewer = request.registry.queryMultiAdapter((self.context, request, self, portlet_config.settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ changes.update({'status': 'success',
+ 'message': translate(self.successMessage),
+ 'callback': 'PyAMS_portal.template.editPortletCallback',
+ 'options': {'portlet_id': portlet_id,
+ 'preview': previewer.render()}})
+ return changes
+
+
+@view_config(name='delete-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_template_portlet(request):
+ """Delete template portlet"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.delete_portlet(int(request.params.get('portlet_id')))
+ return {'status': 'success'}