--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/common/zmi/__init__.py Thu Oct 08 13:37:29 2015 +0200
@@ -0,0 +1,319 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from datetime import datetime
+from uuid import uuid4
+
+# import interfaces
+from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, MANAGE_CONTENT_PERMISSION, CREATE_CONTENT_PERMISSION, \
+ PUBLISH_CONTENT_PERMISSION
+from pyams_content.shared.common.interfaces import IWfSharedContent, ISharedContent, ISharedTool, IManagerRestrictions
+from pyams_form.interfaces.form import IFormContextPermissionChecker, IWidgetsPrefixViewletsManager
+from pyams_i18n.interfaces import II18n, II18nManager
+from pyams_sequence.interfaces import ISequentialIntIds, ISequentialIdInfo
+from pyams_skin.interfaces import IContentTitle
+from pyams_skin.interfaces.container import ITable, ITableElementEditor
+from pyams_skin.interfaces.viewlet import IContextActions, IMenuHeader
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import FORBIDDEN_PERMISSION
+from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowInfo, IWorkflowState, IWorkflowCommentInfo, IWorkflow
+from pyams_zmi.interfaces.menu import ISiteManagementMenu
+from zope.dublincore.interfaces import IZopeDublinCore
+
+# import packages
+from pyams_form.form import AJAXAddForm
+from pyams_form.schema import CloseButton
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.page import DefaultPageHeaderAdapter
+from pyams_skin.table import DefaultElementEditorAdapter
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextAdapter, ContextRequestAdapter
+from pyams_utils.registry import get_utility
+from pyams_utils.request import check_request
+from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config, Viewlet
+from pyams_workflow.versions import WorkflowHistoryItem
+from pyams_zmi.form import AdminDialogAddForm
+from pyramid.view import view_config
+from z3c.form import field, button
+from zope.copy import copy
+from zope.interface import Interface
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate
+
+from pyams_content import _
+
+
+class SharedContentAddForm(AdminDialogAddForm):
+ """Shared content add form"""
+
+ @property
+ def title(self):
+ return II18n(self.context).query_attribute('title', request=self.request)
+
+ icon_css_class = 'fa fa-fw fa-plus'
+ fields = field.Fields(IWfSharedContent).select('title', 'description')
+
+ ajax_handler = 'add-shared-content.json'
+ edit_permission = CREATE_CONTENT_PERMISSION
+
+ def updateWidgets(self, prefix=None):
+ super(SharedContentAddForm, self).updateWidgets(prefix)
+ self.widgets['description'].label_css_class = 'textarea'
+
+ def create(self, data):
+ return self.context.shared_content_factory.content_class()
+
+ def update_content(self, content, data):
+ # generic content update
+ changes = super(SharedContentAddForm, self).update_content(content, data)
+ content.creator = self.request.principal.id
+ content.owner = self.request.principal.id
+ content.short_name = content.title.copy()
+ # init content languages
+ languages = II18nManager(self.context).languages
+ if languages:
+ II18nManager(content).languages = languages.copy()
+ return changes
+
+ def add(self, wf_content):
+ content = self.context.shared_content_factory()
+ self.request.registry.notify(ObjectCreatedEvent(content))
+ uuid = self.__uuid = str(uuid4())
+ self.context[uuid] = content
+ IWorkflowVersions(content).add_version(wf_content, None)
+ IWorkflowInfo(wf_content).fire_transition('init')
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, '{0}/++versions++/1/@@admin.html'.format(self.__uuid))
+
+
+class SharedContentAJAXAddForm(AJAXAddForm):
+ """Shared event add form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ return {'status': 'redirect',
+ 'location': self.nextURL()}
+
+
+@viewlet_config(name='wf-create-message', context=Interface, layer=IPyAMSLayer, view=SharedContentAddForm,
+ manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-create-message.pt')
+class SharedContentAddFormMessage(Viewlet):
+ """Shared content add form info message"""
+
+
+#
+# Edit adapters and views
+#
+
+@adapter_config(context=IWfSharedContent, provides=IFormContextPermissionChecker)
+class WfSharedContentPermissionChecker(ContextAdapter):
+ """Shared content form permission checker"""
+
+ @property
+ def edit_permission(self):
+ workflow = IWorkflow(self.context)
+ state = IWorkflowState(self.context).state
+ if state in workflow.readonly_states: # access forbidden to all for archived contents
+ return FORBIDDEN_PERMISSION
+ elif state in workflow.protected_states: # webmaster can update published contents
+ return MANAGE_SITE_ROOT_PERMISSION
+ else:
+ request = check_request()
+ if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, self.context): # webmaster access
+ return MANAGE_SITE_ROOT_PERMISSION
+ if state in workflow.manager_states: # restricted manager access
+ if request.principal.id in self.context.managers:
+ return PUBLISH_CONTENT_PERMISSION
+ restrictions = IManagerRestrictions(self.context).get_restrictions(request.principal)
+ if restrictions and restrictions.check_access(self.context,
+ permission=PUBLISH_CONTENT_PERMISSION,
+ request=request):
+ return PUBLISH_CONTENT_PERMISSION
+ else:
+ if request.principal.id in self.context.owner | self.context.contributors | self.context.managers:
+ return MANAGE_CONTENT_PERMISSION
+ restrictions = IManagerRestrictions(self.context).get_restrictions(request.principal)
+ if restrictions and restrictions.check_access(self.context,
+ permission=MANAGE_CONTENT_PERMISSION,
+ request=request):
+ return MANAGE_CONTENT_PERMISSION
+ return FORBIDDEN_PERMISSION
+
+
+class WfSharedContentPermissionMixin(object):
+ """Shared content permission checker"""
+
+ @property
+ def permission(self):
+ content = get_parent(self.context, IWfSharedContent)
+ if content is not None:
+ return IFormContextPermissionChecker(content).edit_permission
+
+
+@adapter_config(context=(IWfSharedContent, ISiteManagementMenu), provides=IMenuHeader)
+class WfSharedContentSiteManagementMenuHeader(ContextRequestAdapter):
+ """Shared content site management menu header adapter"""
+
+ header = _("Manage this content")
+
+
+@adapter_config(context=(IWfSharedContent, IPyAMSLayer, Interface), provides=IContentTitle)
+class WfSharedContentTitleAdapter(ContextRequestViewAdapter):
+ """Shared content title adapter"""
+
+ @property
+ def title(self):
+ return II18n(self.context).query_attribute('title', request=self.request)
+
+
+class WfSharedContentHeaderAdapter(DefaultPageHeaderAdapter):
+ """Shared content header adapter"""
+
+ @property
+ def back_url(self):
+ shared_tool = get_parent(self.context, ISharedTool)
+ return absolute_url(shared_tool, self.request, 'admin.html#dashboard.html')
+
+ back_target = None
+ icon_class = 'fa fa-fw fa-edit'
+
+
+@adapter_config(context=(IWfSharedContent, IPyAMSLayer, ITable), provides=ITableElementEditor)
+class WfSharedContentElementEditor(DefaultElementEditorAdapter):
+ """Shared content element editor"""
+
+ view_name = 'admin.html'
+ modal_target = False
+
+
+#
+# Duplication menus and views
+#
+
+@viewlet_config(name='duplication.menu', context=IWfSharedContent, layer=IPyAMSLayer,
+ view=Interface, manager=IContextActions, permission=CREATE_CONTENT_PERMISSION, weight=1)
+class WfSharedContentDuplicateMenu(ToolbarMenuItem):
+ """Shared content duplication menu item"""
+
+ label = _("Duplicate content...")
+ label_css_class = 'fa fa-fw fa-files-o'
+
+ url = 'duplicate.html'
+ modal_target = True
+
+
+class ISharedContentDuplicateButtons(Interface):
+ """Shared content duplication form buttons"""
+
+ close = CloseButton(name='close', title=_("Cancel"))
+ duplicate = button.Button(name='duplicate', title=_("Duplicate content"))
+
+
+@pagelet_config(name='duplicate.html', context=IWfSharedContent, layer=IPyAMSLayer,
+ permission=CREATE_CONTENT_PERMISSION)
+class WfSharedContentDuplicateForm(AdminDialogAddForm):
+ """Shared content duplicate form"""
+
+ legend = _("Duplicate content")
+ fields = field.Fields(IWorkflowCommentInfo)
+ buttons = button.Buttons(ISharedContentDuplicateButtons)
+
+ ajax_handler = 'duplicate.json'
+ edit_permission = CREATE_CONTENT_PERMISSION
+
+ def updateWidgets(self, prefix=None):
+ super(WfSharedContentDuplicateForm, self).updateWidgets(prefix)
+ self.widgets['comment'].label_css_class = 'textarea'
+
+ def updateActions(self):
+ super(WfSharedContentDuplicateForm, self).updateActions()
+ if 'duplicate' in self.actions:
+ self.actions['duplicate'].addClass('btn-primary')
+
+ def createAndAdd(self, data):
+ registry = self.request.registry
+ # initialize new content
+ content = get_parent(self.context, ISharedContent)
+ new_content = content.__class__()
+ registry.notify(ObjectCreatedEvent(new_content))
+ container = get_parent(content, ISharedTool)
+ container[str(uuid4())] = new_content
+ # initialize new version
+ new_version = copy(self.context)
+ registry.notify(ObjectCreatedEvent(new_version))
+ locate(new_version, self.context.__parent__) # locate new version for traversing to work...
+ new_version.creator = self.request.principal.id
+ new_version.owner = self.request.principal.id
+ new_version.modifiers = set()
+ # store new version
+ translate = self.request.localizer.translate
+ workflow = get_utility(IWorkflow, name=new_content.workflow_name)
+ sequence = get_utility(ISequentialIntIds, name=new_content.sequence_name)
+ IWorkflowVersions(new_content).add_version(new_version, workflow.initial_state)
+ history = WorkflowHistoryItem(date=datetime.utcnow(),
+ source_state=translate(workflow.states.getTerm(IWorkflowState(
+ self.context).state).title),
+ transition=translate(_("Duplicate content ({oid})")).format(
+ oid=sequence.get_short_oid(ISequentialIdInfo(content).oid,
+ content.sequence_prefix)),
+ target_state=translate(workflow.states.getTerm(workflow.initial_state).title),
+ principal=self.request.principal.id,
+ comment=data.get('comment'))
+ state = IWorkflowState(new_version)
+ state.history.clear()
+ state.history.append(history)
+ return new_version
+
+
+@view_config(name='duplicate.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+ permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
+class WfSharedContentDuplicateAJAXForm(AJAXAddForm, WfSharedContentDuplicateForm):
+ """Shared content duplicate form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ return {'status': 'redirect',
+ 'location': absolute_url(changes, self.request, 'admin.html')}
+
+
+@viewlet_config(name='wf-duplicate-message', context=IWfSharedContent, layer=IPyAMSLayer,
+ view=WfSharedContentDuplicateForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-duplicate-message.pt')
+class WfSharedContentDuplicateFormMessage(Viewlet):
+ """Shared content add form info message"""
+
+
+#
+# Custom columns mixins
+#
+
+class WfModifiedContentColumnMixin(object):
+ """Shared content modified column mixin"""
+
+ def renderCell(self, item):
+ value = self.getValue(item)
+ content = get_parent(item, IWfSharedContent)
+ if content is not None:
+ if IWorkflowState(content).version_id > 1:
+ item_dc = IZopeDublinCore(item)
+ if item_dc.modified and (item_dc.modified > IZopeDublinCore(content).created):
+ translate = self.request.localizer.translate
+ value += '<i class="fa fa-fw fa-circle txt-color-orange pull-right hint" title="{0}" ' \
+ 'data-ams-hint-gravity="e"></i>'.format(translate(_("Created or modified in this version")))
+ return value