# HG changeset patch # User Thierry Florac # Date 1542975951 -3600 # Node ID 5f026f7b6ada0a88c33d7e43ffe57041487f51a1 # Parent 6f9d0e77f4ce41aef083c137856ffcafe75fe12d Added external links into sites diff -r 6f9d0e77f4ce -r 5f026f7b6ada src/pyams_content/shared/site/interfaces.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) diff -r 6f9d0e77f4ce -r 5f026f7b6ada src/pyams_content/shared/site/link.py --- 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 diff -r 6f9d0e77f4ce -r 5f026f7b6ada src/pyams_content/shared/site/zmi/container.py --- 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 @@ ' ' \ '   {title}' \ ''.format( - padding='' * depth, - hint=self.request.localizer.translate(_("Click to show/hide inner folders")), - switch='fa-{state}-square-o switch'.format( - state=self.table.rows_state or ('minus' if item in lineage(self.context) else 'plus')) - if ISiteContainer.providedBy(item) else '', - title=name or super(SiteContainerTreeNameColumn, self).renderCell(item)) + padding='' * depth, + hint=self.request.localizer.translate(_("Click to show/hide inner folders")), + switch='fa-{state}-square-o switch'.format( + state=self.table.rows_state or ('minus' if item in lineage(self.context) else 'plus')) + if ISiteContainer.providedBy(item) else '', + title=name or super(SiteContainerTreeNameColumn, self).renderCell(item)) @adapter_config(name='content-type', context=(IBaseSiteItem, IPyAMSLayer, ISiteTreeTable), provides=IColumn) diff -r 6f9d0e77f4ce -r 5f026f7b6ada src/pyams_content/shared/site/zmi/link.py --- 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 '{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