--- 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 '» {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