|
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 |
|
13 __docformat__ = 'restructuredtext' |
|
14 |
|
15 |
|
16 # import standard library |
|
17 import json |
|
18 |
|
19 # import interfaces |
|
20 from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer, \ |
|
21 IParagraphPreview |
|
22 from pyams_content.component.paragraph.interfaces.milestone import MILESTONE_PARAGRAPH_TYPE, IMilestoneParagraph, \ |
|
23 IMilestoneContainer, IMilestoneContainerTarget, IMilestone |
|
24 from pyams_content.component.paragraph.zmi import IParagraphContainerView |
|
25 from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor |
|
26 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION |
|
27 from pyams_content.shared.common import IWfSharedContent |
|
28 from pyams_form.interfaces.form import IInnerForm, IEditFormButtons, IInnerSubForm |
|
29 from pyams_i18n.interfaces import II18n |
|
30 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager |
|
31 from pyams_skin.layer import IPyAMSLayer |
|
32 from pyams_utils.interfaces import MANAGE_PERMISSION |
|
33 from z3c.form.interfaces import INPUT_MODE |
|
34 from z3c.table.interfaces import IValues, IColumn |
|
35 |
|
36 # import packages |
|
37 from pyams_content.component.paragraph.milestone import MilestoneParagraph, Milestone |
|
38 from pyams_content.component.paragraph.zmi import BaseParagraphAddMenu, BaseParagraphAJAXAddForm, \ |
|
39 BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm |
|
40 from pyams_content.features.renderer.zmi import BaseRenderedContentPreview |
|
41 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget |
|
42 from pyams_content.skin import pyams_content |
|
43 from pyams_form.form import AJAXAddForm, AJAXEditForm |
|
44 from pyams_form.security import ProtectedFormObjectMixin |
|
45 from pyams_i18n.column import I18nAttrColumn |
|
46 from pyams_pagelet.pagelet import pagelet_config |
|
47 from pyams_skin.table import BaseTable, SorterColumn, JsActionColumn, I18nColumn, TrashColumn |
|
48 from pyams_skin.viewlet.toolbar import ToolbarAction |
|
49 from pyams_template.template import template_config |
|
50 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter |
|
51 from pyams_utils.fanstatic import get_resource_path |
|
52 from pyams_utils.traversing import get_parent |
|
53 from pyams_utils.url import absolute_url |
|
54 from pyams_viewlet.viewlet import viewlet_config |
|
55 from pyams_zmi.form import AdminDialogAddForm, InnerAdminDisplayForm, AdminDialogEditForm |
|
56 from pyramid.decorator import reify |
|
57 from pyramid.exceptions import NotFound |
|
58 from pyramid.view import view_config |
|
59 from z3c.form import field, button |
|
60 from z3c.table.column import GetAttrColumn |
|
61 from zope.interface import implementer, Interface |
|
62 |
|
63 from pyams_content import _ |
|
64 |
|
65 |
|
66 class IMilestonesView(Interface): |
|
67 """Milestones view marker interface""" |
|
68 |
|
69 |
|
70 class IMilestonesParentForm(Interface): |
|
71 """Milestones parent form marker interface""" |
|
72 |
|
73 |
|
74 @viewlet_config(name='add-milestone-paragraph.menu', context=IParagraphContainerTarget, view=IParagraphContainerView, |
|
75 layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=590) |
|
76 class MilestoneParagraphAddMenu(BaseParagraphAddMenu): |
|
77 """Milestone paragraph add menu""" |
|
78 |
|
79 label = _("Milestones...") |
|
80 label_css_class = 'fa fa-fw fa-arrows-h' |
|
81 url = 'add-milestone-paragraph.html' |
|
82 paragraph_type = MILESTONE_PARAGRAPH_TYPE |
|
83 |
|
84 |
|
85 @pagelet_config(name='add-milestone-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer, |
|
86 permission=MANAGE_CONTENT_PERMISSION) |
|
87 class MilestoneParagraphAddForm(AdminDialogAddForm): |
|
88 """Milestone paragraph add form""" |
|
89 |
|
90 legend = _("Add new milestone paragraph") |
|
91 icon_css_class = 'fa fa-fw fa-arrows-h' |
|
92 |
|
93 fields = field.Fields(IMilestoneParagraph).select('title', 'renderer') |
|
94 ajax_handler = 'add-milestone-paragraph.json' |
|
95 edit_permission = MANAGE_CONTENT_PERMISSION |
|
96 |
|
97 def create(self, data): |
|
98 return MilestoneParagraph() |
|
99 |
|
100 def add(self, object): |
|
101 IParagraphContainer(self.context).append(object) |
|
102 |
|
103 |
|
104 @view_config(name='add-milestone-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer, |
|
105 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
106 class MilestoneParagraphAJAXAddForm(BaseParagraphAJAXAddForm, MilestoneParagraphAddForm): |
|
107 """Milestone paragraph add form, JSON renderer""" |
|
108 |
|
109 |
|
110 @pagelet_config(name='properties.html', context=IMilestoneParagraph, layer=IPyAMSLayer, |
|
111 permission=MANAGE_CONTENT_PERMISSION) |
|
112 class MilestoneParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm): |
|
113 """Milestone paragraph properties edit form""" |
|
114 |
|
115 @property |
|
116 def title(self): |
|
117 content = get_parent(self.context, IWfSharedContent) |
|
118 return II18n(content).query_attribute('title', request=self.request) |
|
119 |
|
120 legend = _("Edit milestone paragraph properties") |
|
121 icon_css_class = 'fa fa-fw fa-arrows-h' |
|
122 |
|
123 fields = field.Fields(IMilestoneParagraph).select('title', 'renderer') |
|
124 fields['renderer'].widgetFactory = RendererFieldWidget |
|
125 |
|
126 ajax_handler = 'properties.json' |
|
127 edit_permission = MANAGE_CONTENT_PERMISSION |
|
128 |
|
129 |
|
130 @view_config(name='properties.json', context=IMilestoneParagraph, request_type=IPyAMSLayer, |
|
131 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
132 class MilestoneParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, MilestoneParagraphPropertiesEditForm): |
|
133 """Milestone paragraph properties edit form, JSON renderer""" |
|
134 |
|
135 |
|
136 @adapter_config(context=(IMilestoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor) |
|
137 @implementer(IInnerForm, IMilestonesParentForm) |
|
138 class MilestoneParagraphInnerEditForm(MilestoneParagraphPropertiesEditForm): |
|
139 """Milestone paragraph inner edit form""" |
|
140 |
|
141 legend = None |
|
142 ajax_handler = 'inner-properties.json' |
|
143 |
|
144 @property |
|
145 def buttons(self): |
|
146 if self.mode == INPUT_MODE: |
|
147 return button.Buttons(IEditFormButtons) |
|
148 else: |
|
149 return button.Buttons() |
|
150 |
|
151 |
|
152 @view_config(name='inner-properties.json', context=IMilestoneParagraph, request_type=IPyAMSLayer, |
|
153 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
154 class MilestoneParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, MilestoneParagraphInnerEditForm): |
|
155 """Milestones paragraph inner edit form, JSON renderer""" |
|
156 |
|
157 def get_ajax_output(self, changes): |
|
158 output = super(MilestoneParagraphInnerAJAXEditForm, self).get_ajax_output(changes) |
|
159 updated = changes.get(IMilestoneParagraph, ()) |
|
160 if 'renderer' in updated: |
|
161 form = MilestoneParagraphInnerEditForm(self.context, self.request) |
|
162 form.update() |
|
163 output.setdefault('events', []).append({ |
|
164 'event': 'myams.refresh', |
|
165 'options': { |
|
166 'object_id': '{0}_{1}_{2}'.format( |
|
167 self.context.__class__.__name__, |
|
168 getattr(form.getContent(), '__name__', 'noname').replace('++', ''), |
|
169 form.id), |
|
170 'content': form.render() |
|
171 } |
|
172 }) |
|
173 return output |
|
174 |
|
175 |
|
176 # |
|
177 # Milestone paragraph preview |
|
178 # |
|
179 |
|
180 @adapter_config(context=(IMilestoneParagraph, IPyAMSLayer), provides=IParagraphPreview) |
|
181 class MilestoneParagraphPreview(BaseRenderedContentPreview): |
|
182 """Milestone paragraph preview""" |
|
183 |
|
184 |
|
185 # |
|
186 # Milestone items table view |
|
187 # |
|
188 |
|
189 class MilestonesTable(ProtectedFormObjectMixin, BaseTable): |
|
190 """Milestones view inner table""" |
|
191 |
|
192 @property |
|
193 def id(self): |
|
194 return 'milestones_{0}_list'.format(self.context.__name__) |
|
195 |
|
196 hide_header = True |
|
197 sortOn = None |
|
198 |
|
199 @property |
|
200 def cssClasses(self): |
|
201 classes = ['table', 'table-bordered', 'table-striped', 'table-hover', 'table-tight'] |
|
202 permission = self.permission |
|
203 if (not permission) or self.request.has_permission(permission, self.context): |
|
204 classes.append('table-dnd') |
|
205 return {'table': ' '.join(classes)} |
|
206 |
|
207 @property |
|
208 def data_attributes(self): |
|
209 attributes = super(MilestonesTable, self).data_attributes |
|
210 attributes['table'] = { |
|
211 'id': self.id, |
|
212 'data-ams-plugins': 'pyams_content', |
|
213 'data-ams-plugin-pyams_content-src': get_resource_path(pyams_content), |
|
214 'data-ams-location': absolute_url(IMilestoneContainer(self.context), self.request), |
|
215 'data-ams-tablednd-drag-handle': 'td.sorter', |
|
216 'data-ams-tablednd-drop-target': 'set-milestones-order.json' |
|
217 } |
|
218 attributes.setdefault('tr', {}).update({ |
|
219 'id': lambda x, col: 'milestone_{0}::{1}'.format(get_parent(x, IMilestoneContainerTarget).__name__, |
|
220 x.__name__), |
|
221 'data-ams-delete-target': 'delete-milestone.json' |
|
222 }) |
|
223 return attributes |
|
224 |
|
225 @reify |
|
226 def values(self): |
|
227 return list(super(MilestonesTable, self).values) |
|
228 |
|
229 |
|
230 @adapter_config(context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), provides=IValues) |
|
231 class MilestonesTableValuesAdapter(ContextRequestViewAdapter): |
|
232 """Milestones table values adapter""" |
|
233 |
|
234 @property |
|
235 def values(self): |
|
236 return IMilestoneContainer(self.context).values() |
|
237 |
|
238 |
|
239 @adapter_config(name='sorter', context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), provides=IColumn) |
|
240 class MilestonesTableSorterColumn(ProtectedFormObjectMixin, SorterColumn): |
|
241 """Milestones table sorter column""" |
|
242 |
|
243 |
|
244 @view_config(name='set-milestones-order.json', context=IMilestoneContainer, request_type=IPyAMSLayer, |
|
245 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
246 def set_milestones_order(request): |
|
247 """Update milestones order""" |
|
248 order = list(map(str, json.loads(request.params.get('names')))) |
|
249 request.context.updateOrder(order) |
|
250 return {'status': 'success'} |
|
251 |
|
252 |
|
253 @adapter_config(name='show-hide', context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), |
|
254 provides=IColumn) |
|
255 class MilestonesTableShowHideColumn(ProtectedFormObjectMixin, JsActionColumn): |
|
256 """Milestones container visibility switcher column""" |
|
257 |
|
258 cssClasses = {'th': 'action', |
|
259 'td': 'action switcher'} |
|
260 |
|
261 icon_class = 'fa fa-fw fa-eye' |
|
262 icon_hint = _("Switch milestone visibility") |
|
263 |
|
264 url = 'PyAMS_content.milestones.switchVisibility' |
|
265 |
|
266 weight = 5 |
|
267 |
|
268 def get_icon(self, item): |
|
269 if item.visible: |
|
270 icon_class = 'fa fa-fw fa-eye' |
|
271 else: |
|
272 icon_class = 'fa fa-fw fa-eye-slash text-danger' |
|
273 return '<i class="{icon_class}"></i>'.format(icon_class=icon_class) |
|
274 |
|
275 def renderCell(self, item): |
|
276 if self.permission and not self.request.has_permission(self.permission, context=item): |
|
277 return self.get_icon(item) |
|
278 else: |
|
279 return super(MilestonesTableShowHideColumn, self).renderCell(item) |
|
280 |
|
281 |
|
282 @view_config(name='set-milestone-visibility.json', context=IMilestoneContainer, request_type=IPyAMSLayer, |
|
283 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
284 def set_milestone_visibility(request): |
|
285 """Set milestone visibility""" |
|
286 container = IMilestoneContainer(request.context) |
|
287 milestone = container.get(str(request.params.get('object_name'))) |
|
288 if milestone is None: |
|
289 raise NotFound() |
|
290 milestone.visible = not milestone.visible |
|
291 return {'visible': milestone.visible} |
|
292 |
|
293 |
|
294 @adapter_config(name='name', context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), provides=IColumn) |
|
295 class MilestonesTableNameColumn(I18nColumn, I18nAttrColumn): |
|
296 """Milestones table name column""" |
|
297 |
|
298 _header = _("Title") |
|
299 attrName = 'title' |
|
300 weight = 10 |
|
301 |
|
302 |
|
303 @adapter_config(name='info', context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), provides=IColumn) |
|
304 class MilestonesTableInfoColumn(I18nColumn, I18nAttrColumn): |
|
305 """Milestones table information column""" |
|
306 |
|
307 _header = _("Associated label") |
|
308 attrName = 'label' |
|
309 weight = 20 |
|
310 |
|
311 |
|
312 @adapter_config(name='anchor', context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), provides=IColumn) |
|
313 class MilestonesTableAnchorColumn(I18nColumn, GetAttrColumn): |
|
314 """Milestones table anchor column""" |
|
315 |
|
316 _header = _("Anchor") |
|
317 weight = 30 |
|
318 |
|
319 def getValue(self, obj): |
|
320 if not obj.anchor: |
|
321 return '--' |
|
322 target = get_parent(self.context, IParagraphContainerTarget) |
|
323 if target is None: |
|
324 return '--' |
|
325 paragraph= IParagraphContainer(target).get(obj.anchor) |
|
326 if paragraph is None: |
|
327 return '--' |
|
328 return II18n(paragraph).query_attribute('title', request=self.request) or '--' |
|
329 |
|
330 |
|
331 @adapter_config(name='trash', context=(IMilestoneContainerTarget, IPyAMSLayer, MilestonesTable), provides=IColumn) |
|
332 class MilestonesTableTrashColumn(ProtectedFormObjectMixin, TrashColumn): |
|
333 """Milestones table trash column""" |
|
334 |
|
335 |
|
336 @view_config(name='delete-milestone.json', context=IMilestoneContainer, request_type=IPyAMSLayer, |
|
337 permission=MANAGE_PERMISSION, renderer='json', xhr=True) |
|
338 def delete_milestone(request): |
|
339 """Delete milestone""" |
|
340 translate = request.localizer.translate |
|
341 name = request.params.get('object_name') |
|
342 if not name: |
|
343 return { |
|
344 'status': 'message', |
|
345 'messagebox': { |
|
346 'status': 'error', |
|
347 'content': translate(_("No provided object_name argument!")) |
|
348 } |
|
349 } |
|
350 if name not in request.context: |
|
351 return { |
|
352 'status': 'message', |
|
353 'messagebox': { |
|
354 'status': 'error', |
|
355 'content': translate(_("Given association name doesn't exist!")) |
|
356 } |
|
357 } |
|
358 del request.context[name] |
|
359 return {'status': 'success'} |
|
360 |
|
361 |
|
362 @adapter_config(name='milestones', context=(IMilestoneContainerTarget, IPyAMSLayer, IMilestonesParentForm), |
|
363 provides=IInnerSubForm) |
|
364 @template_config(template='templates/milestones.pt', layer=IPyAMSLayer) |
|
365 @implementer(IMilestonesView) |
|
366 class MilestonesView(InnerAdminDisplayForm): |
|
367 """Milestones view""" |
|
368 |
|
369 fields = field.Fields(Interface) |
|
370 weight = 100 |
|
371 |
|
372 def __init__(self, context, request, view): |
|
373 super(MilestonesView, self).__init__(context, request, view) |
|
374 self.table = MilestonesTable(context, request) |
|
375 self.table.view = self |
|
376 |
|
377 def update(self): |
|
378 super(MilestonesView, self).update() |
|
379 self.table.update() |
|
380 |
|
381 |
|
382 # |
|
383 # Milestones forms |
|
384 # |
|
385 |
|
386 @viewlet_config(name='add-milestone.action', context=IMilestoneContainerTarget, layer=IPyAMSLayer, view=IMilestonesView, |
|
387 manager=IWidgetTitleViewletManager, permission=MANAGE_CONTENT_PERMISSION, weight=1) |
|
388 class MilestoneAddAction(ToolbarAction): |
|
389 """Milestone add action""" |
|
390 |
|
391 label = _("Add milestone") |
|
392 label_css_class = 'fa fa-fw fa-plus' |
|
393 url = 'add-milestone.html' |
|
394 modal_target = True |
|
395 |
|
396 |
|
397 @pagelet_config(name='add-milestone.html', context=IMilestoneContainerTarget, layer=IPyAMSLayer, |
|
398 permission=MANAGE_CONTENT_PERMISSION) |
|
399 class MilestoneAddForm(AdminDialogAddForm): |
|
400 """Milestone add form""" |
|
401 |
|
402 legend = _("Add new milestone") |
|
403 icon_css_class = 'fa fa-fw fa-arrow-h' |
|
404 |
|
405 fields = field.Fields(IMilestone).omit('__parent__', '__name__', 'visible') |
|
406 ajax_handler = 'add-milestone.json' |
|
407 edit_permission = MANAGE_CONTENT_PERMISSION |
|
408 |
|
409 def create(self, data): |
|
410 return Milestone() |
|
411 |
|
412 def add(self, object): |
|
413 IMilestoneContainer(self.context).append(object) |
|
414 |
|
415 |
|
416 @view_config(name='add-milestone.json', context=IMilestoneContainerTarget, request_type=IPyAMSLayer, |
|
417 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
418 class MilestoneAJAXAddForm(AJAXAddForm, MilestoneAddForm): |
|
419 """Milestone add form, JSON renderer""" |
|
420 |
|
421 def get_ajax_output(self, changes): |
|
422 table = MilestonesTable(self.context, self.request) |
|
423 table.update() |
|
424 return { |
|
425 'status': 'success', |
|
426 'message': self.request.localizer.translate(_("Milestone was correctly added")), |
|
427 'events': [{ |
|
428 'event': 'myams.refresh', |
|
429 'options': { |
|
430 'handler': 'PyAMS_content.milestones.refreshMilestones', |
|
431 'object_id': table.id, |
|
432 'table': table.render() |
|
433 } |
|
434 }] |
|
435 } |
|
436 |
|
437 |
|
438 @pagelet_config(name='properties.html', context=IMilestone, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION) |
|
439 class MilestonePropertiesEditForm(AdminDialogEditForm): |
|
440 """Milestone properties edit form""" |
|
441 |
|
442 legend = _("Edit milestone properties") |
|
443 icon_css_class = 'fa fa-fw fa-arrows-h' |
|
444 |
|
445 fields = field.Fields(IMilestone).omit('__parent__', '__name__', 'visible') |
|
446 ajax_handler = 'properties.json' |
|
447 edit_permission = MANAGE_CONTENT_PERMISSION |
|
448 |
|
449 |
|
450 @view_config(name='properties.json', context=IMilestone, request_type=IPyAMSLayer, |
|
451 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
452 class MilestonePropertiesAJAXEditForm(AJAXEditForm, MilestonePropertiesEditForm): |
|
453 """Milestone properties edit form, JSON renderer""" |
|
454 |
|
455 def get_ajax_output(self, changes): |
|
456 output = super(MilestonePropertiesAJAXEditForm, self).get_ajax_output(changes) |
|
457 updated = changes.get(IMilestone, ()) |
|
458 if ('title' in updated) or ('anchor' in updated): |
|
459 target = get_parent(self.context, IMilestoneContainerTarget) |
|
460 table = MilestonesTable(target, self.request) |
|
461 table.update() |
|
462 row = table.setUpRow(self.context) |
|
463 output.setdefault('events', []).append({ |
|
464 'event': 'myams.refresh', |
|
465 'options': { |
|
466 'handler': 'MyAMS.skin.refreshRow', |
|
467 'object_id': 'milestone_{0}::{1}'.format(target.__name__, |
|
468 self.context.__name__), |
|
469 'row': table.renderRow(row) |
|
470 } |
|
471 }) |
|
472 return output |