src/pyams_portal/zmi/template/config.py
changeset 0 6f99128c6d48
child 2 619200513bbc
equal deleted inserted replaced
-1:000000000000 0:6f99128c6d48
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 from pyams_skin.page import DefaultPageHeaderAdapter
       
    13 
       
    14 __docformat__ = 'restructuredtext'
       
    15 
       
    16 
       
    17 # import standard library
       
    18 import json
       
    19 
       
    20 # import interfaces
       
    21 from pyams_pagelet.interfaces import IPagelet, PageletCreatedEvent
       
    22 from pyams_portal.interfaces import IPortalTemplate, IPortalTemplateConfiguration, ISlot, \
       
    23     IPortletAddingInfo, IPortlet, ISlotConfiguration, IPortletPreviewer, IPortalTemplateContainer, \
       
    24     IPortalTemplateContainerConfiguration
       
    25 from pyams_skin.interfaces import IInnerPage, IPageHeader, IContentTitle
       
    26 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
       
    27 from pyams_skin.layer import IPyAMSLayer
       
    28 from pyams_workflow.interfaces import IWorkflowState, IWorkflowVersions
       
    29 from pyams_zmi.interfaces.menu import ISiteManagementMenu, IPropertiesMenu
       
    30 from pyams_zmi.layer import IAdminLayer
       
    31 from transaction.interfaces import ITransactionManager
       
    32 from z3c.form.interfaces import IDataExtractedEvent, HIDDEN_MODE
       
    33 
       
    34 # import packages
       
    35 from pyams_form.form import AJAXAddForm, AJAXEditForm
       
    36 from pyams_pagelet.pagelet import pagelet_config
       
    37 from pyams_portal.workflow import STATUS_LABELS, STATUS_IDS, PUBLISHED, ARCHIVED
       
    38 from pyams_skin.viewlet.menu import MenuItem
       
    39 from pyams_skin.viewlet.toolbar import JsToolbarMenuItem, ToolbarMenuDivider, ToolbarMenuItem
       
    40 from pyams_template.template import template_config
       
    41 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
       
    42 from pyams_utils.registry import query_utility
       
    43 from pyams_viewlet.manager import viewletmanager_config
       
    44 from pyams_viewlet.viewlet import viewlet_config
       
    45 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
       
    46 from pyams_zmi.view import AdminView
       
    47 from pyramid.decorator import reify
       
    48 from pyramid.events import subscriber
       
    49 from pyramid.exceptions import NotFound
       
    50 from pyramid.view import view_config
       
    51 from z3c.form import field
       
    52 from zope.interface import implementer, Invalid
       
    53 
       
    54 from pyams_portal import _
       
    55 
       
    56 
       
    57 @viewlet_config(name='template-properties.menu', context=IPortalTemplate, layer=IAdminLayer,
       
    58                 manager=ISiteManagementMenu, permission='system.view', weight=1)
       
    59 @viewletmanager_config(name='template-properties.menu', layer=IAdminLayer, provides=IPropertiesMenu)
       
    60 @implementer(IPropertiesMenu)
       
    61 class PortalTemplatePropertiesMenu(MenuItem):
       
    62     """Portal template properties menu"""
       
    63 
       
    64     label = _("Properties")
       
    65     icon_class = 'fa-twitch'
       
    66     url = '#properties.html'
       
    67 
       
    68 
       
    69 @pagelet_config(name='properties.html', context=IPortalTemplate, layer=IPyAMSLayer, permission='system.view')
       
    70 @template_config(template='templates/config.pt', layer=IAdminLayer)
       
    71 @implementer(IInnerPage)
       
    72 class PortalTemplateConfigView(AdminView):
       
    73     """Portal template configuration view"""
       
    74 
       
    75     title = _("Shared portal template configuration")
       
    76 
       
    77     def get_context(self):
       
    78         return self.context
       
    79 
       
    80     @property
       
    81     def can_change(self):
       
    82         return self.request.has_permission('portal.templates.manage') and \
       
    83                IWorkflowState(self.get_context()).state not in (PUBLISHED, ARCHIVED)
       
    84 
       
    85     @reify
       
    86     def configuration(self):
       
    87         return IPortalTemplateConfiguration(self.get_context())
       
    88 
       
    89     @property
       
    90     def selected_portlets(self):
       
    91         container = query_utility(IPortalTemplateContainer)
       
    92         configuration = IPortalTemplateContainerConfiguration(container)
       
    93         return [query_utility(IPortlet, name=portlet_name) for portlet_name in configuration.selected_portlets or ()]
       
    94 
       
    95     def get_portlet(self, name):
       
    96         return self.request.registry.getUtility(IPortlet, name=name)
       
    97 
       
    98     def get_portlet_label(self, name):
       
    99         return self.request.localizer.translate(self.get_portlet(name).label)
       
   100 
       
   101     def get_portlet_preview(self, slot_name, position):
       
   102         portlet_config = self.configuration.get_portlet_configuration(slot_name, position)
       
   103         previewer = self.request.registry.queryMultiAdapter((self.get_context(), self.request, self, portlet_config),
       
   104                                                             IPortletPreviewer)
       
   105         if previewer is not None:
       
   106             previewer.update()
       
   107             return previewer.render()
       
   108         else:
       
   109             return ''
       
   110 
       
   111 
       
   112 @adapter_config(context=(IPortalTemplate, IAdminLayer, PortalTemplateConfigView), provides=IPageHeader)
       
   113 class PortalTemplateConfigHeaderAdapter(DefaultPageHeaderAdapter):
       
   114     """Portal template configuration header adapter"""
       
   115 
       
   116     back_url = '/admin.html#portal-templates.html'
       
   117     back_target = None
       
   118 
       
   119     icon_class = 'fa fa-fw fa-columns'
       
   120     subtitle = _("Portlets configuration")
       
   121 
       
   122 
       
   123 #
       
   124 # Rows views
       
   125 #
       
   126 
       
   127 @viewlet_config(name='add-template-row.menu', context=IPortalTemplate, layer=IAdminLayer,
       
   128                 view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
       
   129                 permission='portal.templates.manage', weight=1)
       
   130 class PortalTemplateRowAddMenu(JsToolbarMenuItem):
       
   131     """Portal template row add menu"""
       
   132 
       
   133     label = _("Add row...")
       
   134     label_css_class = 'fa fa-fw fa-indent'
       
   135     url = 'PyAMS_portal.template.addRow'
       
   136 
       
   137 
       
   138 @view_config(name='add-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   139              permission='portal.templates.manage', renderer='json', xhr=True)
       
   140 def add_template_row(request):
       
   141     """Add template raw"""
       
   142     config = IPortalTemplateConfiguration(request.context)
       
   143     return {'row_id': config.add_row()}
       
   144 
       
   145 
       
   146 @view_config(name='set-template-row-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   147              permission='portal.templates.manage', renderer='json', xhr=True)
       
   148 def set_template_row_order(request):
       
   149     """Set template rows order"""
       
   150     config = IPortalTemplateConfiguration(request.context)
       
   151     row_ids = map(int, json.loads(request.params.get('rows')))
       
   152     config.set_row_order(row_ids)
       
   153     return {'status': 'success'}
       
   154 
       
   155 
       
   156 @view_config(name='delete-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   157              permission='portal.templates.manage', renderer='json', xhr=True)
       
   158 def delete_template_row(request):
       
   159     """Delete template row"""
       
   160     config = IPortalTemplateConfiguration(request.context)
       
   161     config.delete_row(int(request.params.get('row_id')))
       
   162     return {'status': 'success'}
       
   163 
       
   164 
       
   165 #
       
   166 # Slots views
       
   167 #
       
   168 
       
   169 @viewlet_config(name='add-template-slot.menu', context=IPortalTemplate, layer=IAdminLayer,
       
   170                 view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
       
   171                 permission='portal.templates.manage', weight=2)
       
   172 class PortalTemplateSlotAddMenu(ToolbarMenuItem):
       
   173     """Portal template slot add menu"""
       
   174 
       
   175     label = _("Add slot...")
       
   176     label_css_class = 'fa fa-fw fa-columns'
       
   177     url = 'add-template-slot.html'
       
   178     modal_target = True
       
   179 
       
   180 
       
   181 @pagelet_config(name='add-template-slot.html', context=IPortalTemplate, layer=IPyAMSLayer,
       
   182                 permission='portal.templates.manage')
       
   183 class PortalTemplateSlotAddForm(AdminDialogAddForm):
       
   184     """Portal template slot add form"""
       
   185 
       
   186     @property
       
   187     def title(self):
       
   188         translate = self.request.localizer.translate
       
   189         return translate(_("« {0} »  portal template")).format(self.context.name)
       
   190 
       
   191     legend = _("Add slot")
       
   192     icon_css_class = 'fa fa-fw fa-columns'
       
   193 
       
   194     fields = field.Fields(ISlot)
       
   195     ajax_handler = 'add-template-slot.json'
       
   196     edit_permission = None
       
   197 
       
   198     def updateWidgets(self, prefix=None):
       
   199         super(PortalTemplateSlotAddForm, self).updateWidgets()
       
   200         self.widgets['row_id'].value = self.request.params.get('form.widgets.row_id')
       
   201 
       
   202     def createAndAdd(self, data):
       
   203         config = IPortalTemplateConfiguration(self.context)
       
   204         return config.add_slot(data.get('name'), data.get('row_id'))
       
   205 
       
   206 
       
   207 @subscriber(IDataExtractedEvent, form_selector=PortalTemplateSlotAddForm)
       
   208 def handle_new_slot_data_extraction(event):
       
   209     """Handle new slot form data extraction"""
       
   210     config = IPortalTemplateConfiguration(event.form.context)
       
   211     name = event.data.get('name')
       
   212     if name in config.slot_names:
       
   213         event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
       
   214 
       
   215 
       
   216 @view_config(name='add-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   217              permission='portal.templates.manage', renderer='json', xhr=True)
       
   218 class PortalTemplateSlotAJAXAddForm(AJAXAddForm, PortalTemplateSlotAddForm):
       
   219     """Portal template slot add form, AJAX handler"""
       
   220 
       
   221     def get_ajax_output(self, changes):
       
   222         return {'status': 'callback',
       
   223                 'callback': 'PyAMS_portal.template.addSlotCallback',
       
   224                 'options': {'row_id': changes[0],
       
   225                             'slot_name': changes[1]}}
       
   226 
       
   227 
       
   228 @view_config(name='set-template-slot-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   229              permission='portal.templates.manage', renderer='json', xhr=True)
       
   230 def set_template_slot_order(request):
       
   231     """Set template slots order"""
       
   232     config = IPortalTemplateConfiguration(request.context)
       
   233     order = json.loads(request.params.get('order'))
       
   234     for key in order.copy().keys():
       
   235         order[int(key)] = order.pop(key)
       
   236     config.set_slot_order(order)
       
   237     return {'status': 'success'}
       
   238 
       
   239 
       
   240 @view_config(name='get-slots-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   241              permission='system.view', renderer='json', xhr=True)
       
   242 def get_template_slots_width(request):
       
   243     """Get template slots width"""
       
   244     config = IPortalTemplateConfiguration(request.context)
       
   245     return config.get_slots_width(request.params.get('device'))
       
   246 
       
   247 
       
   248 @view_config(name='set-slot-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   249              permission='portal.templates.manage', renderer='json', xhr=True)
       
   250 def set_template_slot_width(request):
       
   251     """Set template slot width"""
       
   252     config = IPortalTemplateConfiguration(request.context)
       
   253     config.set_slot_width(request.params.get('slot_name'),
       
   254                           request.params.get('device'),
       
   255                           int(request.params.get('width')))
       
   256     return config.get_slots_width(request.params.get('device'))
       
   257 
       
   258 
       
   259 @pagelet_config(name='slot-properties.html', context=IPortalTemplate, layer=IPyAMSLayer, permission='system.view')
       
   260 class PortalTemplateSlotPropertiesEditForm(AdminDialogEditForm):
       
   261     """Slot properties edit form"""
       
   262 
       
   263     @property
       
   264     def title(self):
       
   265         translate = self.request.localizer.translate
       
   266         return translate(_("« {0} »  portal template - {1} slot")).format(self.context.name,
       
   267                                                                           self.getContent().slot_name)
       
   268 
       
   269     legend = _("Edit slot properties")
       
   270 
       
   271     label_css_class = 'control-label col-md-5'
       
   272     input_css_class = 'col-md-7'
       
   273 
       
   274     @property
       
   275     def fields(self):
       
   276         fields = field.Fields(ISlotConfiguration)
       
   277         if not self.getContent().can_inherit:
       
   278             fields = fields.omit('inherit_parent')
       
   279         return fields
       
   280 
       
   281     ajax_handler = 'slot-properties.json'
       
   282     edit_permission = 'portal.templates.manage'
       
   283 
       
   284     def __init__(self, context, request):
       
   285         super(PortalTemplateSlotPropertiesEditForm, self).__init__(context, request)
       
   286         self.config = IPortalTemplateConfiguration(context)
       
   287 
       
   288     def getContent(self):
       
   289         slot_name = self.request.params.get('form.widgets.slot_name')
       
   290         return self.config.slot_config[slot_name]
       
   291 
       
   292     def updateWidgets(self, prefix=None):
       
   293         super(PortalTemplateSlotPropertiesEditForm, self).updateWidgets(prefix)
       
   294         self.widgets['slot_name'].mode = HIDDEN_MODE
       
   295 
       
   296 
       
   297 @view_config(name='slot-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   298              permission='portal.templates.manage', renderer='json', xhr=True)
       
   299 class PortalTemplateSlotPropertiesAJAXEditForm(AJAXEditForm, PortalTemplateSlotPropertiesEditForm):
       
   300     """Slot properties edit form, AJAX renderer"""
       
   301 
       
   302     def get_ajax_output(self, changes):
       
   303         if changes:
       
   304             slot_name = self.widgets['slot_name'].value
       
   305             slot_config = self.config.slot_config[slot_name]
       
   306             return {'status': 'success',
       
   307                     'callback': 'PyAMS_portal.template.editSlotCallback',
       
   308                     'options': {'slot_name': slot_name,
       
   309                                 'width': slot_config.get_width()}}
       
   310         else:
       
   311             return super(PortalTemplateSlotPropertiesAJAXEditForm, self).get_ajax_output(changes)
       
   312 
       
   313 
       
   314 @view_config(name='delete-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   315              permission='portal.templates.manage', renderer='json', xhr=True)
       
   316 def delete_template_slot(request):
       
   317     """Delete template slot"""
       
   318     config = IPortalTemplateConfiguration(request.context)
       
   319     config.delete_slot(request.params.get('slot_name'))
       
   320     return {'status': 'success'}
       
   321 
       
   322 
       
   323 #
       
   324 # Portlet views
       
   325 #
       
   326 
       
   327 @viewlet_config(name='add-template-portlet.divider', context=IPortalTemplate, layer=IAdminLayer,
       
   328                 view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
       
   329                 permission='portal.templates.manage', weight=10)
       
   330 class PortalTemplateAddMenuDivider(ToolbarMenuDivider):
       
   331     """Portal template menu divider"""
       
   332 
       
   333 
       
   334 @viewlet_config(name='add-template-portlet.menu', context=IPortalTemplate, layer=IAdminLayer,
       
   335                 view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
       
   336                 permission='portal.templates.manage', weight=20)
       
   337 class PortalTemplatePortletAddMenu(ToolbarMenuItem):
       
   338     """Portal template portlet add menu"""
       
   339 
       
   340     label = _("Add portlet...")
       
   341     label_css_class = 'fa fa-fw fa-columns'
       
   342     url = 'add-template-portlet.html'
       
   343     modal_target = True
       
   344 
       
   345 
       
   346 @pagelet_config(name='add-template-portlet.html', context=IPortalTemplate, layer=IPyAMSLayer,
       
   347                 permission='portal.templates.manage')
       
   348 class PortalTemplatePortletAddForm(AdminDialogAddForm):
       
   349     """Portal template portlet add form"""
       
   350 
       
   351     @property
       
   352     def title(self):
       
   353         translate = self.request.localizer.translate
       
   354         return translate(_("« {0} »  portal template")).format(self.context.name)
       
   355 
       
   356     legend = _("Add portlet")
       
   357     icon_css_class = 'fa fa-fw fa-columns'
       
   358 
       
   359     fields = field.Fields(IPortletAddingInfo)
       
   360     ajax_handler = 'add-template-portlet.json'
       
   361     edit_permission = None
       
   362 
       
   363     def createAndAdd(self, data):
       
   364         config = IPortalTemplateConfiguration(self.context)
       
   365         return config.add_portlet(data.get('portlet_name'), data.get('slot_name'))
       
   366 
       
   367 
       
   368 @view_config(name='add-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   369              permission='portal.templates.manage', renderer='json', xhr=True)
       
   370 class PortalTemplatePortletAJAXAddForm(AJAXAddForm, PortalTemplatePortletAddForm):
       
   371     """Portal template portlet add form, AJAX handler"""
       
   372 
       
   373     def get_ajax_output(self, changes):
       
   374         config = IPortalTemplateConfiguration(self.context)
       
   375         portlet_config = config.get_portlet_configuration(changes['slot_name'], changes['position'])
       
   376         previewer = self.request.registry.queryMultiAdapter((self.context, self.request, self, portlet_config),
       
   377                                                             IPortletPreviewer)
       
   378         if previewer is not None:
       
   379             previewer.update()
       
   380             changes['preview'] = previewer.render()
       
   381         return {'status': 'callback',
       
   382                 'callback': 'PyAMS_portal.template.addPortletCallback',
       
   383                 'options': changes}
       
   384 
       
   385 
       
   386 @view_config(name='drag-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   387              permission='portal.templates.manage', renderer='json', xhr=True)
       
   388 def drag_template_portlet(request):
       
   389     """Drag portlet icon to slot"""
       
   390     config = IPortalTemplateConfiguration(request.context)
       
   391     portlet_name = request.params.get('portlet_name')
       
   392     slot_name = request.params.get('slot_name')
       
   393     changes = config.add_portlet(portlet_name, slot_name)
       
   394     portlet_config = config.get_portlet_configuration(changes['slot_name'], changes['position'])
       
   395     previewer = request.registry.queryMultiAdapter((request.context, request, request, portlet_config),
       
   396                                                    IPortletPreviewer)
       
   397     if previewer is not None:
       
   398         previewer.update()
       
   399         changes['preview'] = previewer.render()
       
   400     return {'status': 'callback',
       
   401             'close_form': False,
       
   402             'callback': 'PyAMS_portal.template.addPortletCallback',
       
   403             'options': changes}
       
   404 
       
   405 
       
   406 @view_config(name='set-template-portlet-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   407              permission='portal.templates.manage', renderer='json', xhr=True)
       
   408 def set_template_portlet_order(request):
       
   409     """Set template portlet order"""
       
   410     config = IPortalTemplateConfiguration(request.context)
       
   411     order = json.loads(request.params.get('order'))
       
   412     order['from']['position'] = int(order['from']['position'])
       
   413     order['to']['positions'] = list(map(int, order['to']['positions']))
       
   414     config.set_portlet_order(order)
       
   415     return {'status': 'success'}
       
   416 
       
   417 
       
   418 @view_config(name='portlet-properties.html', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   419              permission='system.view')
       
   420 class PortalTemplatePortletEditForm(AdminDialogEditForm):
       
   421     """Portal template portlet edit form"""
       
   422 
       
   423     dialog_class = 'modal-large'
       
   424 
       
   425     def __call__(self):
       
   426         request = self.request
       
   427         request.registry.notify(PageletCreatedEvent(self))
       
   428         slot_name = request.params.get('form.widgets.slot_name')
       
   429         position = int(request.params.get('form.widgets.position'))
       
   430         config = IPortalTemplateConfiguration(self.context)
       
   431         portlet_config = config.get_portlet_configuration(slot_name, position)
       
   432         if portlet_config is None:
       
   433             raise NotFound()
       
   434         editor = self.request.registry.queryMultiAdapter((portlet_config, request),
       
   435                                                          IPagelet, name='properties.html')
       
   436         if editor is None:
       
   437             raise NotFound()
       
   438         editor.ajax_handler = 'portlet-properties.json'
       
   439         editor.update()
       
   440         return editor()
       
   441 
       
   442 
       
   443 @view_config(name='portlet-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   444              permission='portal.templates.manage', renderer='json', xhr=True)
       
   445 class PortalTemplatePortletAJAXEditForm(AJAXEditForm, PortalTemplatePortletEditForm):
       
   446     """Portal template portlet edit form, AJAX renderer"""
       
   447 
       
   448     def __call__(self):
       
   449         request = self.request
       
   450         request.registry.notify(PageletCreatedEvent(self))
       
   451         slot_name = request.params.get('form.widgets.slot_name')
       
   452         position = int(request.params.get('form.widgets.position'))
       
   453         config = IPortalTemplateConfiguration(self.context)
       
   454         portlet_config = config.get_portlet_configuration(slot_name, position)
       
   455         if portlet_config is None:
       
   456             raise NotFound()
       
   457         editor = request.registry.queryMultiAdapter((portlet_config, request),
       
   458                                                     IPagelet, name='properties.json')
       
   459         if editor is None:
       
   460             raise NotFound()
       
   461         changes = editor()
       
   462         if changes:
       
   463             # we commit before loading previewer to avoid BLOBs "uncommited changes" error
       
   464             ITransactionManager(self.context).commit()
       
   465             previewer = request.registry.queryMultiAdapter((self.context, request, self, portlet_config),
       
   466                                                            IPortletPreviewer)
       
   467             if previewer is not None:
       
   468                 previewer.update()
       
   469                 changes.update({'status': 'callback',
       
   470                                 'callback': 'PyAMS_portal.template.editPortletCallback',
       
   471                                 'options': {'slot_name': slot_name,
       
   472                                             'position': position,
       
   473                                             'preview': previewer.render()}})
       
   474         return changes
       
   475 
       
   476 
       
   477 @view_config(name='delete-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
       
   478              permission='portal.templates.manage', renderer='json', xhr=True)
       
   479 def delete_template_portlet(request):
       
   480     """Delete template portlet"""
       
   481     config = IPortalTemplateConfiguration(request.context)
       
   482     config.delete_portlet(request.params.get('slot_name'), int(request.params.get('position')))
       
   483     return {'status': 'success'}