Added deletable interface methods
authorThierry Florac <thierry.florac@onf.fr>
Tue, 28 Nov 2017 17:27:24 +0100
changeset 300 49e8f3cef75a
parent 299 70de02fd0cd1
child 301 9bdda3bcd680
Added deletable interface methods
src/pyams_content/shared/site/__init__.py
src/pyams_content/shared/site/folder.py
src/pyams_content/shared/site/interfaces/__init__.py
src/pyams_content/shared/site/link.py
src/pyams_content/shared/site/manager.py
src/pyams_content/shared/site/zmi/container.py
src/pyams_content/shared/site/zmi/link.py
--- a/src/pyams_content/shared/site/__init__.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/__init__.py	Tue Nov 28 17:27:24 2017 +0100
@@ -20,6 +20,7 @@
 from pyams_content.component.theme.interfaces import IThemesTarget
 from pyams_content.features.preview.interfaces import IPreviewTarget
 from pyams_content.features.review.interfaces import IReviewTarget
+from pyams_workflow.interfaces import IWorkflow, IWorkflowVersions, IWorkflowState
 
 # import packages
 from pyams_content.shared.common import SharedContent, WfSharedContent, register_content_type
@@ -44,3 +45,10 @@
     """WOrkflow managed topic class"""
 
     content_class = WfTopic
+
+    def is_deletable(self):
+        workflow = IWorkflow(self)
+        for version in IWorkflowVersions(self).get_versions():
+            if IWorkflowState(version).state != workflow.initial_state:
+                return False
+        return True
--- a/src/pyams_content/shared/site/folder.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/folder.py	Tue Nov 28 17:27:24 2017 +0100
@@ -52,6 +52,12 @@
     sequence_name = ''  # use default sequence generator
     sequence_prefix = ''
 
+    def is_deletable(self):
+        for element in self.values():
+            if not element.is_deletable():
+                return False
+        return True
+
 
 @adapter_config(context=ISiteFolder, provides=IFormContextPermissionChecker)
 class SiteFolderPermissionChecker(ContextAdapter):
--- a/src/pyams_content/shared/site/interfaces/__init__.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/interfaces/__init__.py	Tue Nov 28 17:27:24 2017 +0100
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_i18n.schema import I18nTextLineField
 
 __docformat__ = 'restructuredtext'
 
@@ -24,16 +23,17 @@
 
 # import packages
 from pyams_content.shared.common.interfaces import ISharedSite, IWfSharedContent, ISharedContent, \
-    IBaseContentManagerRoles, IBaseSharedTool
+    IBaseContentManagerRoles, IBaseSharedTool, IDeletableElement
+from pyams_i18n.schema import I18nTextLineField
 from pyams_sequence.schema import InternalReference
 from zope.container.constraints import containers, contains
 from zope.interface import Attribute
-from zope.schema import Text
+from zope.schema import Text, Bool
 
 from pyams_content import _
 
 
-class ISiteElement(IContained):
+class ISiteElement(IContained, IDeletableElement):
     """Base site element interface"""
 
     containers('.ISiteContainer')
@@ -63,7 +63,7 @@
     """Site folder roles interface"""
 
 
-class ISiteManager(ISharedSite, ISiteContainer, IBaseSharedTool, ISequentialIdTarget):
+class ISiteManager(ISharedSite, ISiteContainer, IBaseSharedTool, IDeletableElement, ISequentialIdTarget):
     """Site manager interface"""
 
     contains(ISiteElement)
@@ -99,5 +99,10 @@
                                   description=_("Content title, as shown in front-office"),
                                   required=False)
 
+    visible = Bool(title=_("Visible?"),
+                   description=_("If 'no', link is not visible"),
+                   required=True,
+                   default=True)
+
     def get_target(self):
         """Get reference target"""
--- a/src/pyams_content/shared/site/link.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/link.py	Tue Nov 28 17:27:24 2017 +0100
@@ -38,6 +38,10 @@
 
     reference = FieldProperty(IContentLink['reference'])
     alt_title = FieldProperty(IContentLink['alt_title'])
+    visible = FieldProperty(IContentLink['visible'])
+
+    def is_deletable(self):
+        return True
 
     def get_target(self):
         target = get_reference_target(self.reference)
--- a/src/pyams_content/shared/site/manager.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/manager.py	Tue Nov 28 17:27:24 2017 +0100
@@ -61,6 +61,12 @@
     sequence_name = ''  # use default sequence generator
     sequence_prefix = ''
 
+    def is_deletable(self):
+        for element in self.values():
+            if not element.is_deletable():
+                return False
+        return True
+
 
 @subscriber(IObjectAddedEvent, context_selector=ISiteManager)
 def handle_added_site_manager(event):
--- a/src/pyams_content/shared/site/zmi/container.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/zmi/container.py	Tue Nov 28 17:27:24 2017 +0100
@@ -17,13 +17,13 @@
 import json
 
 # import interfaces
-from pyams_content.interfaces import MANAGE_SITE_PERMISSION
-from pyams_content.shared.common.interfaces import ISharedContent
+from pyams_content.interfaces import MANAGE_SITE_PERMISSION, MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.common.interfaces import ISharedContent, IWfSharedContent
 from pyams_content.shared.common.interfaces.zmi import IDashboardTable
-from pyams_content.shared.site.interfaces import ISiteContainer, ISiteManager
+from pyams_content.shared.site.interfaces import ISiteContainer, ISiteManager, IContentLink
 from pyams_content.zmi.interfaces import IUserAddingsMenuLabel, ISiteTreeMenu, ISiteTreeTable
 from pyams_i18n.interfaces import II18n
-from pyams_sequence.interfaces import ISequentialIdInfo
+from pyams_sequence.interfaces import ISequentialIdInfo, ISequentialIntIds
 from pyams_skin.interfaces import IInnerPage, IPageHeader
 from pyams_skin.interfaces.container import ITableElementEditor, ITableElementName, ITableWithActions
 from pyams_skin.interfaces.viewlet import IBreadcrumbItem, ITableItemColumnActionsMenu
@@ -43,7 +43,7 @@
 from pyams_skin.container import ContainerView
 from pyams_skin.page import DefaultPageHeaderAdapter
 from pyams_skin.table import BaseTable, TrashColumn, DefaultElementEditorAdapter, NameColumn, SorterColumn, \
-    ActionColumn, I18nColumn
+    I18nColumn, JsActionColumn
 from pyams_skin.viewlet.breadcrumb import BreadcrumbItem
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
@@ -56,11 +56,13 @@
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogEditForm
 from pyams_zmi.view import AdminView
+from pyramid.exceptions import NotFound
 from pyramid.location import lineage
 from pyramid.view import view_config
 from z3c.form import field
 from z3c.table.column import GetAttrColumn
 from zope.interface import implementer
+from zope.lifecycleevent import ObjectMovedEvent
 
 from pyams_content import _
 
@@ -236,7 +238,7 @@
 
 
 @adapter_config(name='visible', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn)
-class SiteContainerTreeVisibleColumn(ActionColumn):
+class SiteContainerTreeVisibleColumn(JsActionColumn):
     """Site container tree visible column"""
 
     cssClasses = {'th': 'action',
@@ -245,26 +247,38 @@
     icon_class = 'fa fa-fw fa-eye'
     icon_hint = _("Visible element?")
 
-    weight = 5
+    url = 'PyAMS_content.site.switchVisibility'
+    permission = MANAGE_CONTENT_PERMISSION
 
-    def renderCell(self, item):
-        return self.get_icon(item)
+    weight = 5
 
     def get_icon(self, item):
-        if ISharedContent.providedBy(item):
-            item = IWorkflowVersions(item).get_last_versions(count=1)[-1]
-        info = IWorkflowPublicationInfo(item, None)
-        if info is None:
-            return ''
+        if IContentLink.providedBy(item):
+            icon_class = 'fa-eye' if item.visible else 'fa-eye-slash'
+            if not IWorkflowPublicationInfo(item.__parent__).is_published():
+                icon_class += ' text-danger'
+            return '<i class="fa fa-fw {icon_class}"></i>'.format(icon_class=icon_class)
         else:
-            if info.is_published():
-                icon_class = 'fa-eye opacity-75'
+            if ISharedContent.providedBy(item):
+                item = IWorkflowVersions(item).get_last_versions(count=1)[-1]
+            info = IWorkflowPublicationInfo(item, None)
+            if info is None:
+                return ''
             else:
-                icon_class = 'fa-eye-slash text-danger opaque'
+                if info.is_published():
+                    icon_class = 'fa-eye opacity-75'
+                else:
+                    icon_class = 'fa-eye-slash text-danger opaque'
             return '<i class="fa fa-fw {icon_class} hint align-base" title="{title}" data-ams-hint-gravity="e"></i>'.format(
                 icon_class=icon_class,
                 title=self.request.localizer.translate(self.icon_hint))
 
+    def renderCell(self, item):
+        if IContentLink.providedBy(item) and self.request.has_permission(self.permission, context=item):
+            return super(SiteContainerTreeVisibleColumn, self).renderCell(item)
+        else:
+            return self.get_icon(item)
+
 
 @adapter_config(name='name', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn)
 class SiteContainerTreeNameColumn(NameColumn):
@@ -310,14 +324,18 @@
     weight = 70
 
     def getValue(self, obj):
-        sequence = ISequentialIdInfo(obj, None)
-        if sequence is None:
-            return '--'
+        if IContentLink.providedBy(obj):
+            sequence = get_utility(ISequentialIntIds)
+            return '&raquo; {0}'.format(sequence.get_base_oid(sequence.get_internal_id(obj.reference)))
         else:
-            try:
-                return sequence.get_short_oid()
-            except TypeError:
+            sequence = ISequentialIdInfo(obj, None)
+            if sequence is None:
                 return '--'
+            else:
+                try:
+                    return sequence.get_base_oid()
+                except TypeError:
+                    return '--'
 
 
 @adapter_config(name='state', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn)
@@ -328,10 +346,16 @@
     weight = 80
 
     def getValue(self, obj):
-        if not ISharedContent.providedBy(obj):
-            return '--'
-        version = IWorkflowVersions(obj).get_last_versions()[-1]
-        return self.request.localizer.translate(IWorkflow(version).get_state_label(IWorkflowState(version).state))
+        target = obj.get_target() if IContentLink.providedBy(obj) else obj
+        if ISharedContent.providedBy(target):
+            target = IWorkflowVersions(target).get_last_versions()[-1]
+        if IWfSharedContent.providedBy(target):
+            result = self.request.localizer.translate(IWorkflow(target).get_state_label(IWorkflowState(target).state))
+        else:
+            result = '--'
+        if IContentLink.providedBy(obj):
+            result = '({0})'.format(result)
+        return result
 
 
 @adapter_config(name='version', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn)
@@ -356,9 +380,9 @@
     permission = MANAGE_SITE_PERMISSION
 
     def has_permission(self, item):
-        if (not ISiteContainer.providedBy(item)) or (item in lineage(self.context)):
+        if item in lineage(self.context):
             return False
-        return super(SiteContainerTreeTrashColumn, self).has_permission(item)
+        return super(SiteContainerTreeTrashColumn, self).has_permission(item) and item.is_deletable()
 
 
 @adapter_config(context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IValues)
@@ -446,37 +470,51 @@
 def set_site_order(request):
     """Set site elements order"""
     intids = get_utility(IIntIds)
-    parent = intids.queryObject(int(request.params.get('parent')))
+    new_parent = intids.queryObject(int(request.params.get('parent')))
     # check for changing parent
     if request.params.get('action') == 'reparent':
         child = intids.queryObject(int(request.params.get('child')))
         old_parent = child.__parent__
         new_name = old_name = child.__name__
-        if old_name in parent:
+        if old_name in new_parent:
             index = 1
             new_name = '{name}-{index:02}'.format(name=old_name, index=index)
-            while new_name in parent:
+            while new_name in new_parent:
                 index += 1
                 new_name = '{name}-{index:02}'.format(name=old_name, index=index)
-        parent[new_name] = child
         del old_parent[old_name]
+        new_parent[new_name] = child
+        request.registry.notify(ObjectMovedEvent(child, old_parent, old_name, new_parent, new_name))
     # Re-define order
     names = [child.__name__ for child in [intids.queryObject(oid)
                                           for oid in map(int, json.loads(request.params.get('order')))]
-             if child.__parent__ is parent]
-    parent.updateOrder(names)
+             if child.__parent__ is new_parent]
+    new_parent.updateOrder(names)
     # get all new parent child
     table = SiteContainerTreeTable(request.context, request,
                                    can_sort=json.loads(request.params.get('can_sort', 'false')),
                                    rows_state='plus')
     table.update()
     result = []
-    for item in parent.values():
+    for item in new_parent.values():
         row = table.setUpRow(item)
         result.append(table.renderRow(row).strip())
     return result
     
 
+@view_config(name='set-content-visibility.json', context=ISiteContainer, request_type=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+def set_content_visibility(request):
+    """Set content link visibility"""
+    container = ISiteContainer(request.context)
+    content = container.get(str(request.params.get('object_name')))
+    if not IContentLink.providedBy(content):
+        raise NotFound()
+    content.visible = not content.visible
+    return {'visible': content.visible,
+            'published': IWorkflowPublicationInfo(content.__parent__).is_published()}
+
+
 @view_config(name='delete-site-item.json', context=ISiteContainer, request_type=IPyAMSLayer,
              permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True)
 def delete_site_item(request):
--- a/src/pyams_content/shared/site/zmi/link.py	Tue Nov 28 17:26:34 2017 +0100
+++ b/src/pyams_content/shared/site/zmi/link.py	Tue Nov 28 17:27:24 2017 +0100
@@ -11,7 +11,8 @@
 #
 from pyramid.location import lineage
 
-from pyams_content.shared.site.zmi.container import SiteContainerTreeTable, SiteContainerTreeNameColumn
+from pyams_content.shared.site.zmi.container import SiteContainerTreeTable, SiteContainerTreeNameColumn, \
+    SiteContainerTreeOidColumn
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 
 __docformat__ = 'restructuredtext'
@@ -127,7 +128,7 @@
             target = self.context.get_target()
             if target is not None:
                 title = get_object_name(target, self.request, self.view)
-        return '<i class="fa fa-fw fa-external-link-square rotate-90"></i>{title}'.format(
+        return '<i class="fa fa-fw fa-external-link-square fa-rotate-90"></i>{title}'.format(
             title=title or '--')
 
 
@@ -151,8 +152,8 @@
 
     def get_ajax_output(self, changes):
         output = super(ContentLinkPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        intids = get_utility(IIntIds)
         if 'alt_title' in changes.get(IContentLink, ()):
-            intids = get_utility(IIntIds)
             adapter = ContentLinkTableElementName(self.context, self.request, None)
             column = SiteContainerTreeNameColumn(self.context, self.request, None)
             output.setdefault('events', []).append({
@@ -164,4 +165,15 @@
                     'cell': column.renderCell(self.context, name=adapter.name)
                 }
             })
+        if 'reference' in changes.get(IContentLink, ()):
+            column = SiteContainerTreeOidColumn(self.context, self.request, None)
+            output.setdefault('events', []).append({
+                'event': 'myams.refresh',
+                'options': {
+                    'handler': 'MyAMS.skin.refreshRowCell',
+                    'object_id': '{0}::{1}'.format(SiteContainerTreeTable.id, intids.queryId(self.context)),
+                    'col_name': 'oid',
+                    'cell': column.renderCell(self.context)
+                }
+            })
         return output