src/pyams_content/shared/common/zmi/__init__.py
changeset 0 7c0001cacf8e
child 14 234db8f05928
equal deleted inserted replaced
-1:000000000000 0:7c0001cacf8e
       
     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 from datetime import datetime
       
    18 from uuid import uuid4
       
    19 
       
    20 # import interfaces
       
    21 from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, MANAGE_CONTENT_PERMISSION, CREATE_CONTENT_PERMISSION, \
       
    22     PUBLISH_CONTENT_PERMISSION
       
    23 from pyams_content.shared.common.interfaces import IWfSharedContent, ISharedContent, ISharedTool, IManagerRestrictions
       
    24 from pyams_form.interfaces.form import IFormContextPermissionChecker, IWidgetsPrefixViewletsManager
       
    25 from pyams_i18n.interfaces import II18n, II18nManager
       
    26 from pyams_sequence.interfaces import ISequentialIntIds, ISequentialIdInfo
       
    27 from pyams_skin.interfaces import IContentTitle
       
    28 from pyams_skin.interfaces.container import ITable, ITableElementEditor
       
    29 from pyams_skin.interfaces.viewlet import IContextActions, IMenuHeader
       
    30 from pyams_skin.layer import IPyAMSLayer
       
    31 from pyams_utils.interfaces import FORBIDDEN_PERMISSION
       
    32 from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowInfo, IWorkflowState, IWorkflowCommentInfo, IWorkflow
       
    33 from pyams_zmi.interfaces.menu import ISiteManagementMenu
       
    34 from zope.dublincore.interfaces import IZopeDublinCore
       
    35 
       
    36 # import packages
       
    37 from pyams_form.form import AJAXAddForm
       
    38 from pyams_form.schema import CloseButton
       
    39 from pyams_pagelet.pagelet import pagelet_config
       
    40 from pyams_skin.page import DefaultPageHeaderAdapter
       
    41 from pyams_skin.table import DefaultElementEditorAdapter
       
    42 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
       
    43 from pyams_template.template import template_config
       
    44 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextAdapter, ContextRequestAdapter
       
    45 from pyams_utils.registry import get_utility
       
    46 from pyams_utils.request import check_request
       
    47 from pyams_utils.traversing import get_parent
       
    48 from pyams_utils.url import absolute_url
       
    49 from pyams_viewlet.viewlet import viewlet_config, Viewlet
       
    50 from pyams_workflow.versions import WorkflowHistoryItem
       
    51 from pyams_zmi.form import AdminDialogAddForm
       
    52 from pyramid.view import view_config
       
    53 from z3c.form import field, button
       
    54 from zope.copy import copy
       
    55 from zope.interface import Interface
       
    56 from zope.lifecycleevent import ObjectCreatedEvent
       
    57 from zope.location import locate
       
    58 
       
    59 from pyams_content import _
       
    60 
       
    61 
       
    62 class SharedContentAddForm(AdminDialogAddForm):
       
    63     """Shared content add form"""
       
    64 
       
    65     @property
       
    66     def title(self):
       
    67         return II18n(self.context).query_attribute('title', request=self.request)
       
    68 
       
    69     icon_css_class = 'fa fa-fw fa-plus'
       
    70     fields = field.Fields(IWfSharedContent).select('title', 'description')
       
    71 
       
    72     ajax_handler = 'add-shared-content.json'
       
    73     edit_permission = CREATE_CONTENT_PERMISSION
       
    74 
       
    75     def updateWidgets(self, prefix=None):
       
    76         super(SharedContentAddForm, self).updateWidgets(prefix)
       
    77         self.widgets['description'].label_css_class = 'textarea'
       
    78 
       
    79     def create(self, data):
       
    80         return self.context.shared_content_factory.content_class()
       
    81 
       
    82     def update_content(self, content, data):
       
    83         # generic content update
       
    84         changes = super(SharedContentAddForm, self).update_content(content, data)
       
    85         content.creator = self.request.principal.id
       
    86         content.owner = self.request.principal.id
       
    87         content.short_name = content.title.copy()
       
    88         # init content languages
       
    89         languages = II18nManager(self.context).languages
       
    90         if languages:
       
    91             II18nManager(content).languages = languages.copy()
       
    92         return changes
       
    93 
       
    94     def add(self, wf_content):
       
    95         content = self.context.shared_content_factory()
       
    96         self.request.registry.notify(ObjectCreatedEvent(content))
       
    97         uuid = self.__uuid = str(uuid4())
       
    98         self.context[uuid] = content
       
    99         IWorkflowVersions(content).add_version(wf_content, None)
       
   100         IWorkflowInfo(wf_content).fire_transition('init')
       
   101 
       
   102     def nextURL(self):
       
   103         return absolute_url(self.context, self.request, '{0}/++versions++/1/@@admin.html'.format(self.__uuid))
       
   104 
       
   105 
       
   106 class SharedContentAJAXAddForm(AJAXAddForm):
       
   107     """Shared event add form, JSON renderer"""
       
   108 
       
   109     def get_ajax_output(self, changes):
       
   110         return {'status': 'redirect',
       
   111                 'location': self.nextURL()}
       
   112 
       
   113 
       
   114 @viewlet_config(name='wf-create-message', context=Interface, layer=IPyAMSLayer, view=SharedContentAddForm,
       
   115                 manager=IWidgetsPrefixViewletsManager, weight=20)
       
   116 @template_config(template='templates/wf-create-message.pt')
       
   117 class SharedContentAddFormMessage(Viewlet):
       
   118     """Shared content add form info message"""
       
   119 
       
   120 
       
   121 #
       
   122 # Edit adapters and views
       
   123 #
       
   124 
       
   125 @adapter_config(context=IWfSharedContent, provides=IFormContextPermissionChecker)
       
   126 class WfSharedContentPermissionChecker(ContextAdapter):
       
   127     """Shared content form permission checker"""
       
   128 
       
   129     @property
       
   130     def edit_permission(self):
       
   131         workflow = IWorkflow(self.context)
       
   132         state = IWorkflowState(self.context).state
       
   133         if state in workflow.readonly_states:  # access forbidden to all for archived contents
       
   134             return FORBIDDEN_PERMISSION
       
   135         elif state in workflow.protected_states:  # webmaster can update published contents
       
   136             return MANAGE_SITE_ROOT_PERMISSION
       
   137         else:
       
   138             request = check_request()
       
   139             if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, self.context):  # webmaster access
       
   140                 return MANAGE_SITE_ROOT_PERMISSION
       
   141             if state in workflow.manager_states:  # restricted manager access
       
   142                 if request.principal.id in self.context.managers:
       
   143                     return PUBLISH_CONTENT_PERMISSION
       
   144                 restrictions = IManagerRestrictions(self.context).get_restrictions(request.principal)
       
   145                 if restrictions and restrictions.check_access(self.context,
       
   146                                                               permission=PUBLISH_CONTENT_PERMISSION,
       
   147                                                               request=request):
       
   148                     return PUBLISH_CONTENT_PERMISSION
       
   149             else:
       
   150                 if request.principal.id in self.context.owner | self.context.contributors | self.context.managers:
       
   151                     return MANAGE_CONTENT_PERMISSION
       
   152                 restrictions = IManagerRestrictions(self.context).get_restrictions(request.principal)
       
   153                 if restrictions and restrictions.check_access(self.context,
       
   154                                                               permission=MANAGE_CONTENT_PERMISSION,
       
   155                                                               request=request):
       
   156                     return MANAGE_CONTENT_PERMISSION
       
   157         return FORBIDDEN_PERMISSION
       
   158 
       
   159 
       
   160 class WfSharedContentPermissionMixin(object):
       
   161     """Shared content permission checker"""
       
   162 
       
   163     @property
       
   164     def permission(self):
       
   165         content = get_parent(self.context, IWfSharedContent)
       
   166         if content is not None:
       
   167             return IFormContextPermissionChecker(content).edit_permission
       
   168 
       
   169 
       
   170 @adapter_config(context=(IWfSharedContent, ISiteManagementMenu), provides=IMenuHeader)
       
   171 class WfSharedContentSiteManagementMenuHeader(ContextRequestAdapter):
       
   172     """Shared content site management menu header adapter"""
       
   173 
       
   174     header = _("Manage this content")
       
   175 
       
   176 
       
   177 @adapter_config(context=(IWfSharedContent, IPyAMSLayer, Interface), provides=IContentTitle)
       
   178 class WfSharedContentTitleAdapter(ContextRequestViewAdapter):
       
   179     """Shared content title adapter"""
       
   180 
       
   181     @property
       
   182     def title(self):
       
   183         return II18n(self.context).query_attribute('title', request=self.request)
       
   184 
       
   185 
       
   186 class WfSharedContentHeaderAdapter(DefaultPageHeaderAdapter):
       
   187     """Shared content header adapter"""
       
   188 
       
   189     @property
       
   190     def back_url(self):
       
   191         shared_tool = get_parent(self.context, ISharedTool)
       
   192         return absolute_url(shared_tool, self.request, 'admin.html#dashboard.html')
       
   193 
       
   194     back_target = None
       
   195     icon_class = 'fa fa-fw fa-edit'
       
   196 
       
   197 
       
   198 @adapter_config(context=(IWfSharedContent, IPyAMSLayer, ITable), provides=ITableElementEditor)
       
   199 class WfSharedContentElementEditor(DefaultElementEditorAdapter):
       
   200     """Shared content element editor"""
       
   201 
       
   202     view_name = 'admin.html'
       
   203     modal_target = False
       
   204 
       
   205 
       
   206 #
       
   207 # Duplication menus and views
       
   208 #
       
   209 
       
   210 @viewlet_config(name='duplication.menu', context=IWfSharedContent, layer=IPyAMSLayer,
       
   211                 view=Interface, manager=IContextActions, permission=CREATE_CONTENT_PERMISSION, weight=1)
       
   212 class WfSharedContentDuplicateMenu(ToolbarMenuItem):
       
   213     """Shared content duplication menu item"""
       
   214 
       
   215     label = _("Duplicate content...")
       
   216     label_css_class = 'fa fa-fw fa-files-o'
       
   217 
       
   218     url = 'duplicate.html'
       
   219     modal_target = True
       
   220 
       
   221 
       
   222 class ISharedContentDuplicateButtons(Interface):
       
   223     """Shared content duplication form buttons"""
       
   224 
       
   225     close = CloseButton(name='close', title=_("Cancel"))
       
   226     duplicate = button.Button(name='duplicate', title=_("Duplicate content"))
       
   227 
       
   228 
       
   229 @pagelet_config(name='duplicate.html', context=IWfSharedContent, layer=IPyAMSLayer,
       
   230                 permission=CREATE_CONTENT_PERMISSION)
       
   231 class WfSharedContentDuplicateForm(AdminDialogAddForm):
       
   232     """Shared content duplicate form"""
       
   233 
       
   234     legend = _("Duplicate content")
       
   235     fields = field.Fields(IWorkflowCommentInfo)
       
   236     buttons = button.Buttons(ISharedContentDuplicateButtons)
       
   237 
       
   238     ajax_handler = 'duplicate.json'
       
   239     edit_permission = CREATE_CONTENT_PERMISSION
       
   240 
       
   241     def updateWidgets(self, prefix=None):
       
   242         super(WfSharedContentDuplicateForm, self).updateWidgets(prefix)
       
   243         self.widgets['comment'].label_css_class = 'textarea'
       
   244 
       
   245     def updateActions(self):
       
   246         super(WfSharedContentDuplicateForm, self).updateActions()
       
   247         if 'duplicate' in self.actions:
       
   248             self.actions['duplicate'].addClass('btn-primary')
       
   249 
       
   250     def createAndAdd(self, data):
       
   251         registry = self.request.registry
       
   252         # initialize new content
       
   253         content = get_parent(self.context, ISharedContent)
       
   254         new_content = content.__class__()
       
   255         registry.notify(ObjectCreatedEvent(new_content))
       
   256         container = get_parent(content, ISharedTool)
       
   257         container[str(uuid4())] = new_content
       
   258         # initialize new version
       
   259         new_version = copy(self.context)
       
   260         registry.notify(ObjectCreatedEvent(new_version))
       
   261         locate(new_version, self.context.__parent__)  # locate new version for traversing to work...
       
   262         new_version.creator = self.request.principal.id
       
   263         new_version.owner = self.request.principal.id
       
   264         new_version.modifiers = set()
       
   265         # store new version
       
   266         translate = self.request.localizer.translate
       
   267         workflow = get_utility(IWorkflow, name=new_content.workflow_name)
       
   268         sequence = get_utility(ISequentialIntIds, name=new_content.sequence_name)
       
   269         IWorkflowVersions(new_content).add_version(new_version, workflow.initial_state)
       
   270         history = WorkflowHistoryItem(date=datetime.utcnow(),
       
   271                                       source_state=translate(workflow.states.getTerm(IWorkflowState(
       
   272                                           self.context).state).title),
       
   273                                       transition=translate(_("Duplicate content ({oid})")).format(
       
   274                                           oid=sequence.get_short_oid(ISequentialIdInfo(content).oid,
       
   275                                                                      content.sequence_prefix)),
       
   276                                       target_state=translate(workflow.states.getTerm(workflow.initial_state).title),
       
   277                                       principal=self.request.principal.id,
       
   278                                       comment=data.get('comment'))
       
   279         state = IWorkflowState(new_version)
       
   280         state.history.clear()
       
   281         state.history.append(history)
       
   282         return new_version
       
   283 
       
   284 
       
   285 @view_config(name='duplicate.json', context=IWfSharedContent, request_type=IPyAMSLayer,
       
   286              permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
       
   287 class WfSharedContentDuplicateAJAXForm(AJAXAddForm, WfSharedContentDuplicateForm):
       
   288     """Shared content duplicate form, JSON renderer"""
       
   289 
       
   290     def get_ajax_output(self, changes):
       
   291         return {'status': 'redirect',
       
   292                 'location': absolute_url(changes, self.request, 'admin.html')}
       
   293 
       
   294 
       
   295 @viewlet_config(name='wf-duplicate-message', context=IWfSharedContent, layer=IPyAMSLayer,
       
   296                 view=WfSharedContentDuplicateForm, manager=IWidgetsPrefixViewletsManager, weight=20)
       
   297 @template_config(template='templates/wf-duplicate-message.pt')
       
   298 class WfSharedContentDuplicateFormMessage(Viewlet):
       
   299     """Shared content add form info message"""
       
   300 
       
   301 
       
   302 #
       
   303 # Custom columns mixins
       
   304 #
       
   305 
       
   306 class WfModifiedContentColumnMixin(object):
       
   307     """Shared content modified column mixin"""
       
   308 
       
   309     def renderCell(self, item):
       
   310         value = self.getValue(item)
       
   311         content = get_parent(item, IWfSharedContent)
       
   312         if content is not None:
       
   313             if IWorkflowState(content).version_id > 1:
       
   314                 item_dc = IZopeDublinCore(item)
       
   315                 if item_dc.modified and (item_dc.modified > IZopeDublinCore(content).created):
       
   316                     translate = self.request.localizer.translate
       
   317                     value += '<i class="fa fa-fw fa-circle txt-color-orange pull-right hint" title="{0}" ' \
       
   318                              'data-ams-hint-gravity="e"></i>'.format(translate(_("Created or modified in this version")))
       
   319         return value