|
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> <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 |