Added external links into sites
authorThierry Florac <thierry.florac@onf.fr>
Fri, 23 Nov 2018 13:25:51 +0100
changeset 1113 5f026f7b6ada
parent 1112 6f9d0e77f4ce
child 1114 20fbecad8cf4
Added external links into sites
src/pyams_content/shared/site/interfaces.py
src/pyams_content/shared/site/link.py
src/pyams_content/shared/site/zmi/container.py
src/pyams_content/shared/site/zmi/link.py
--- a/src/pyams_content/shared/site/interfaces.py	Fri Nov 23 13:23:22 2018 +0100
+++ b/src/pyams_content/shared/site/interfaces.py	Fri Nov 23 13:25:51 2018 +0100
@@ -17,8 +17,8 @@
 from zope.annotation.interfaces import IAttributeAnnotatable
 from zope.container.constraints import containers, contains
 from zope.container.interfaces import IContained, IContainer
-from zope.interface import Attribute, Interface
-from zope.schema import Bool, Choice, Text
+from zope.interface import Attribute, Interface, invariant
+from zope.schema import Bool, Choice, Text, URI
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
 from pyams_content.interfaces import IBaseContent
@@ -159,8 +159,8 @@
     """Workflow managed site topic interface"""
 
 
-class IContentLink(ISiteElement, IInternalReference, IAttributeAnnotatable):
-    """Rented content interface"""
+class ISiteLink(ISiteElement):
+    """Site link interface"""
 
     navigation_title = I18nTextLineField(title=_("Navigation title"),
                                          description=_("Alternate content's title displayed in navigation pages; "
@@ -176,3 +176,15 @@
                    description=_("If 'no', link is not visible"),
                    required=True,
                    default=True)
+
+
+class IContentLink(ISiteLink, IInternalReference, IAttributeAnnotatable):
+    """Content link interface"""
+
+
+class IExternalContentLink(ISiteLink, IAttributeAnnotatable):
+    """External link interface"""
+
+    url = URI(title=_("Target URL"),
+              description=_("URL used to access external resource"),
+              required=True)
--- a/src/pyams_content/shared/site/link.py	Fri Nov 23 13:23:22 2018 +0100
+++ b/src/pyams_content/shared/site/link.py	Fri Nov 23 13:25:51 2018 +0100
@@ -17,7 +17,8 @@
 from zope.interface import implementer
 from zope.schema.fieldproperty import FieldProperty
 
-from pyams_content.shared.site.interfaces import IContentLink, ISiteElementNavigation
+from pyams_content.component.illustration import ILinkIllustrationTarget
+from pyams_content.shared.site.interfaces import IContentLink, IExternalContentLink, ISiteElementNavigation, ISiteLink
 from pyams_sequence.reference import get_reference_target
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.adapter import ContextRequestAdapter, adapter_config
@@ -28,8 +29,25 @@
 from pyams_content import _
 
 
+@implementer(ISiteLink)
+class SiteLink(Persistent, Contained):
+    """Site link persistent class"""
+
+    navigation_title = FieldProperty(IContentLink['navigation_title'])
+    navigation_header = FieldProperty(IContentLink['navigation_header'])
+    visible = FieldProperty(IContentLink['visible'])
+
+    @staticmethod
+    def is_deletable():
+        return True
+
+
+#
+# Internal content link
+#
+
 @implementer(IContentLink)
-class ContentLink(Persistent, Contained):
+class ContentLink(SiteLink):
     """Content link persistent class
 
     A 'content link' is a link to another content, which may be stored anywhere (same site,
@@ -37,16 +55,9 @@
     """
 
     reference = FieldProperty(IContentLink['reference'])
-    navigation_title = FieldProperty(IContentLink['navigation_title'])
-    navigation_header = FieldProperty(IContentLink['navigation_header'])
-    visible = FieldProperty(IContentLink['visible'])
 
     content_name = _("Content link")
 
-    @staticmethod
-    def is_deletable():
-        return True
-
     @volatile_property
     def target(self):
         target = get_reference_target(self.reference)
@@ -98,3 +109,25 @@
     target = context.get_target()
     if target is not None:
         return IWorkflowPublicationInfo(target, None)
+
+
+#
+# External content link
+#
+
+@implementer(IExternalContentLink, ILinkIllustrationTarget)
+class ExternalContentLink(SiteLink):
+    """External link persistent class"""
+
+    url = FieldProperty(IExternalContentLink['url'])
+
+    content_name = _("External content link")
+
+
+@adapter_config(context=(IExternalContentLink, IPyAMSLayer), provides=ISiteElementNavigation)
+class ExternalContentLinkNavigationAdapter(ContextRequestAdapter):
+    """External content link navigation adapter"""
+
+    @property
+    def visible(self):
+        return self.context.visible
--- a/src/pyams_content/shared/site/zmi/container.py	Fri Nov 23 13:23:22 2018 +0100
+++ b/src/pyams_content/shared/site/zmi/container.py	Fri Nov 23 13:25:51 2018 +0100
@@ -32,7 +32,7 @@
     SharedToolDashboardStatusColumn, SharedToolDashboardStatusDateColumn, SharedToolDashboardStatusPrincipalColumn, \
     SharedToolDashboardVersionColumn
 from pyams_content.shared.site import WfSiteTopic
-from pyams_content.shared.site.interfaces import IBaseSiteItem, IContentLink, ISiteContainer, ISiteManager
+from pyams_content.shared.site.interfaces import IBaseSiteItem, IContentLink, ISiteContainer, ISiteManager, ISiteLink
 from pyams_content.zmi import pyams_content
 from pyams_content.zmi.interfaces import ISiteTreeMenu, ISiteTreeTable, IUserAddingsMenuLabel
 from pyams_form.form import ajax_config
@@ -311,7 +311,7 @@
     weight = 5
 
     def get_icon(self, item):
-        if IContentLink.providedBy(item):
+        if ISiteLink.providedBy(item):
             icon_class = 'fa-eye' if item.visible else 'fa-eye-slash'
             if not IWorkflowPublicationInfo(item.__parent__).is_published():
                 icon_class += ' text-danger'
@@ -334,13 +334,13 @@
 
     def get_icon_hint(self, item):
         translate = self.request.localizer.translate
-        if IContentLink.providedBy(item) and self.request.has_permission(self.permission, context=item):
+        if ISiteLink.providedBy(item) and self.request.has_permission(self.permission, context=item):
             return translate(self.active_icon_hint)
         else:
             return translate(self.inactive_icon_hint)
 
     def renderCell(self, item):
-        if IContentLink.providedBy(item) and self.request.has_permission(self.permission, context=item):
+        if ISiteLink.providedBy(item) and self.request.has_permission(self.permission, context=item):
             return super(SiteContainerTreeVisibleColumn, self).renderCell(item)
         else:
             return self.get_icon(item)
@@ -352,7 +352,7 @@
     """Switch content link visibility"""
     container = ISiteContainer(request.context)
     content = container.get(str(request.params.get('object_name')))
-    if not IContentLink.providedBy(content):
+    if not ISiteLink.providedBy(content):
         raise NotFound()
     content.visible = not content.visible
     return {
@@ -389,12 +389,12 @@
                '        <i class="fa fa-fw {switch}"></i>' \
                '    </span>&nbsp;&nbsp;<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))
+                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='content-type', context=(IBaseSiteItem, IPyAMSLayer, ISiteTreeTable), provides=IColumn)
--- a/src/pyams_content/shared/site/zmi/link.py	Fri Nov 23 13:23:22 2018 +0100
+++ b/src/pyams_content/shared/site/zmi/link.py	Fri Nov 23 13:25:51 2018 +0100
@@ -15,13 +15,13 @@
 from uuid import uuid4
 
 from z3c.form import field
-from zope.interface import Interface
+from zope.interface import Interface, implementer
 from zope.intid.interfaces import IIntIds
 from zope.schema import Int
 
 from pyams_content.interfaces import CREATE_CONTENT_PERMISSION, MANAGE_CONTENT_PERMISSION
-from pyams_content.shared.site.interfaces import IContentLink, ISiteContainer
-from pyams_content.shared.site.link import ContentLink
+from pyams_content.shared.site.interfaces import IContentLink, IExternalContentLink, ISiteContainer
+from pyams_content.shared.site.link import ContentLink, ExternalContentLink
 from pyams_content.shared.site.zmi.container import SiteContainerTreeTable
 from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget
 from pyams_content.zmi.interfaces import ISiteTreeTable
@@ -32,6 +32,7 @@
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from pyams_skin.table import get_object_name
+from pyams_skin.viewlet.menu import MenuDivider
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
@@ -39,11 +40,22 @@
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.interfaces import IPropertiesEditForm
 from pyams_zmi.layer import IAdminLayer
 
 from pyams_content import _
 
 
+@viewlet_config(name='add-link.divider', context=ISiteContainer, layer=IAdminLayer, view=Interface,
+                manager=IToolbarAddingMenu, permission=CREATE_CONTENT_PERMISSION, weight=89)
+class AddLinkMenuDivider(MenuDivider):
+    """Add links menu divider"""
+
+
+#
+# Content link views
+#
+
 @viewlet_config(name='add-content-link.menu', context=ISiteContainer, layer=IAdminLayer, view=Interface,
                 manager=IToolbarAddingMenu, permission=CREATE_CONTENT_PERMISSION, weight=90)
 class ContentLinkAddMenu(ToolbarMenuItem):
@@ -134,7 +146,7 @@
 
     legend = _("Edit content link properties")
 
-    fields = field.Fields(IContentLink).omit('__parent__', '__name__', 'visible')
+    fields = field.Fields(IContentLink).select('reference', 'navigation_title', 'navigation_header')
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def get_ajax_output(self, changes):
@@ -153,3 +165,118 @@
                 }
             })
         return output
+
+
+#
+# External content link views
+#
+
+@viewlet_config(name='add-external-link.menu', context=ISiteContainer, layer=IAdminLayer, view=Interface,
+                manager=IToolbarAddingMenu, permission=CREATE_CONTENT_PERMISSION, weight=91)
+class ExternalContentLinkAddMenu(ToolbarMenuItem):
+    """External content link add menu"""
+
+    label = _("External content link...")
+    label_css_class = 'fa fa-fw fa-external-link'
+    url = 'add-external-link.html'
+    modal_target = True
+
+
+class IExternalContentLinkAddFormFields(IExternalContentLink):
+    """External content link add forms fields interface"""
+
+    parent = Int(title=_("Parent"),
+                 description=_("Folder's parent"),
+                 required=True)
+
+
+@pagelet_config(name='add-external-link.html', context=ISiteContainer, layer=IPyAMSLayer,
+                permission=CREATE_CONTENT_PERMISSION)
+@ajax_config(name='add-external-link.json', context=ISiteContainer, layer=IPyAMSLayer, base=AJAXAddForm)
+class ExternalContentLinkAddForm(AdminDialogAddForm):
+    """External content link add form"""
+
+    legend = _("Link external content")
+
+    fields = field.Fields(IExternalContentLinkAddFormFields).select('url', 'navigation_title',
+                                                                    'navigation_header', 'parent')
+    fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget
+
+    edit_permission = CREATE_CONTENT_PERMISSION
+
+    __target = None
+
+    def updateWidgets(self, prefix=None):
+        super(ExternalContentLinkAddForm, self).updateWidgets(prefix)
+        if 'parent' in self.widgets:
+            self.widgets['parent'].permission = CREATE_CONTENT_PERMISSION
+
+    def create(self, data):
+        return ExternalContentLink()
+
+    def update_content(self, content, data):
+        data = data.get(self, data)
+        content.url = data.get('url')
+        content.navigation_title = data['navigation_title']
+        content.navigation_header = data['navigation_header']
+        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')
+
+    def get_ajax_output(self, changes):
+        return {'status': 'reload'}
+
+
+@adapter_config(context=(IExternalContentLink, IPyAMSLayer, ISiteTreeTable), provides=ITableElementName)
+class ExternalContentLinkTableElementName(ContextRequestViewAdapter):
+    """External content link table element name"""
+
+    @property
+    def name(self):
+        title = II18n(self.context).query_attribute('navigation_title', request=self.request)
+        if not title:
+            title = self.context.url
+        return '<i class="fa fa-fw fa-external-link"></i>{title}'.format(
+            title=title or '--')
+
+
+@pagelet_config(name='properties.html', context=IExternalContentLink, layer=IPyAMSLayer,
+                permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IExternalContentLink, layer=IPyAMSLayer)
+@implementer(IPropertiesEditForm)
+class ExternalContentLinkPropertiesEditForm(AdminDialogEditForm):
+    """External content link properties edit form"""
+
+    prefix = 'link_properties.'
+
+    legend = _("Edit external content link properties")
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(IExternalContentLink).select('url', 'navigation_title', 'navigation_header')
+    edit_permission = MANAGE_CONTENT_PERMISSION
+
+    def get_ajax_output(self, changes):
+        output = super(self.__class__, self).get_ajax_output(changes)
+        intids = get_utility(IIntIds)
+        if changes:
+            table = SiteContainerTreeTable(self.context.__parent__, self.request)
+            table.update()
+            row = table.setUpRow(self.context)
+            output.setdefault('events', []).append({
+                'event': 'myams.refresh',
+                'options': {
+                    'handler': 'MyAMS.skin.refreshRow',
+                    'object_id': '{0}::{1}'.format(SiteContainerTreeTable.id, intids.queryId(self.context)),
+                    'row': table.renderRow(row)
+                }
+            })
+        return output