--- 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+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)
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# 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)
--- 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"""
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# 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)
--- 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"""
--- 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"""
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+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 = '<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(_("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 '<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))
+
+
+@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 '<span data-ams-stop-propagation="true"' \
+ ' data-ams-click-handler="MyAMS.tree.switchTree">' \
+ ' <span class="small hint" title="{hint}" data-ams-hint-gravity="e">' \
+ ' <i class="fa fa-fw fa-plus-square-o switch"></i>' \
+ ' </span> {title}' \
+ '</span>'.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 '<div class="name">' \
+ ' {padding}' \
+ ' <span class="small hint" title="{hint}" data-ams-hint-gravity="e" ' \
+ ' data-ams-click-handler="MyAMS.tree.switchTableNode"' \
+ ' data-ams-stop-propagation="true">' \
+ ' <i class="fa fa-fw {switch}"></i>' \
+ ' </span> <span class="title">{title}</span>' \
+ '</div>'.format(
+ padding='<span class="tree-node-padding"></span>' * 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)
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# 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'}
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+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 '<i class="fa fa-fw fa-external-link-square rotate-90"></i>{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
--- 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# 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))
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# 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")
--- /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 @@
+<div i18n:domain="pyams_content"
+ data-ams-plugins="pyams_content"
+ tal:attributes="data-ams-plugin-pyams_content-src extension:resource_path('pyams_content.skin:pyams_content')">
+ <input type="hidden"
+ tal:attributes="id view/id;
+ name view/name;
+ class view/klass;
+ lang view/lang;
+ value view/value;
+ disabled view/disabled;
+ onchange view/onchange;
+ readonly view/readonly;
+ size view/size;
+ maxlength view/maxlength;" />
+ <div class="treeview bordered padding-5"
+ tal:attributes="id string:${view/id}_treeview;
+ data-ams-treeview-data python:context.get_folders_tree(permission=view.permission);"
+ data-ams-treeview-show-border="false"
+ data-ams-treeview-levels="3"
+ data-ams-treeview-toggle-unselectable="false"
+ data-ams-treeview-node-selected="PyAMS_content.widget.treeview.selectFolder"
+ data-ams-treeview-node-unselected="PyAMS_content.widget.treeview.unselectFolder"></div>
+</div>