src/pyams_content/component/paragraph/zmi/milestone.py
changeset 456 07646760c1b5
child 487 093f201e3168
equal deleted inserted replaced
455:95582493a5ac 456:07646760c1b5
       
     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