src/pyams_content/features/menu/zmi/__init__.py
changeset 635 ac6c59149f0d
child 673 e609ce907277
equal deleted inserted replaced
634:4e43b556a2a9 635:ac6c59149f0d
       
     1 #
       
     2 # Copyright (c) 2008-2018 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 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 import json
       
    18 
       
    19 # import interfaces
       
    20 from pyams_content.component.links.zmi import InternalLinkAddMenu, InternalLinkAddForm, InternalLinkPropertiesEditForm, \
       
    21     ExternalLinkAddMenu, ExternalLinkAddForm, ExternalLinkPropertiesEditForm
       
    22 from pyams_content.features.menu import IMenusContainer, IMenu, Menu, IMenuLink
       
    23 from pyams_content.features.menu.interfaces import IMenusContainerTarget, IMenuLinksContainer, IMenuInternalLink, \
       
    24     IMenuExternalLink, IMenuLinksContainerTarget
       
    25 from pyams_form.interfaces.form import IFormContextPermissionChecker
       
    26 from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
       
    27 from pyams_skin.interfaces.viewlet import IWidgetTitleViewletManager, IToolbarAddingMenu
       
    28 from pyams_skin.layer import IPyAMSLayer
       
    29 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
       
    30 from pyams_viewlet.interfaces import IViewletManager
       
    31 from z3c.table.interfaces import IValues, IColumn
       
    32 
       
    33 # import packages
       
    34 from pyams_content.component.association.zmi import AssociationsTable, AssociationsTablePublicNameColumn
       
    35 from pyams_form.form import ajax_config, AJAXAddForm, AJAXEditForm
       
    36 from pyams_i18n.column import I18nAttrColumn
       
    37 from pyams_pagelet.pagelet import pagelet_config
       
    38 from pyams_skin.container import switch_element_visibility, delete_container_element
       
    39 from pyams_skin.event import get_json_switched_table_refresh_event, get_json_table_row_refresh_event
       
    40 from pyams_skin.table import BaseTable, SorterColumn, VisibilitySwitcherColumn, I18nColumn, TrashColumn, NameColumn, \
       
    41     get_table_id
       
    42 from pyams_skin.viewlet.toolbar import ToolbarAction
       
    43 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextAdapter
       
    44 from pyams_utils.traversing import get_parent
       
    45 from pyams_utils.url import absolute_url
       
    46 from pyams_viewlet.viewlet import viewlet_config
       
    47 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
       
    48 from pyams_zmi.zmi.table import InnerTableView
       
    49 from pyramid.decorator import reify
       
    50 from pyramid.exceptions import NotFound
       
    51 from pyramid.view import view_config
       
    52 from z3c.form import field
       
    53 from zope.interface import implementer, alsoProvides, Interface
       
    54 
       
    55 from pyams_content import _
       
    56 
       
    57 
       
    58 #
       
    59 # Custom marker interfaces
       
    60 #
       
    61 
       
    62 class IMenuLinksView(Interface):
       
    63     """Menu links view marker interface"""
       
    64 
       
    65 
       
    66 class IMenusView(Interface):
       
    67     """Menus view marker interface"""
       
    68 
       
    69 
       
    70 #
       
    71 # Menus add and edit forms
       
    72 #
       
    73 
       
    74 @viewlet_config(name='add-menu.action', context=IMenusContainerTarget, layer=IPyAMSLayer,
       
    75                 view=IMenusView, manager=IWidgetTitleViewletManager, weight=10)
       
    76 class MenuAddAction(ToolbarAction):
       
    77     """Menu add action"""
       
    78 
       
    79     label = _("Add menu...")
       
    80     url = 'add-menu.html'
       
    81     modal_target = True
       
    82 
       
    83 
       
    84 @pagelet_config(name='add-menu.html', context=IMenusContainer, layer=IPyAMSLayer,
       
    85                 permission=MANAGE_TEMPLATE_PERMISSION)
       
    86 @ajax_config(name='add-menu.json', context=IMenusContainer, layer=IPyAMSLayer, base=AJAXAddForm)
       
    87 class MenuAddForm(AdminDialogAddForm):
       
    88     """Menu add form"""
       
    89 
       
    90     legend = _("Add new menu")
       
    91     icon_css_class = 'fa fa-fw fa-bars'
       
    92 
       
    93     fields = field.Fields(IMenu).select('title')
       
    94     edit_permission = MANAGE_TEMPLATE_PERMISSION
       
    95 
       
    96     def create(self, data):
       
    97         return Menu()
       
    98 
       
    99     def add(self, object):
       
   100         self.context.append(object)
       
   101 
       
   102     def get_ajax_output(self, changes):
       
   103         settings = get_parent(self.context, IMenusContainerTarget)
       
   104         view = self.request.registry.getMultiAdapter((self.context, self.request), IMenusView,
       
   105                                                      name=self.context.__name__)
       
   106         return {
       
   107             'status': 'success',
       
   108             'message': self.request.localizer.translate(_("Menu was correctly added.")),
       
   109             'events': [
       
   110                 get_json_switched_table_refresh_event(settings, self.request, view.table_class)
       
   111             ]
       
   112         }
       
   113 
       
   114 
       
   115 @pagelet_config(name='properties.html', context=IMenu, layer=IPyAMSLayer, permission=MANAGE_TEMPLATE_PERMISSION)
       
   116 @ajax_config(name='properties.json', context=IMenu, layer=IPyAMSLayer)
       
   117 class MenuPropertiesEditForm(AdminDialogEditForm):
       
   118     """Menu properties edit form"""
       
   119 
       
   120     legend = _("Edit menu properties")
       
   121     icon_css_class = 'fa fa-fw fa-bars'
       
   122 
       
   123     fields = field.Fields(IMenu).select('title')
       
   124     edit_permission = MANAGE_TEMPLATE_PERMISSION
       
   125 
       
   126     def get_ajax_output(self, changes):
       
   127         output = super(self.__class__, self).get_ajax_output(changes)
       
   128         if changes:
       
   129             settings = get_parent(self.context, IMenusContainerTarget)
       
   130             container = settings.menus
       
   131             view = self.request.registry.getMultiAdapter((container, self.request), IMenusView,
       
   132                                                          name=container.__name__)
       
   133             output.setdefault('events', []).append(
       
   134                 get_json_table_row_refresh_event(settings, self.request, view.table_class, self.context))
       
   135         return output
       
   136 
       
   137 
       
   138 #
       
   139 # Menus table views
       
   140 #
       
   141 
       
   142 class MenusTable(BaseTable):
       
   143     """Menus table"""
       
   144 
       
   145     prefix = 'menus'
       
   146     associations_name = 'menus'
       
   147 
       
   148     permission = MANAGE_TEMPLATE_PERMISSION
       
   149     hide_header = True
       
   150     hide_body_toolbar = True
       
   151     sortOn = None
       
   152 
       
   153     @property
       
   154     def cssClasses(self):
       
   155         classes = ['table', 'table-bordered', 'table-striped', 'table-hover', 'table-tight']
       
   156         permission = self.permission
       
   157         if (not permission) or self.request.has_permission(permission, self.context):
       
   158             classes.append('table-dnd')
       
   159         return {'table': ' '.join(classes)}
       
   160 
       
   161     @property
       
   162     def data_attributes(self):
       
   163         menus = getattr(self.context, self.associations_name)
       
   164         attributes = super(MenusTable, self).data_attributes
       
   165         attributes.setdefault('table', {}).update({
       
   166             'id': self.id,
       
   167             'data-ams-location': absolute_url(menus, self.request),
       
   168             'data-ams-tablednd-drag-handle': 'td.sorter',
       
   169             'data-ams-tablednd-drop-target': 'set-menus-order.json',
       
   170             'data-ams-visibility-switcher': 'switch-menu-visibility.json'
       
   171         })
       
   172         return attributes
       
   173 
       
   174 
       
   175 @adapter_config(context=(IMenusContainerTarget, IPyAMSLayer, MenusTable), provides=IValues)
       
   176 class MenusTableValuesAdapter(ContextRequestViewAdapter):
       
   177     """Menus table values adapter"""
       
   178 
       
   179     @property
       
   180     def values(self):
       
   181         return getattr(self.context, self.view.associations_name).values()
       
   182 
       
   183 
       
   184 @adapter_config(name='sorter', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable),
       
   185                 provides=IColumn)
       
   186 class MenusTableSorterColumn(SorterColumn):
       
   187     """Menus table sorter column"""
       
   188 
       
   189     permission = MANAGE_TEMPLATE_PERMISSION
       
   190 
       
   191 
       
   192 @adapter_config(name='show-hide', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable),
       
   193                 provides=IColumn)
       
   194 class MenusTableShowHideColumn(VisibilitySwitcherColumn):
       
   195     """Menus table visibility switcher column"""
       
   196 
       
   197     permission = MANAGE_TEMPLATE_PERMISSION
       
   198 
       
   199 
       
   200 @adapter_config(name='name', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable), provides=IColumn)
       
   201 class MenusTableNameColumn(I18nColumn, I18nAttrColumn):
       
   202     """Menus table name column"""
       
   203 
       
   204     _header = _("Label")
       
   205     attrName = 'title'
       
   206     weight = 10
       
   207 
       
   208     def renderCell(self, item):
       
   209         return '<span data-ams-stop-propagation="true" ' \
       
   210                '      data-ams-click-handler="MyAMS.skin.switchCellContent" ' \
       
   211                '      data-ams-switch-handler="get-menu-items.json" ' \
       
   212                '      data-ams-switch-target=".menus">' \
       
   213                '    <span class="small hint" title="{hint}" data-ams-hint-gravity="e">' \
       
   214                '        <i class="fa fa-plus-square-o switch"></i>' \
       
   215                '    </span>' \
       
   216                '</span>&nbsp;&nbsp;&nbsp;<span class="title">{title}</span>' \
       
   217                '<div class="inner-table-form menus margin-x-10 margin-bottom-0 padding-left-5"></div>'.format(
       
   218             hint=self.request.localizer.translate(_("Click to see menu items")),
       
   219             title=super(MenusTableNameColumn, self).renderCell(item))
       
   220 
       
   221 
       
   222 @adapter_config(name='trash', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable), provides=IColumn)
       
   223 class MenusTableTrashColumn(TrashColumn):
       
   224     """Menus table trash column"""
       
   225 
       
   226     permission = MANAGE_TEMPLATE_PERMISSION
       
   227 
       
   228 
       
   229 @implementer(IMenusView)
       
   230 class MenusView(InnerTableView):
       
   231     """Menus view"""
       
   232 
       
   233     table_class = MenusTable
       
   234 
       
   235     @property
       
   236     def actions_context(self):  # define context for internal actions
       
   237         return self.request.registry.getAdapter(self.context, IMenusContainer,
       
   238                                                 name=self.table.associations_name)
       
   239 
       
   240 
       
   241 #
       
   242 # Menus container views
       
   243 #
       
   244 
       
   245 @view_config(name='set-menus-order.json', context=IMenusContainer, request_type=IPyAMSLayer,
       
   246              permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
       
   247 def set_menus_order(request):
       
   248     """Update menus order"""
       
   249     order = list(map(str, json.loads(request.params.get('names'))))
       
   250     request.context.updateOrder(order)
       
   251     return {'status': 'success'}
       
   252 
       
   253 
       
   254 @view_config(name='switch-menu-visibility.json', context=IMenusContainer, request_type=IPyAMSLayer,
       
   255              permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
       
   256 def set_menu_visibility(request):
       
   257     """Set menu visibility"""
       
   258     return switch_element_visibility(request, IMenusContainer)
       
   259 
       
   260 
       
   261 @view_config(name='delete-element.json', context=IMenusContainer, request_type=IPyAMSLayer,
       
   262              permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
       
   263 def delete_menu(request):
       
   264     """Delete menu"""
       
   265     return delete_container_element(request, ignore_permission=True)
       
   266 
       
   267 
       
   268 @view_config(name='get-menu-items.json', context=IMenusContainer, request_type=IPyAMSLayer,
       
   269              permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
       
   270 def get_menu_items_table(request):
       
   271     """Get menu items table"""
       
   272     menu = request.context.get(str(request.params.get('object_name')))
       
   273     if menu is None:
       
   274         raise NotFound()
       
   275     table = MenuLinksTable(menu, request)
       
   276     table.update()
       
   277     return table.render()
       
   278 
       
   279 
       
   280 #
       
   281 # Menu links table
       
   282 #
       
   283 
       
   284 class LinksTable(AssociationsTable):
       
   285     """Links base table class"""
       
   286 
       
   287     associations_name = None
       
   288 
       
   289     @reify
       
   290     def id(self):
       
   291         if IMenu.providedBy(self.context):
       
   292             context = self.context
       
   293         else:
       
   294             context = self.request.registry.getAdapter(self.context, IMenuLinksContainer,
       
   295                                                        name=self.associations_name)
       
   296         return get_table_id(self, context)
       
   297 
       
   298     @property
       
   299     def prefix(self):
       
   300         return '{0}_links'.format(self.associations_name)
       
   301 
       
   302     permission = MANAGE_TEMPLATE_PERMISSION
       
   303     hide_header = True
       
   304     hide_body_toolbar = True
       
   305 
       
   306 
       
   307 class MenuLinksTable(LinksTable):
       
   308     """Menu links associations table"""
       
   309 
       
   310     prefix = 'menu_links'
       
   311     associations_name = ''
       
   312 
       
   313     @property
       
   314     def data_attributes(self):
       
   315         attributes = super(LinksTable, self).data_attributes
       
   316         attributes.setdefault('table', {}).update({
       
   317             'data-ams-location': absolute_url(self.context, self.request),
       
   318         })
       
   319         attributes.setdefault('tr', {}).update({'data-ams-stop-propagation': 'true'})
       
   320         return attributes
       
   321 
       
   322 
       
   323 @adapter_config(name='name', context=(IMenu, IPyAMSLayer, MenuLinksTable), provides=IColumn)
       
   324 class MenuLinksTableNameColumn(AssociationsTablePublicNameColumn):
       
   325     """Menu links table name column"""
       
   326 
       
   327     def renderHeadCell(self):
       
   328         result = super(MenuLinksTableNameColumn, self).renderHeadCell()
       
   329         registry = self.request.registry
       
   330         viewlet = registry.queryMultiAdapter((self.context, self.request, self.table), IViewletManager,
       
   331                                              name='pyams.widget_title')
       
   332         if viewlet is not None:
       
   333             viewlet.update()
       
   334             result += viewlet.render()
       
   335         return result
       
   336 
       
   337 
       
   338 @adapter_config(context=(IMenu, IPyAMSLayer), provides=IMenuLinksView)
       
   339 @implementer(IMenuLinksView)
       
   340 class MenuLinksView(InnerTableView):
       
   341     """Links base view"""
       
   342 
       
   343     table_class = MenuLinksTable
       
   344 
       
   345     @property
       
   346     def actions_context(self):  # define context for internal actions
       
   347         return self.request.registry.getAdapter(self.context, IMenuLinksContainer,
       
   348                                                 name=self.table.associations_name)
       
   349 
       
   350 
       
   351 #
       
   352 # Menu links container views
       
   353 #
       
   354 
       
   355 @view_config(name='set-associations-order.json', context=IMenuLinksContainer, request_type=IPyAMSLayer,
       
   356              permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
       
   357 def set_associations_order(request):
       
   358     """Update associations order"""
       
   359     order = list(map(str, json.loads(request.params.get('names'))))
       
   360     request.context.updateOrder(order)
       
   361     return {'status': 'success'}
       
   362 
       
   363 
       
   364 @view_config(name='switch-association-visibility.json', context=IMenuLinksContainer, request_type=IPyAMSLayer,
       
   365              permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
       
   366 def set_association_visibility(request):
       
   367     """Set association visibility"""
       
   368     return switch_element_visibility(request, IMenuLinksContainer)
       
   369 
       
   370 
       
   371 #
       
   372 # Link add and edit forms
       
   373 #
       
   374 
       
   375 @adapter_config(context=IMenuLink, provides=IFormContextPermissionChecker)
       
   376 class MenuLinkPermissionChecker(ContextAdapter):
       
   377     """Menu link permission checker"""
       
   378 
       
   379     edit_permission = MANAGE_TEMPLATE_PERMISSION
       
   380 
       
   381 
       
   382 class LinkAJAXAddForm(AJAXAddForm):
       
   383     """Menu link add form, JSON renderer"""
       
   384 
       
   385     def get_ajax_output(self, changes):
       
   386         registry = self.request.registry
       
   387         container = get_parent(self.context, IMenuLinksContainer)
       
   388         view = registry.queryMultiAdapter((container, self.request), IMenuLinksView,
       
   389                                           name=container.__name__)
       
   390         if view is None:
       
   391             view = registry.getMultiAdapter((container, self.request), IMenuLinksView)
       
   392         return {
       
   393             'status': 'success',
       
   394             'message': self.request.localizer.translate(_("Link was correctly added.")),
       
   395             'events': [
       
   396                 get_json_switched_table_refresh_event(self.context, self.request, view.table_class)
       
   397             ]
       
   398         }
       
   399 
       
   400 
       
   401 class LinkPropertiesAJAXEditForm(AJAXEditForm):
       
   402     """Menu link properties edit form, JSON renderer"""
       
   403 
       
   404     def get_ajax_output(self, changes):
       
   405         output = AJAXEditForm.get_ajax_output(self, changes)
       
   406         if changes:
       
   407             registry = self.request.registry
       
   408             container = get_parent(self.context, IMenuLinksContainer)
       
   409             view = registry.queryMultiAdapter((container, self.request), IMenuLinksView,
       
   410                                               name=container.__name__)
       
   411             if view is None:
       
   412                 view = registry.getMultiAdapter((container, self.request), IMenuLinksView)
       
   413             output.setdefault('events', []).append(
       
   414                 get_json_table_row_refresh_event(container, self.request, view.table_class, self.context))
       
   415         return output
       
   416 
       
   417 
       
   418 #
       
   419 # Internal links
       
   420 #
       
   421 
       
   422 @viewlet_config(name='add-internal-link.menu', context=IMenuLinksContainerTarget, layer=IPyAMSLayer,
       
   423                 view=IMenuLinksView, manager=IToolbarAddingMenu, weight=50)
       
   424 @viewlet_config(name='add-internal-link.menu', context=IMenu, layer=IPyAMSLayer,
       
   425                 view=MenuLinksTable, manager=IToolbarAddingMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=50)
       
   426 class MenuInternalLinkAddMenu(InternalLinkAddMenu):
       
   427     """Header internal link add menu"""
       
   428 
       
   429 
       
   430 @pagelet_config(name='add-internal-link.html', context=IMenuLinksContainer, layer=IPyAMSLayer,
       
   431                 permission=MANAGE_TEMPLATE_PERMISSION)
       
   432 @ajax_config(name='add-internal-link.json', context=IMenuLinksContainer, layer=IPyAMSLayer,
       
   433              base=LinkAJAXAddForm)
       
   434 class MenuInternalLinkAddForm(InternalLinkAddForm):
       
   435     """Menu internal link add form"""
       
   436 
       
   437     edit_permission = MANAGE_TEMPLATE_PERMISSION
       
   438 
       
   439     def create(self, data):
       
   440         result = super(MenuInternalLinkAddForm, self).create(data)
       
   441         alsoProvides(result, IMenuInternalLink)
       
   442         return result
       
   443 
       
   444     def add(self, object):
       
   445         self.context.append(object)
       
   446 
       
   447 
       
   448 @pagelet_config(name='properties.html', context=IMenuInternalLink, layer=IPyAMSLayer,
       
   449                 permission=VIEW_SYSTEM_PERMISSION)
       
   450 @ajax_config(name='properties.json', context=IMenuInternalLink, layer=IPyAMSLayer,
       
   451              base=LinkPropertiesAJAXEditForm)
       
   452 class MenuInternalLinkPropertiesEditForm(InternalLinkPropertiesEditForm):
       
   453     """Menu internal link properties edit form"""
       
   454 
       
   455     edit_permission = None  # managed by IFormContextPermissionChecker adapter
       
   456 
       
   457 
       
   458 #
       
   459 # External links
       
   460 #
       
   461 
       
   462 @viewlet_config(name='add-external-link.menu', context=IMenuLinksContainerTarget, view=IMenuLinksView,
       
   463                 layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=51)
       
   464 @viewlet_config(name='add-external-link.menu', context=IMenu, layer=IPyAMSLayer,
       
   465                 view=MenuLinksTable, manager=IToolbarAddingMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=51)
       
   466 class MenuExternalLinkAddMenu(ExternalLinkAddMenu):
       
   467     """Menu external link add menu"""
       
   468 
       
   469 
       
   470 @pagelet_config(name='add-external-link.html', context=IMenuLinksContainer, layer=IPyAMSLayer,
       
   471                 permission=MANAGE_TEMPLATE_PERMISSION)
       
   472 @ajax_config(name='add-external-link.json', context=IMenuLinksContainer, layer=IPyAMSLayer,
       
   473              base=LinkAJAXAddForm)
       
   474 class MenuExternalLinkAddForm(ExternalLinkAddForm):
       
   475     """Menu external link add form"""
       
   476 
       
   477     edit_permission = MANAGE_TEMPLATE_PERMISSION
       
   478 
       
   479     def create(self, data):
       
   480         result = super(MenuExternalLinkAddForm, self).create(data)
       
   481         alsoProvides(result, IMenuExternalLink)
       
   482         return result
       
   483 
       
   484     def add(self, object):
       
   485         self.context.append(object)
       
   486 
       
   487 
       
   488 @pagelet_config(name='properties.html', context=IMenuExternalLink, layer=IPyAMSLayer,
       
   489                 permission=VIEW_SYSTEM_PERMISSION)
       
   490 @ajax_config(name='properties.json', context=IMenuExternalLink, layer=IPyAMSLayer,
       
   491              base=LinkPropertiesAJAXEditForm)
       
   492 class MenuExternalLinkPropertiesEditForm(ExternalLinkPropertiesEditForm):
       
   493     """Menu external link properties edit form"""
       
   494 
       
   495     edit_permission = None  # managed by IFormContextPermissionChecker adapter