# HG changeset patch # User Thierry Florac # Date 1511886444 -3600 # Node ID 49e8f3cef75aaa1f2a35a715b76179800d2ce105 # Parent 70de02fd0cd14c450d899345a08f5a08c7169ef6 Added deletable interface methods diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/__init__.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 diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/folder.py --- 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): diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/interfaces/__init__.py --- 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""" diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/link.py --- 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) diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/manager.py --- 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): diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/zmi/container.py --- 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 ''.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 ''.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): diff -r 70de02fd0cd1 -r 49e8f3cef75a src/pyams_content/shared/site/zmi/link.py --- 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 '{title}'.format( + return '{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