# HG changeset patch # User Thierry Florac # Date 1511686687 -3600 # Node ID 8742c8ac126c00f9f7966aec6ae40e6c6d03244c # Parent 401794fc244b957ccbe29d3b8c8a516077661b7a Added site management features diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/__init__.py --- a/src/pyams_content/shared/site/__init__.py Sun Nov 26 09:57:42 2017 +0100 +++ b/src/pyams_content/shared/site/__init__.py Sun Nov 26 09:58:07 2017 +0100 @@ -20,10 +20,11 @@ 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_content.shared.site.interfaces import IWfTopic, TOPIC_CONTENT_TYPE, TOPIC_CONTENT_NAME, ITopic # import packages -from pyams_content.shared.common import register_content_type, WfSharedContent, SharedContent +from pyams_content.shared.common import SharedContent, WfSharedContent, register_content_type +from pyams_content.shared.site.interfaces import ISiteContainer, ISiteFolder, ITopic, IWfTopic, TOPIC_CONTENT_NAME, \ + TOPIC_CONTENT_TYPE from zope.interface import implementer diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/container.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/container.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,84 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 +import json + +# import interfaces +from pyams_content.shared.site.interfaces import ISiteContainer, ISiteFolder +from pyams_i18n.interfaces import II18n +from zope.intid.interfaces import IIntIds + +# import packages +from pyams_utils.registry import get_utility +from pyams_utils.request import query_request +from pyramid.location import lineage +from zope.interface import implementer + + +@implementer(ISiteContainer) +class SiteContainerMixin(object): + """Site container mixin class""" + + def get_folders_tree(self, selected=None, permission=None): + + request = query_request() + intids = get_utility(IIntIds) + + def get_folder_items(parent, input): + for folder in parent.values(): + if ISiteFolder.providedBy(folder): + if permission is not None: + can_select = request.has_permission(permission, context=folder) + else: + can_select = True + value = { + 'id': intids.queryId(folder), + 'text': II18n(folder).query_attribute('title', request=request), + 'state': { + 'expanded': folder in lineage(self), + 'selected': folder is selected + }, + 'selectable': can_select + } + items = get_folder_items(folder, []) + if items: + value['nodes'] = items + input.append(value) + return input + + # get child folders + items = get_folder_items(self, []) + + # get parents folders + container = self + while ISiteContainer.providedBy(container): + if permission is not None: + can_select = request.has_permission(permission, context=container) + else: + can_select = True + items = [{ + 'id': intids.queryId(container), + 'text': II18n(container).query_attribute('title', request=request), + 'state': { + 'expanded': True, + 'selected': container is selected + }, + 'selectable': can_select, + 'nodes': items + }] + container = container.__parent__ + + return json.dumps(items) diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/folder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/folder.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,78 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 + +# import interfaces +from pyams_content.interfaces import MANAGE_SITE_PERMISSION +from pyams_content.shared.site.interfaces import ISiteFolder, ISiteManager, ISiteFolderRoles +from pyams_form.interfaces.form import IFormContextPermissionChecker +from pyams_i18n.interfaces import II18n +from pyams_portal.interfaces import IPortalContext +from pyams_security.interfaces import IDefaultProtectionPolicy +from zope.annotation.interfaces import IAttributeAnnotatable +from zope.intid.interfaces import IIntIds + +# import packages +from pyams_content.shared.common.manager import BaseSharedTool +from pyams_content.shared.site.container import SiteContainerMixin +from pyams_utils.adapter import adapter_config, ContextAdapter +from pyams_utils.container import find_objects_providing +from pyams_utils.registry import get_local_registry +from pyams_utils.request import query_request +from pyams_utils.traversing import get_parent +from pyams_utils.vocabulary import vocabulary_config +from zope.container.ordered import OrderedContainer +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty +from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm + + +@implementer(IDefaultProtectionPolicy, ISiteFolder, ISiteFolderRoles, + IPortalContext, IAttributeAnnotatable) +class SiteFolder(SiteContainerMixin, OrderedContainer, BaseSharedTool): + """Site folder persistent class""" + + roles_interface = ISiteFolderRoles + + notepad = FieldProperty(ISiteFolder['notepad']) + + sequence_name = '' # use default sequence generator + sequence_prefix = '' + + +@adapter_config(context=ISiteFolder, provides=IFormContextPermissionChecker) +class SiteFolderPermissionChecker(ContextAdapter): + """Site folder edit permission checker""" + + edit_permission = MANAGE_SITE_PERMISSION + + +@vocabulary_config(name='PyAMS site folders') +class SiteManagerFoldersVocabulary(SimpleVocabulary): + """Site manager folders vocabulary""" + + def __init__(self, context): + terms = [] + site = get_parent(context, ISiteManager) + if site is not None: + registry = get_local_registry() + if registry is not None: + request = query_request() + intids = registry.getUtility(IIntIds) + for folder in find_objects_providing(site, ISiteFolder): + terms.append(SimpleTerm(value=intids.queryId(folder), + title=II18n(folder).query_attribute('title', request=request))) + super(SiteManagerFoldersVocabulary, self).__init__(terms) diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/interfaces/__init__.py --- a/src/pyams_content/shared/site/interfaces/__init__.py Sun Nov 26 09:57:42 2017 +0100 +++ b/src/pyams_content/shared/site/interfaces/__init__.py Sun Nov 26 09:58:07 2017 +0100 @@ -9,6 +9,7 @@ # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # +from pyams_i18n.schema import I18nTextLineField __docformat__ = 'restructuredtext' @@ -17,39 +18,61 @@ # import interfaces from pyams_content.interfaces import IBaseContent -from zope.container.interfaces import IContainer +from pyams_sequence.interfaces import ISequentialIdTarget +from pyams_workflow.interfaces import IWorkflowPublicationSupport +from zope.container.interfaces import IContainer, IContained # import packages -from pyams_content.shared.common.interfaces import ISharedSite, IWfSharedContent, ISharedContent +from pyams_content.shared.common.interfaces import ISharedSite, IWfSharedContent, ISharedContent, \ + IBaseContentManagerRoles, IBaseSharedTool +from pyams_sequence.schema import InternalReference from zope.container.constraints import containers, contains +from zope.interface import Attribute +from zope.schema import Text from pyams_content import _ -class ISiteElement(IBaseContent): +class ISiteElement(IContained): """Base site element interface""" containers('.ISiteContainer') -class ISiteContainer(IBaseContent, IContainer): +class ISiteContainer(IContainer, IContained, IWorkflowPublicationSupport): """Base site container interface""" contains(ISiteElement) + def get_folders_tree(self, selected=None): + """Get site tree in JSON format""" -class ISiteFolder(ISiteElement, ISiteContainer): + +class ISiteFolder(IBaseContent, ISiteElement, ISiteContainer, ISequentialIdTarget): """Site folder interface A site folder is made to contain sub-folders and topics """ + notepad = Text(title=_("Notepad"), + description=_("Internal information to be known about this content"), + required=False) -class ISiteManager(ISharedSite, ISiteContainer): + +class ISiteFolderRoles(IBaseContentManagerRoles): + """Site folder roles interface""" + + +class ISiteManager(ISharedSite, ISiteContainer, IBaseSharedTool, ISequentialIdTarget): """Site manager interface""" contains(ISiteElement) + folder_factory = Attribute("Folder factory") + + topic_content_type = Attribute("Topic content type") + topic_content_factory = Attribute("Topic content factory") + TOPIC_CONTENT_TYPE = 'topic' TOPIC_CONTENT_NAME = _("Topic") @@ -61,3 +84,20 @@ class ITopic(ISharedContent, ISiteElement): """Workflow managed topic interface""" + + +class IContentLink(ISiteElement): + """Rented content interface""" + + reference = InternalReference(title=_("Internal reference"), + description=_("Internal link target reference. You can search a reference using " + "'+' followed by internal number, of by entering text matching " + "content title."), + required=True) + + alt_title = I18nTextLineField(title=_("Alternate title"), + description=_("Content title, as shown in front-office"), + required=False) + + def get_target(self): + """Get reference target""" diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/link.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/link.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,60 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 + +# import interfaces +from pyams_content.shared.site.interfaces import IContentLink +from pyams_workflow.interfaces import IWorkflow, IWorkflowVersion, IWorkflowVersions, IWorkflowPublicationInfo + +# import packages +from persistent import Persistent +from pyams_sequence.utility import get_reference_target +from pyams_utils.adapter import adapter_config +from zope.container.contained import Contained +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(IContentLink) +class ContentLink(Persistent, Contained): + """Content link persistent class + + A 'content link' is a link to another content, which may be stored anywhere (same site, + another site or in any shared tool). + """ + + reference = FieldProperty(IContentLink['reference']) + alt_title = FieldProperty(IContentLink['alt_title']) + + def get_target(self): + target = get_reference_target(self.reference) + if IWorkflowVersion.providedBy(target): + workflow = IWorkflow(target, None) + if workflow is not None: + versions = IWorkflowVersions(target).get_versions(workflow.published_states, sort=True) + if not versions: + versions = IWorkflowVersions(target).get_last_versions() + if versions: + target = versions[-1] + return target + + +@adapter_config(context=IContentLink, provides=IWorkflowPublicationInfo) +def content_link_publication_info(context): + """Content link publication info""" + target = context.get_target() + if target is not None: + return IWorkflowPublicationInfo(target, None) diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/manager.py --- a/src/pyams_content/shared/site/manager.py Sun Nov 26 09:57:42 2017 +0100 +++ b/src/pyams_content/shared/site/manager.py Sun Nov 26 09:58:07 2017 +0100 @@ -18,7 +18,9 @@ # import interfaces from pyams_content.component.paragraph.interfaces import IParagraphFactorySettings from pyams_content.component.theme.interfaces import IThemesManagerTarget -from pyams_content.shared.site.interfaces import ISiteManager +from pyams_content.interfaces import MANAGE_SITE_PERMISSION +from pyams_content.shared.site.interfaces import ISiteManager, TOPIC_CONTENT_TYPE +from pyams_form.interfaces.form import IFormContextPermissionChecker from pyams_i18n.interfaces import II18n from pyams_portal.interfaces import IPortalContext from zope.annotation.interfaces import IAttributeAnnotatable @@ -27,7 +29,11 @@ # import packages from pyams_content.shared.common.manager import BaseSharedTool +from pyams_content.shared.site import Topic +from pyams_content.shared.site.container import SiteContainerMixin +from pyams_content.shared.site.folder import SiteFolder from pyams_skin.skin import UserSkinnableContent +from pyams_utils.adapter import adapter_config, ContextAdapter from pyams_utils.registry import get_utilities_for from pyams_utils.request import query_request from pyams_utils.traversing import get_parent @@ -39,13 +45,22 @@ from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary -@implementer(ISiteManager, IParagraphFactorySettings, IThemesManagerTarget, IAttributeAnnotatable, IPortalContext) -class SiteManager(OrderedContainer, BaseSharedTool, UserSkinnableContent): +@implementer(ISiteManager, IParagraphFactorySettings, IThemesManagerTarget, + IPortalContext, IAttributeAnnotatable) +class SiteManager(SiteContainerMixin, OrderedContainer, BaseSharedTool, UserSkinnableContent): """Site manager persistent class""" + folder_factory = SiteFolder + + topic_content_type = TOPIC_CONTENT_TYPE + topic_content_factory = Topic + allowed_paragraphs = FieldProperty(IParagraphFactorySettings['allowed_paragraphs']) auto_created_paragraphs = FieldProperty(IParagraphFactorySettings['auto_created_paragraphs']) + sequence_name = '' # use default sequence generator + sequence_prefix = '' + @subscriber(IObjectAddedEvent, context_selector=ISiteManager) def handle_added_site_manager(event): @@ -65,6 +80,13 @@ registry.unregisterUtility(event.object, ISiteManager, name=event.object.__name__) +@adapter_config(context=ISiteManager, provides=IFormContextPermissionChecker) +class SiteManagerPermissionChecker(ContextAdapter): + """Site manager edit permission checker""" + + edit_permission = MANAGE_SITE_PERMISSION + + @vocabulary_config(name='PyAMS site managers') class SiteManagerVocabulary(SimpleVocabulary): """Site manager vocabulary""" diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/__init__.py --- a/src/pyams_content/shared/site/zmi/__init__.py Sun Nov 26 09:57:42 2017 +0100 +++ b/src/pyams_content/shared/site/zmi/__init__.py Sun Nov 26 09:58:07 2017 +0100 @@ -14,7 +14,112 @@ # import standard library +from uuid import uuid4 # import interfaces +from pyams_content.interfaces import CREATE_CONTENT_PERMISSION +from pyams_content.shared.common.interfaces import IWfSharedContent +from pyams_content.shared.site.interfaces import ISiteContainer, ISiteManager +from pyams_i18n.interfaces import II18nManager +from pyams_skin.interfaces.viewlet import IToolbarAddingMenu +from pyams_skin.layer import IPyAMSLayer +from pyams_workflow.interfaces import IWorkflowInfo, IWorkflowVersions +from pyams_zmi.layer import IAdminLayer +from zope.intid.interfaces import IIntIds # import packages +from pyams_content.shared.common.zmi import SharedContentAddForm, SharedContentAJAXAddForm +from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget +from pyams_pagelet.pagelet import pagelet_config +from pyams_skin.viewlet.toolbar import ToolbarMenuItem +from pyams_utils.registry import get_utility +from pyams_utils.traversing import get_parent +from pyams_utils.url import absolute_url +from pyams_viewlet.viewlet import viewlet_config +from pyramid.view import view_config +from z3c.form import field +from zope.interface import Interface +from zope.lifecycleevent import ObjectCreatedEvent +from zope.schema import Int +from pyams_content import _ + + +@viewlet_config(name='add-topic.menu', context=ISiteContainer, layer=IAdminLayer, view=Interface, + manager=IToolbarAddingMenu, permission=CREATE_CONTENT_PERMISSION, weight=20) +class TopicAddMenu(ToolbarMenuItem): + """Topic add menu""" + + label = _("Add topic...") + label_css_class = 'fa fa-fw fa-file-o' + url = 'add-topic.html' + modal_target = True + + +class ITopicAddFormFields(IWfSharedContent): + """Topic add form fields interface""" + + parent = Int(title=_("Parent"), + description=_("Topic's parent"), + required=True) + + +@pagelet_config(name='add-topic.html', context=ISiteContainer, layer=IPyAMSLayer, + permission=CREATE_CONTENT_PERMISSION) +@pagelet_config(name='add-shared-content.html', context=ISiteContainer, layer=IPyAMSLayer, + permission=CREATE_CONTENT_PERMISSION) +class TopicAddForm(SharedContentAddForm): + """Topic add form""" + + legend = _("Add topic") + + fields = field.Fields(ITopicAddFormFields).select('title', 'parent', 'notepad') + fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget + + ajax_handler = 'add-topic.json' + edit_permission = CREATE_CONTENT_PERMISSION + + __target = None + + def updateWidgets(self, prefix=None): + super(TopicAddForm, self).updateWidgets(prefix) + if 'parent' in self.widgets: + self.widgets['parent'].permission = CREATE_CONTENT_PERMISSION + + def create(self, data): + manager = get_parent(self.context, ISiteManager) + return manager.topic_content_factory.content_class() + + def update_content(self, content, data): + # initialize content fields + content.title = data['title'] + content.short_name = content.title.copy() + content.notepad = data.get('notepad') + content.creator = self.request.principal.id + content.owner = self.request.principal.id + # get parent + intids = get_utility(IIntIds) + parent = intids.queryObject(data.get('parent')) + if parent is not None: + languages = II18nManager(parent).languages + if languages: + II18nManager(content).languages = languages.copy() + manager = get_parent(parent, ISiteManager) + wf_parent = manager.topic_content_factory() + self.request.registry.notify(ObjectCreatedEvent(wf_parent)) + uuid = str(uuid4()) + parent[uuid] = wf_parent + IWorkflowVersions(wf_parent).add_version(content, None) + IWorkflowInfo(content).fire_transition('init', comment=content.notepad) + self.__target = content + + def add(self, content): + pass + + def nextURL(self): + return absolute_url(self.__target, self.request, 'admin') + + +@view_config(name='add-topic.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True) +class TopicAJAXAddForm(SharedContentAJAXAddForm, TopicAddForm): + """Topic add form, JSON renderer""" diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/container.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/zmi/container.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,535 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 +import json + +# import interfaces +from pyams_content.interfaces import MANAGE_SITE_PERMISSION +from pyams_content.shared.common.interfaces import ISharedContent +from pyams_content.shared.common.interfaces.zmi import IDashboardTable +from pyams_content.shared.site.interfaces import ISiteContainer, ISiteManager +from pyams_content.zmi.interfaces import IUserAddingsMenuLabel, ISiteTreeMenu, ISiteTreeTable +from pyams_i18n.interfaces import II18n +from pyams_sequence.interfaces import ISequentialIdInfo +from pyams_skin.interfaces import IInnerPage, IPageHeader +from pyams_skin.interfaces.container import ITableElementEditor, ITableElementName, ITableWithActions +from pyams_skin.interfaces.viewlet import IBreadcrumbItem, ITableItemColumnActionsMenu +from pyams_skin.layer import IPyAMSLayer +from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION +from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowPublicationInfo, IWorkflowState, IWorkflow +from pyams_zmi.interfaces.menu import ISiteManagementMenu, IPropertiesMenu +from pyams_zmi.layer import IAdminLayer +from z3c.table.interfaces import IColumn, IValues +from zope.intid.interfaces import IIntIds + +# import packages +from pyams_content.shared.site import WfTopic +from pyams_content.skin import pyams_content +from pyams_form.form import AJAXEditForm +from pyams_pagelet.pagelet import pagelet_config +from pyams_skin.container import ContainerView +from pyams_skin.page import DefaultPageHeaderAdapter +from pyams_skin.table import BaseTable, TrashColumn, DefaultElementEditorAdapter, NameColumn, SorterColumn, \ + ActionColumn, I18nColumn +from pyams_skin.viewlet.breadcrumb import BreadcrumbItem +from pyams_skin.viewlet.menu import MenuItem +from pyams_skin.viewlet.toolbar import ToolbarMenuItem +from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextRequestAdapter +from pyams_utils.fanstatic import get_resource_path +from pyams_utils.registry import get_utility +from pyams_utils.traversing import get_parent +from pyams_utils.url import absolute_url +from pyams_viewlet.manager import viewletmanager_config +from pyams_viewlet.viewlet import viewlet_config +from pyams_zmi.form import AdminDialogEditForm +from pyams_zmi.view import AdminView +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 pyams_content import _ + + +@adapter_config(context=(ISiteContainer, IAdminLayer), provides=IBreadcrumbItem) +class SiteContainerBreadcrumbAdapter(BreadcrumbItem): + """Site container breadcrumb adapter""" + + @property + def label(self): + return II18n(self.context).query_attribute('short_name', request=self.request) + + +@adapter_config(context=(ISiteContainer, IAdminLayer), provides=IUserAddingsMenuLabel) +class SiteManagerUserAddingsMenuLabelAdapter(ContextRequestAdapter): + """Site container user addings menu label adapter""" + + @property + def label(self): + return '{content} ({blog})'.format( + content=self.request.localizer.translate(WfTopic.content_name), + blog=II18n(self.context).query_attribute('title', request=self.request)) + + +# +# Site container publication views +# + +@viewlet_config(name='workflow-publication.menu', context=ISiteContainer, layer=IPyAMSLayer, view=ISiteTreeTable, + manager=ITableItemColumnActionsMenu, permission=MANAGE_SITE_PERMISSION, weight=2) +class SiteContainerTableItemWorkflowPublicationMenu(ToolbarMenuItem): + """Site container tree item workflow publication menu""" + + label = _("Publication dates...") + label_css_class = 'fa fa-fw fa-eye' + url = 'workflow-publication.html' + modal_target = True + stop_propagation = True + + +@viewlet_config(name='workflow-publication.menu', context=ISiteContainer, layer=IAdminLayer, manager=IPropertiesMenu, + permission=MANAGE_SITE_PERMISSION, weight=2) +class SiteContainerWorkflowPublicationMenu(MenuItem): + """Site container workflow publication menu""" + + label = _("Publication dates...") + icon_class = 'fa-eye' + url = 'workflow-publication.html' + modal_target = True + + +@pagelet_config(name='workflow-publication.html', context=ISiteContainer, layer=IPyAMSLayer, + permission=MANAGE_SITE_PERMISSION) +class SiteContainerWorkflowPublicationEditForm(AdminDialogEditForm): + """Site container workflow publication edit form""" + + legend = _("Update publication dates") + + fields = field.Fields(IWorkflowPublicationInfo).select('publication_effective_date', 'publication_expiration_date') + ajax_handler = 'workflow-publication.json' + edit_permission = MANAGE_SITE_PERMISSION + + +@view_config(name='workflow-publication.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True) +class SiteContainerWorkflowPublicationAJAXEditForm(AJAXEditForm, SiteContainerWorkflowPublicationEditForm): + """Site container workflow publication edit form, JSON renderer""" + + def get_ajax_output(self, changes): + output = super(SiteContainerWorkflowPublicationAJAXEditForm, self).get_ajax_output(changes) + if changes: + info = IWorkflowPublicationInfo(self.context, None) + if info is not None: + if info.is_published(): + icon_class = 'fa-eye opacity-75' + else: + icon_class = 'fa-eye-slash text-danger opaque' + value = ''.format( + icon_class=icon_class, + title=self.request.localizer.translate(_("Visible element?"))) + intids = get_utility(IIntIds) + 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': 'visible', + 'cell': value + } + }) + return output + + +# +# Site container tree view +# + +@viewlet_config(name='site-tree.menu', layer=IAdminLayer, context=ISiteContainer, manager=ISiteManagementMenu, + permission=VIEW_SYSTEM_PERMISSION, weight=5) +@viewletmanager_config(name='site-tree.menu', layer=IAdminLayer, context=ISiteContainer, provides=ISiteTreeMenu) +@implementer(ISiteTreeMenu) +class SiteContainerTreeMenu(MenuItem): + """Site container tree menu""" + + label = _("Site tree") + icon_class = 'fa-sitemap' + url = '#site-tree.html' + + +@implementer(IDashboardTable, ISiteTreeTable, ITableWithActions) +class SiteContainerTreeTable(BaseTable): + """Site container tree table""" + + id = 'site_tree_table' + title = _("Site tree") + + hide_body_toolbar = True + sortOn = None + + permission = MANAGE_SITE_PERMISSION + + def __init__(self, context, request, can_sort=False, rows_state=None): + super(SiteContainerTreeTable, self).__init__(context, request) + self.can_sort = can_sort + self.rows_state = rows_state + + @property + def cssClasses(self): + classes = ['table', 'table-bordered', 'table-striped', 'table-hover', 'table-tight'] + permission = self.permission + if self.can_sort and ((not permission) or self.request.has_permission(permission, self.context)): + classes.append('table-dnd') + return {'table': ' '.join(classes)} + + @property + def data_attributes(self): + attributes = super(SiteContainerTreeTable, self).data_attributes + intids = get_utility(IIntIds) + manager = get_parent(self.context, ISiteManager) + attributes.setdefault('table', {}).update({ + 'data-ams-plugins': 'pyams_content', + 'data-ams-plugin-pyams_content-src': get_resource_path(pyams_content), + 'data-ams-location': absolute_url(self.context, self.request), + 'data-ams-datatable-sort': 'false', + 'data-ams-datatable-pagination': 'false', + 'data-ams-delete-target': 'delete-site-item.json', + 'data-ams-tree-node-id': intids.queryId(manager), + 'data-ams-tablednd-drag-handle': 'td.sorter', + 'data-ams-tablednd-drop': 'MyAMS.tree.sortTree', + 'data-ams-tablednd-drop-target': 'set-site-order.json' + }) + attributes.setdefault('tr', {}).update({ + 'id': lambda x, col: '{0}::{1}'.format(self.id, intids.queryId(x)), + 'data-ams-location': lambda x, col: absolute_url(x.__parent__, self.request), + 'data-ams-tree-node-id': lambda x, col: intids.queryId(x), + 'data-ams-tree-node-parent-id': lambda x, col: intids.queryId(x.__parent__) + }) + return attributes + + +@adapter_config(name='sorter', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeSorterColumn(SorterColumn): + """Site container tree sorter column""" + + permission = MANAGE_SITE_PERMISSION + + def renderCell(self, item): + if self.table.can_sort: + return super(SiteContainerTreeSorterColumn, self).renderCell(item) + else: + return '' + + +@adapter_config(name='visible', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeVisibleColumn(ActionColumn): + """Site container tree visible column""" + + cssClasses = {'th': 'action', + 'td': 'action'} + + icon_class = 'fa fa-fw fa-eye' + icon_hint = _("Visible element?") + + weight = 5 + + def renderCell(self, item): + return self.get_icon(item) + + 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 '' + else: + 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)) + + +@adapter_config(name='name', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeNameColumn(NameColumn): + """Site container tree name column""" + + _header = _("Folders and topics") + + def renderHeadCell(self): + return '' \ + ' ' \ + ' ' \ + '   {title}' \ + ''.format( + hint=self.request.localizer.translate(_("Click to open/close all folders")), + title=super(SiteContainerTreeNameColumn, self).renderHeadCell()) + + def renderCell(self, item, name=None): + depth = -3 + for node in lineage(item): + depth += 1 + return '
' \ + ' {padding}' \ + ' ' \ + ' ' \ + '   {title}' \ + '
'.format( + padding='' * depth, + hint=self.request.localizer.translate(_("Click to show/hide inner folders")), + switch='fa-{state}-square-o switch'.format( + state=self.table.rows_state or ('minus' if item in lineage(self.context) else 'plus')) + if ISiteContainer.providedBy(item) else '', + title=name or super(SiteContainerTreeNameColumn, self).renderCell(item)) + + +@adapter_config(name='oid', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeOidColumn(I18nColumn, GetAttrColumn): + """Site container tree OID column""" + + _header = _("OID") + weight = 70 + + def getValue(self, obj): + sequence = ISequentialIdInfo(obj, None) + if sequence is None: + return '--' + else: + try: + return sequence.get_short_oid() + except TypeError: + return '--' + + +@adapter_config(name='state', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeStateColumn(I18nColumn, GetAttrColumn): + """Site container tree state column""" + + _header = _("Status") + 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)) + + +@adapter_config(name='version', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeVersionColumn(I18nColumn, GetAttrColumn): + """Site container tree version column""" + + _header = _("Version") + weight = 90 + + def getValue(self, obj): + if not ISharedContent.providedBy(obj): + return '--' + version = IWorkflowVersions(obj).get_last_versions()[-1] + return IWorkflowState(version).version_id + + +@adapter_config(name='trash', context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IColumn) +class SiteContainerTreeTrashColumn(TrashColumn): + """Site container tree trash column""" + + icon_hint = _("Delete site item") + permission = MANAGE_SITE_PERMISSION + + def has_permission(self, item): + if (not ISiteContainer.providedBy(item)) or (item in lineage(self.context)): + return False + return super(SiteContainerTreeTrashColumn, self).has_permission(item) + + +@adapter_config(context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=IValues) +class SiteContainerTreeValuesAdapter(ContextRequestViewAdapter): + """Site container tree values adapter""" + + @property + def values(self): + + def get_values(container, result): + if container not in result: + result.append(container) + if ISiteContainer.providedBy(container) and (container in lineage(self.context)): + for child in container.values(): + get_values(child, result) + return result + + manager = get_parent(self.context, ISiteManager) + values = [] + for container in manager.values(): + values.append(container) + if ISiteContainer.providedBy(container): + get_values(container, values) + return values + + +@pagelet_config(name='site-tree.html', context=ISiteContainer, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION) +@implementer(IInnerPage) +class SiteContainerTreeView(AdminView, ContainerView): + """Site Container tree view""" + + table_class = SiteContainerTreeTable + + def __init__(self, context, request): + super(ContainerView, self).__init__(context, request) + self.table = SiteContainerTreeTable(context, request, can_sort=ISiteManager.providedBy(context)) + + +@adapter_config(context=(ISiteContainer, IAdminLayer, ISiteTreeTable), provides=IPageHeader) +class SiteContainerViewHeaderAdapter(DefaultPageHeaderAdapter): + """Site container tree view header adapter""" + + icon_class = 'fa fa-fw fa-sitemap' + + +@view_config(name='get-tree.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True) +def get_tree(request): + """Get whole tree""" + + def get_tree_values(parent): + """Iterator over container tree items""" + for item in parent.values(): + yield item + if ISiteContainer.providedBy(item): + yield from get_tree_values(item) + + table = SiteContainerTreeTable(request.context, request, + can_sort=json.loads(request.params.get('can_sort', 'false'))) + table.update() + result = [] + manager = get_parent(request.context, ISiteManager) + for item in get_tree_values(manager): + row = table.setUpRow(item) + result.append(table.renderRow(row).strip()) + return result + + +@view_config(name='get-tree-nodes.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True) +def get_tree_nodes(request): + """Get tree nodes""" + table = SiteContainerTreeTable(request.context, request, + can_sort=json.loads(request.params.get('can_sort', 'false'))) + table.update() + result = [] + for item in request.context.values(): + row = table.setUpRow(item) + result.append(table.renderRow(row).strip()) + return result + + +@view_config(name='set-site-order.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True) +def set_site_order(request): + """Set site elements order""" + intids = get_utility(IIntIds) + 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: + index = 1 + new_name = '{name}-{index:02}'.format(name=old_name, index=index) + while new_name in parent: + index += 1 + new_name = '{name}-{index:02}'.format(name=old_name, index=index) + parent[new_name] = child + del old_parent[old_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) + # 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(): + row = table.setUpRow(item) + result.append(table.renderRow(row).strip()) + return result + + +@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): + """Delete item from site container""" + translate = request.localizer.translate + name = request.params.get('object_name') + if not name: + return {'status': 'message', + 'messagebox': {'status': 'error', + 'content': translate(_("No provided object_name argument!"))}} + if name not in request.context: + return {'status': 'message', + 'messagebox': {'status': 'error', + 'content': translate(_("Given object name doesn't exist!"))}} + del request.context[name] + return {'status': 'success'} + + +@adapter_config(context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=ITableElementName) +class SiteContainerTableElementName(ContextRequestViewAdapter): + """Site container tree table element name""" + + @property + def name(self): + return II18n(self.context).query_attribute('title', request=self.request) + + +@adapter_config(context=(ISharedContent, IPyAMSLayer, ISiteTreeTable), provides=ITableElementName) +class SharedContentTableElementName(ContextRequestViewAdapter): + """Shared content tree table element name""" + + @property + def name(self): + version = IWorkflowVersions(self.context).get_last_versions(count=1)[0] + return II18n(version).query_attribute('title', request=self.request) + + +@adapter_config(context=(ISiteContainer, IPyAMSLayer, ISiteTreeTable), provides=ITableElementEditor) +class SiteContainerTableElementEditor(DefaultElementEditorAdapter): + """Site container tree table element editor""" + + view_name = 'admin#site-tree.html' + modal_target = False + + +@adapter_config(context=(ISharedContent, IPyAMSLayer, ISiteTreeTable), provides=ITableElementEditor) +class SharedContentTableElementEditor(DefaultElementEditorAdapter): + """Shared content tree table element editor""" + + view_name = 'admin' + modal_target = False + + @property + def url(self): + version = IWorkflowVersions(self.context).get_last_versions(count=1)[0] + return absolute_url(version, self.request, self.view_name) diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/folder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/zmi/folder.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,146 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 + +# import interfaces +from pyams_content.interfaces import MANAGE_SITE_PERMISSION +from pyams_content.shared.site.interfaces import ISiteContainer, ISiteManager +from pyams_i18n.interfaces import INegotiator, II18n +from pyams_skin.interfaces.viewlet import IToolbarAddingMenu +from pyams_skin.layer import IPyAMSLayer +from pyams_zmi.layer import IAdminLayer +from z3c.form.interfaces import IDataExtractedEvent +from zope.intid.interfaces import IIntIds + +# import packages +from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget +from pyams_form.form import AJAXAddForm +from pyams_i18n.schema import I18nTextLineField +from pyams_pagelet.pagelet import pagelet_config +from pyams_skin.viewlet.toolbar import ToolbarMenuItem +from pyams_utils.registry import get_utility +from pyams_utils.traversing import get_parent +from pyams_utils.unicode import translate_string +from pyams_utils.url import absolute_url +from pyams_viewlet.viewlet import viewlet_config +from pyams_zmi.form import AdminDialogAddForm +from pyramid.events import subscriber +from pyramid.view import view_config +from z3c.form import field +from zope.interface import Interface, Invalid +from zope.schema import Text, Int + +from pyams_content import _ + + +@viewlet_config(name='add-site-folder.menu', context=ISiteContainer, layer=IAdminLayer, view=Interface, + manager=IToolbarAddingMenu, permission=MANAGE_SITE_PERMISSION, weight=10) +class SiteFolderAddMenu(ToolbarMenuItem): + """Site folder add menu""" + + label = _("Add site folder...") + label_css_class = 'fa fa-fw fa-folder-o' + url = 'add-site-folder.html' + modal_target = True + + +class ISiteFolderAddFormFields(Interface): + """Site folder add form fields interface""" + + title = I18nTextLineField(title=_("Title"), + description=_("Visible label used to display content"), + required=True) + + parent = Int(title=_("Parent"), + description=_("Folder's parent"), + required=True) + + notepad = Text(title=_("Notepad"), + description=_("Internal information to be known about this content"), + required=False) + + +@pagelet_config(name='add-site-folder.html', context=ISiteContainer, layer=IPyAMSLayer, + permission=MANAGE_SITE_PERMISSION) +class SiteFolderAddForm(AdminDialogAddForm): + """Site folder add form""" + + @property + def title(self): + return II18n(self.context).query_attribute('title', request=self.request) + + legend = _("Add site folder") + icon_css_class = 'fa fa-fw fa-folder-o' + + fields = field.Fields(ISiteFolderAddFormFields) + fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget + + ajax_handler = 'add-site-folder.json' + edit_permission = MANAGE_SITE_PERMISSION + + def updateWidgets(self, prefix=None): + super(SiteFolderAddForm, self).updateWidgets(prefix) + if 'parent' in self.widgets: + self.widgets['parent'].permission = MANAGE_SITE_PERMISSION + if 'notepad' in self.widgets: + self.widgets['notepad'].widget_css_class = 'textarea' + + def create(self, data): + manager = get_parent(self.context, ISiteManager) + return manager.folder_factory() + + def update_content(self, content, data): + content.title = data['title'] + content.short_name = data['title'] + content.notepad = data['notepad'] + intids = get_utility(IIntIds) + parent = intids.queryObject(data.get('parent')) + if parent is not None: + negotiator = get_utility(INegotiator) + title = II18n(content).get_attribute('title', lang=negotiator.server_language) + name = translate_string(title, force_lower=True, spaces='-') + if name in parent: + index = 1 + new_name = '{name}-{index:02}'.format(name=name, index=index) + while new_name in parent: + index += 1 + new_name = '{name}-{index:02}'.format(name=name, index=index) + name = new_name + parent[name] = content + + def add(self, content): + pass + + def nextURL(self): + return absolute_url(self.context, self.request, 'admin#site-tree.html') + + +@subscriber(IDataExtractedEvent, form_selector=SiteFolderAddForm) +def handle_site_folder_add_form_data_extraction(event): + """Handle site folder add form data extraction""" + negotiator = get_utility(INegotiator) + title = event.data.get('title', {}).get(negotiator.server_language) + if not title: + event.form.widgets.errors += (Invalid(_("You must provide a folder name for default server language!")),) + + +@view_config(name='add-site-folder.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True) +class SiteFolderAJAXAddForm(AJAXAddForm, SiteFolderAddForm): + """Site folder add form, JSON renderer""" + + def get_ajax_output(self, changes): + return {'status': 'reload'} diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/link.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/zmi/link.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,167 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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. +# +from pyramid.location import lineage + +from pyams_content.shared.site.zmi.container import SiteContainerTreeTable, SiteContainerTreeNameColumn +from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION + +__docformat__ = 'restructuredtext' + + +# import standard library +from uuid import uuid4 + +# import interfaces +from pyams_content.interfaces import CREATE_CONTENT_PERMISSION, MANAGE_CONTENT_PERMISSION +from pyams_content.shared.site.interfaces import ISiteContainer, IContentLink +from pyams_content.zmi.interfaces import ISiteTreeTable +from pyams_i18n.interfaces import II18n +from pyams_skin.interfaces.container import ITableElementName +from pyams_skin.interfaces.viewlet import IToolbarAddingMenu +from pyams_skin.layer import IPyAMSLayer +from pyams_zmi.layer import IAdminLayer +from zope.intid.interfaces import IIntIds + +# import packages +from pyams_content.shared.site.link import ContentLink +from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget +from pyams_form.form import AJAXAddForm, AJAXEditForm +from pyams_pagelet.pagelet import pagelet_config +from pyams_skin.table import get_object_name +from pyams_skin.viewlet.toolbar import ToolbarMenuItem +from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter +from pyams_utils.registry import get_utility +from pyams_utils.url import absolute_url +from pyams_viewlet.viewlet import viewlet_config +from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm +from pyramid.view import view_config +from z3c.form import field +from zope.interface import Interface +from zope.schema import Int + +from pyams_content import _ + + +@viewlet_config(name='add-content-link.menu', context=ISiteContainer, layer=IAdminLayer, view=Interface, + manager=IToolbarAddingMenu, permission=CREATE_CONTENT_PERMISSION, weight=90) +class ContentLinkAddMenu(ToolbarMenuItem): + """Content link add menu""" + + label = _("Rent content...") + label_css_class = 'fa fa-fw fa-external-link-square fa-rotate-90' + url = 'add-content-link.html' + modal_target = True + + +class IContentLinkAddFormFields(IContentLink): + """Content link add forms fields interface""" + + parent = Int(title=_("Parent"), + description=_("Folder's parent"), + required=True) + + +@pagelet_config(name='add-content-link.html', context=ISiteContainer, layer=IPyAMSLayer, + permission=CREATE_CONTENT_PERMISSION) +class ContentLinkAddForm(AdminDialogAddForm): + """Content link add form""" + + legend = _("Rent existing content") + + fields = field.Fields(IContentLinkAddFormFields).select('reference', 'alt_title', 'parent') + fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget + + ajax_handler = 'add-content-link.json' + edit_permission = CREATE_CONTENT_PERMISSION + + __target = None + + def updateWidgets(self, prefix=None): + super(ContentLinkAddForm, self).updateWidgets(prefix) + if 'parent' in self.widgets: + self.widgets['parent'].permission = CREATE_CONTENT_PERMISSION + + def create(self, data): + return ContentLink() + + def update_content(self, content, data): + content.reference = data.get('reference') + content.alt_title = data['alt_title'] + intids = get_utility(IIntIds) + parent = intids.queryObject(data.get('parent')) + if parent is not None: + uuid = str(uuid4()) + parent[uuid] = content + self.__target = parent + + def add(self, content): + pass + + def nextURL(self): + return absolute_url(self.__target, self.request, 'admin#site-tree.html') + + +@view_config(name='add-content-link.json', context=ISiteContainer, request_type=IPyAMSLayer, + permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True) +class ContentLinkAJAXAddForm(AJAXAddForm, ContentLinkAddForm): + """Content link add form, JSOn renderer""" + + +@adapter_config(context=(IContentLink, IPyAMSLayer, ISiteTreeTable), provides=ITableElementName) +class ContentLinkTableElementName(ContextRequestViewAdapter): + """Content link table element name""" + + @property + def name(self): + title = II18n(self.context).query_attribute('alt_title', request=self.request) + if not title: + target = self.context.get_target() + if target is not None: + title = get_object_name(target, self.request, self.view) + return '{title}'.format( + title=title or '--') + + +@pagelet_config(name='properties.html', context=IContentLink, layer=IPyAMSLayer, + permission=VIEW_SYSTEM_PERMISSION) +class ContentLinkPropertiesEditForm(AdminDialogEditForm): + """Content link properties edit form""" + + legend = _("Edit content link properties") + + fields = field.Fields(IContentLink).omit('__parent__', '__name__') + + ajax_handler = 'properties.json' + edit_permission = MANAGE_CONTENT_PERMISSION + + +@view_config(name='properties.json', context=IContentLink, request_type=IPyAMSLayer, + permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) +class ContentLinkPropertiesAJAXEditForm(AJAXEditForm, ContentLinkPropertiesEditForm): + """Content link properties edit form, JSON renderer""" + + def get_ajax_output(self, changes): + output = super(ContentLinkPropertiesAJAXEditForm, self).get_ajax_output(changes) + 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({ + 'event': 'myams.refresh', + 'options': { + 'handler': 'MyAMS.skin.refreshRowCell', + 'object_id': '{0}::{1}'.format(SiteContainerTreeTable.id, intids.queryId(self.context)), + 'col_name': 'name', + 'cell': column.renderCell(self.context, name=adapter.name) + } + }) + return output diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/manager.py --- a/src/pyams_content/shared/site/zmi/manager.py Sun Nov 26 09:57:42 2017 +0100 +++ b/src/pyams_content/shared/site/zmi/manager.py Sun Nov 26 09:58:07 2017 +0100 @@ -19,20 +19,20 @@ from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION from pyams_content.root.interfaces import ISiteRoot from pyams_content.shared.site.interfaces import ISiteManager -from pyams_content.zmi.interfaces import IUserAddingsMenuLabel +from pyams_content.zmi.interfaces import IUserAddingsMenuLabel, ISiteTreeTable from pyams_i18n.interfaces import II18n, INegotiator from pyams_skin.interfaces.container import ITableElementEditor -from pyams_skin.interfaces.viewlet import IToolbarAddingMenu +from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IBreadcrumbItem from pyams_skin.layer import IPyAMSLayer from pyams_zmi.layer import IAdminLayer from z3c.form.interfaces import IDataExtractedEvent # import packages -from pyams_content.shared.site.manager import SiteManager -from pyams_content.shared.zmi.sites import SiteTreeTable +from pyams_content.shared.site import WfTopic from pyams_form.form import AJAXAddForm from pyams_pagelet.pagelet import pagelet_config from pyams_skin.table import DefaultElementEditorAdapter +from pyams_skin.viewlet.breadcrumb import BreadcrumbItem from pyams_skin.viewlet.toolbar import ToolbarMenuItem from pyams_utils.adapter import adapter_config, ContextRequestAdapter from pyams_utils.registry import query_utility @@ -41,6 +41,7 @@ from pyams_viewlet.viewlet import viewlet_config from pyams_zmi.form import AdminDialogAddForm from pyramid.events import subscriber +from pyramid.path import DottedNameResolver from pyramid.view import view_config from z3c.form import field from zope.interface import Invalid @@ -48,19 +49,30 @@ from pyams_content import _ +@adapter_config(context=(ISiteManager, IPyAMSLayer), provides=IBreadcrumbItem) +class SiteManagerBreadcrumbAdapter(BreadcrumbItem): + """Site manager breadcrumb adapter""" + + @property + def label(self): + return II18n(self.context).query_attribute('short_name', request=self.request) + + css_class = 'strong' + + @adapter_config(context=(ISiteManager, IAdminLayer), provides=IUserAddingsMenuLabel) -class SiteManageruserAddingsMenuLabelAdapter(ContextRequestAdapter): +class SiteManagerUserAddingsMenuLabelAdapter(ContextRequestAdapter): """Site manager user addings menu label adapter""" @property def label(self): return '{content} ({blog})'.format( - content=self.request.localizer.translate(self.context.shared_content_factory.content_class.content_name), + content=self.request.localizer.translate(WfTopic.content_name), blog=II18n(self.context).query_attribute('title', request=self.request)) @viewlet_config(name='add-site-manager.menu', context=ISiteRoot, layer=IAdminLayer, - view=SiteTreeTable, manager=IToolbarAddingMenu, permission=MANAGE_SITE_ROOT_PERMISSION) + view=ISiteTreeTable, manager=IToolbarAddingMenu, permission=MANAGE_SITE_ROOT_PERMISSION) class SiteManagerAddMenu(ToolbarMenuItem): """Site manager add menu""" @@ -84,7 +96,11 @@ edit_permission = None def create(self, data): - return SiteManager() + factory = self.request.registry.settings.get('pyams_content.config.site_factory') + if factory is None: + factory = 'pyams_content.shared.site.manager.SiteManager' + factory = DottedNameResolver().resolve(factory) + return factory() def add(self, object): short_name = II18n(object).query_attribute('short_name', request=self.request) @@ -100,7 +116,7 @@ """Handle new site manager data extraction""" container = event.form.context negotiator = query_utility(INegotiator) - short_name = event.data['short_name'].get(negotiator.server_language) + short_name = event.data.get('short_name', {}).get(negotiator.server_language) if not short_name: event.form.widgets.errors += (Invalid(_("You must provide a short name for default server language!")),) return @@ -119,9 +135,9 @@ """Site manager add form, JSOn renderer""" -@adapter_config(context=(ISiteManager, IAdminLayer, SiteTreeTable), provides=ITableElementEditor) +@adapter_config(context=(ISiteManager, IAdminLayer, ISiteTreeTable), provides=ITableElementEditor) class SiteManagerTableElementEditor(DefaultElementEditorAdapter): """Site tree table element editor""" - view_name = 'admin' + view_name = 'admin#site-tree.html' modal_target = False diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/widget/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/zmi/widget/__init__.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,39 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 + +# import interfaces +from pyams_content.shared.site.zmi.widget.interfaces import ISiteManagerFoldersSelectorWidget +from pyams_skin.layer import IPyAMSLayer + +# import packages +from pyams_form.widget import widgettemplate_config +from z3c.form.browser.text import TextWidget +from z3c.form.widget import FieldWidget +from zope.interface import implementer_only + + +@widgettemplate_config(mode='input', template='templates/folders-input.pt', layer=IPyAMSLayer) +@implementer_only(ISiteManagerFoldersSelectorWidget) +class SiteManagerFoldersSelectorWidget(TextWidget): + """Site manager folders selector widget""" + + permission = None + + +def SiteManagerFoldersSelectorFieldWidget(field, request): + """IFieldWidget factory for TextWidget.""" + return FieldWidget(field, SiteManagerFoldersSelectorWidget(request)) diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/widget/interfaces.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/zmi/widget/interfaces.py Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,28 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 + +# import interfaces +from z3c.form.interfaces import ITextWidget + +# import packages +from zope.interface import Attribute + + +class ISiteManagerFoldersSelectorWidget(ITextWidget): + """Site manager folders selector widget interface""" + + permission = Attribute("Permission required to select a given node") diff -r 401794fc244b -r 8742c8ac126c src/pyams_content/shared/site/zmi/widget/templates/folders-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/site/zmi/widget/templates/folders-input.pt Sun Nov 26 09:58:07 2017 +0100 @@ -0,0 +1,23 @@ +
+ +
+