Added base blog management classes
authorThierry Florac <thierry.florac@onf.fr>
Mon, 20 Jun 2016 12:25:03 +0200
changeset 42 bcb01961928e
parent 41 48aa4de90de2
child 43 2136e95fb9a5
Added base blog management classes
src/pyams_content/shared/blog/__init__.py
src/pyams_content/shared/blog/interfaces/__init__.py
src/pyams_content/shared/blog/manager.py
src/pyams_content/shared/blog/zmi/__init__.py
src/pyams_content/shared/blog/zmi/manager.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/blog/__init__.py	Mon Jun 20 12:25:03 2016 +0200
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2008-2016 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.component.extfile.interfaces import IExtFileContainerTarget
+from pyams_content.component.gallery.interfaces import IGalleryContainerTarget
+from pyams_content.component.links.interfaces import ILinkContainerTarget
+from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget
+from pyams_content.component.theme.interfaces import IThemesTarget
+from pyams_content.shared.blog.interfaces import IWfBlogPost, BLOG_CONTENT_TYPE, BLOG_CONTENT_NAME, IBlogPost
+
+# import packages
+from pyams_content.shared.common import WfSharedContent, register_content_type, SharedContent
+from zope.interface import implementer
+
+
+@implementer(IWfBlogPost, IParagraphContainerTarget, IThemesTarget, IExtFileContainerTarget, ILinkContainerTarget,
+             IGalleryContainerTarget)
+class WfBlogPost(WfSharedContent):
+    """Base blog post"""
+
+    content_type = BLOG_CONTENT_TYPE
+    content_name = BLOG_CONTENT_NAME
+
+register_content_type(WfBlogPost)
+
+
+@implementer(IBlogPost)
+class BlogPost(SharedContent):
+    """Worfklow managed blog post class"""
+
+    content_class = WfBlogPost
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/blog/interfaces/__init__.py	Mon Jun 20 12:25:03 2016 +0200
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2008-2016 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.common.interfaces import ISharedSite, ISharedTool, IWfSharedContent, ISharedContent
+from zope.container.interfaces import IContainer
+
+# import packages
+
+from pyams_content import _
+
+
+BLOG_CONTENT_TYPE = 'blog'
+BLOG_CONTENT_NAME = _("Blog post")
+
+
+class IBlogManager(ISharedSite, ISharedTool):
+    """Blog manager interface"""
+
+
+class IWfBlogPost(IWfSharedContent):
+    """Blog topic interface"""
+
+
+class IBlogFolder(IContainer):
+    """Blog folder interface"""
+
+
+class IBlogPost(ISharedContent):
+    """Workflow managed blog post interface"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/blog/manager.py	Mon Jun 20 12:25:03 2016 +0200
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2008-2016 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.component.theme.interfaces import IThemesManagerTarget
+from pyams_content.shared.blog.interfaces import IBlogManager, BLOG_CONTENT_TYPE, IBlogFolder
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.component.interfaces import ISite
+from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+
+# import packages
+from pyams_content.shared.blog import BlogPost
+from pyams_content.shared.common.manager import SharedTool
+from pyams_utils.traversing import get_parent
+from pyramid.events import subscriber
+from zope.container.folder import Folder
+from zope.interface import implementer
+
+
+@implementer(IBlogFolder)
+class BlogFolder(Folder):
+    """Blog folder class"""
+
+
+@implementer(IBlogManager, IThemesManagerTarget, IAttributeAnnotatable)
+class BlogManager(SharedTool):
+    """Nlog manager class"""
+
+    shared_content_type = BLOG_CONTENT_TYPE
+    shared_content_factory = BlogPost
+
+
+@subscriber(IObjectAddedEvent, context_selector=IBlogManager)
+def handle_added_blog_manager(event):
+    """Register blog manager when added"""
+    site = get_parent(event.newParent, ISite)
+    registry = site.getSiteManager()
+    if registry is not None:
+        registry.registerUtility(event.object, IBlogManager, name=event.object.__name__)
+
+
+@subscriber(IObjectRemovedEvent, context_selector=IBlogManager)
+def handle_deleted_blog_manager(event):
+    """Un-register blog manager when deleted"""
+    site = get_parent(event.oldParent, ISite)
+    registry = site.getSiteManager()
+    if registry is not None:
+        registry.unregisterUtility(event.object, IBlogManager, name=event.object.__name__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/blog/zmi/__init__.py	Mon Jun 20 12:25:03 2016 +0200
@@ -0,0 +1,118 @@
+#
+# Copyright (c) 2008-2016 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
+from datetime import datetime
+
+# import interfaces
+from pyams_content.interfaces import CREATE_CONTENT_PERMISSION
+from pyams_content.shared.blog.interfaces import IWfBlogPost, IBlogManager
+from pyams_i18n.interfaces import II18n, II18nManager
+from pyams_skin.interfaces.viewlet import IMenuHeader, IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowInfo
+from pyams_zmi.interfaces.menu import IContentManagementMenu
+from pyams_zmi.layer import IAdminLayer
+
+# import packages
+from pyams_content.shared.blog.manager import BlogFolder
+from pyams_content.shared.common.zmi import SharedContentAddForm, SharedContentAJAXAddForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.interfaces import IContentTitle
+from pyams_skin.viewlet.toolbar import ToolbarAction
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextRequestAdapter
+from pyams_utils.unicode import translate_string
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyramid.view import view_config
+from zope.interface import Interface
+from zope.lifecycleevent import ObjectCreatedEvent
+
+from pyams_content import _
+
+
+@adapter_config(context=(IWfBlogPost, IContentManagementMenu), provides=IMenuHeader)
+class BlogPostContentMenuHeader(ContextRequestAdapter):
+    """Blog post content menu header adapter"""
+
+    header = _("This blog post")
+
+
+@adapter_config(context=(IWfBlogPost, IPyAMSLayer, Interface), provides=IContentTitle)
+class BlogPostTitleAdapter(ContextRequestViewAdapter):
+    """Blog post title adapter"""
+
+    @property
+    def title(self):
+        translate = self.request.localizer.translate
+        return translate(_("Blog post « {title} »")).format(
+            title=II18n(self.context).query_attribute('title', request=self.request))
+
+
+@viewlet_config(name='add-shared-content.action', context=IBlogManager, layer=IAdminLayer, view=Interface,
+                manager=IWidgetTitleViewletManager, permission=CREATE_CONTENT_PERMISSION, weight=1)
+class BlogPostAddAction(ToolbarAction):
+    """Blog post adding action"""
+
+    label = _("Add blog post")
+    url = 'add-shared-content.html'
+    modal_target = True
+
+
+@pagelet_config(name='add-shared-content.html', context=IBlogManager, layer=IPyAMSLayer,
+                permission=CREATE_CONTENT_PERMISSION)
+class BlogPostAddForm(SharedContentAddForm):
+    """Blog post add form"""
+
+    legend = _("Add blog post")
+    content_url = None
+
+    def add(self, wf_content):
+        # create shared content
+        content = self.context.shared_content_factory()
+        self.request.registry.notify(ObjectCreatedEvent(content))
+        # check blog folders
+        now = datetime.utcnow()
+        year, month = now.strftime('%Y:%m').split(':')
+        year_folder = self.context.get(year)
+        if year_folder is None:
+            year_folder = self.context[year] = BlogFolder()
+        month_folder = year_folder.get(month)
+        if month_folder is None:
+            month_folder = year_folder[month] = BlogFolder()
+        # check title language
+        added = False
+        i18n_manager = II18nManager(self.context)
+        for lang in i18n_manager.get_languages():
+            title = translate_string(wf_content.title.get(lang))
+            if title:
+                content_name = translate_string(title, force_lower=True, spaces='-')
+                month_folder[content_name] = content
+                added = True
+                break
+        # handle workflow
+        if added:
+            IWorkflowVersions(content).add_version(wf_content, None)
+            IWorkflowInfo(wf_content).fire_transition('init')
+            self.content_url = absolute_url(wf_content, self.request, 'admin.html')
+
+    def nextURL(self):
+        return self.content_url
+
+
+@view_config(name='add-shared-content.json', context=IBlogManager, request_type=IPyAMSLayer,
+             permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
+class BlogPostAJAXAddForm(SharedContentAJAXAddForm, BlogPostAddForm):
+    """Blog post add form, JSON renderer"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/blog/zmi/manager.py	Mon Jun 20 12:25:03 2016 +0200
@@ -0,0 +1,127 @@
+#
+# Copyright (c) 2008-2016 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
+from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION
+from pyams_content.root.interfaces import ISiteRoot
+from pyams_content.shared.blog.interfaces import IBlogManager
+from pyams_content.zmi.interfaces import IUserAddingsMenuLabel
+from pyams_i18n.interfaces import II18n, INegotiator
+from pyams_skin.interfaces.container import ITableElementEditor
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.layer import IAdminLayer
+
+# import interfaces
+from z3c.form.interfaces import IDataExtractedEvent
+
+# import packages
+from pyams_content.shared.blog.manager import BlogManager
+from pyams_content.shared.zmi.sites import SiteTreeTable
+from pyams_form.form import AJAXAddForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.table import DefaultElementEditorAdapter
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem
+from pyams_utils.adapter import adapter_config, ContextRequestAdapter
+from pyams_utils.registry import query_utility
+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 Invalid
+
+from pyams_content import _
+
+
+@adapter_config(context=(IBlogManager, IAdminLayer), provides=IUserAddingsMenuLabel)
+class BlogManagerUserAddingsMenuLabelAdapter(ContextRequestAdapter):
+    """Blog 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),
+            blog=II18n(self.context).query_attribute('title', request=self.request))
+
+
+@viewlet_config(name='add-blog-manager.menu', context=ISiteRoot, layer=IAdminLayer,
+                view=SiteTreeTable, manager=IToolbarAddingMenu, permission=MANAGE_SITE_ROOT_PERMISSION)
+class BlogManagerAddMenu(ToolbarMenuItem):
+    """Blog manager add menu"""
+
+    label = _("Add blog manager")
+    label_css_class = 'fa fa-fw fa-tags'
+    url = 'add-blog-manager.html'
+    modal_target = True
+
+
+@pagelet_config(name='add-blog-manager.html', context=ISiteRoot, layer=IPyAMSLayer,
+                permission=MANAGE_SITE_ROOT_PERMISSION)
+class BlogManagerAddForm(AdminDialogAddForm):
+    """Blog manager add form"""
+
+    title = _("Blog manager")
+    legend = _("Add blog manager")
+    icon_css_class = 'fa fa-fw fa-tags'
+
+    fields = field.Fields(IBlogManager).select('title', 'short_name')
+    ajax_handler = 'add-blog-manager.json'
+    edit_permission = None
+
+    def create(self, data):
+        return BlogManager()
+
+    def add(self, object):
+        short_name = II18n(object).query_attribute('short_name', request=self.request)
+        name = translate_string(short_name, force_lower=True, spaces='-')
+        self.context[name] = object
+
+    def nextURL(self):
+        return absolute_url(self.context, self.request, 'site-tree.html')
+
+
+@subscriber(IDataExtractedEvent, form_selector=BlogManagerAddForm)
+def handle_new_blog_manager_data_extraction(event):
+    """Handle new blog manager data extraction"""
+    container = event.form.context
+    negotiator = query_utility(INegotiator)
+    short_name = event.data['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
+    name = translate_string(short_name, force_lower=True, spaces='-')
+    if name in container:
+        event.form.widgets.errors += (Invalid(_("Specified blog manager name is already used!")),)
+        return
+    blog = query_utility(IBlogManager, name=short_name)
+    if blog is not None:
+        event.form.widgets.errors += (Invalid(_("A blog manager is already registered with this name!!")),)
+
+
+@view_config(name='add-blog-manager.json', context=ISiteRoot, request_type=IPyAMSLayer,
+             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
+class BlogManagerAJAXAddForm(AJAXAddForm, BlogManagerAddForm):
+    """Blog manager add form, JSON renderer"""
+
+
+@adapter_config(context=(IBlogManager, IAdminLayer, SiteTreeTable), provides=ITableElementEditor)
+class SiteTreeTableElementEditor(DefaultElementEditorAdapter):
+    """Site tree table element editor"""
+
+    view_name = 'admin.html'
+    modal_target = False