--- /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