--- a/src/pyams_content/component/paragraph/zmi/container.py Thu Oct 15 15:42:01 2015 +0200
+++ b/src/pyams_content/component/paragraph/zmi/container.py Mon Jan 18 16:08:07 2016 +0100
@@ -9,6 +9,7 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
+from pyams_form.interfaces.form import IFormSecurityContext
__docformat__ = 'restructuredtext'
@@ -18,10 +19,11 @@
# import interfaces
from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_content.component.extfile.interfaces import IExtFileLinksContainerTarget
-from pyams_content.component.gallery.interfaces import IGalleryLinksContainerTarget
-from pyams_content.component.links.interfaces import ILinkLinksContainerTarget
-from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer
+from pyams_content.component.extfile.interfaces import IExtFileLinksContainerTarget, IExtFileLinksContainer
+from pyams_content.component.gallery.interfaces import IGalleryLinksContainerTarget, IGalleryLinksContainer
+from pyams_content.component.links.interfaces import ILinkLinksContainerTarget, ILinkLinksContainer
+from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer, IBaseParagraph
+from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor
from pyams_i18n.interfaces import II18n
from pyams_skin.interfaces import IInnerPage, IPageHeader
from pyams_skin.layer import IPyAMSLayer
@@ -35,11 +37,12 @@
from pyams_form.security import ProtectedFormObjectMixin
from pyams_pagelet.pagelet import pagelet_config
from pyams_skin.page import DefaultPageHeaderAdapter
-from pyams_skin.table import BaseTable, I18nColumn, TrashColumn, ActionColumn
+from pyams_skin.table import BaseTable, I18nColumn, TrashColumn, ActionColumn, JsActionColumn
from pyams_skin.viewlet.menu import MenuItem
from pyams_template.template import template_config
-from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextAdapter
from pyams_utils.url import absolute_url
+from pyramid.exceptions import NotFound
from pyramid.view import view_config
from pyams_viewlet.viewlet import viewlet_config
from pyams_zmi.view import AdminView
@@ -64,7 +67,8 @@
# Paragraphs container view
#
-@pagelet_config(name='paragraphs.html', context=IParagraphContainerTarget, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@pagelet_config(name='paragraphs.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
@template_config(template='templates/container.pt', layer=IPyAMSLayer)
@implementer(IInnerPage)
class ParagraphContainerView(AdminView):
@@ -103,10 +107,12 @@
@property
def data_attributes(self):
attributes = super(ParagraphContainerTable, self).data_attributes
- del attributes['tr']['data-ams-url']
- del attributes['tr']['data-toggle']
attributes['table'] = {'id': self.id,
+ 'data-ams-plugins': 'pyams_content',
+ 'data-ams-plugin-pyams_content-src':
+ '/--static--/pyams_content/js/pyams_content{MyAMS.devext}.js',
'data-ams-location': absolute_url(IParagraphContainer(self.context), self.request),
+ 'data-ams-tablednd-drag-handle': 'td.sorter',
'data-ams-tablednd-drop-target': 'set-paragraphs-order.json'}
return attributes
@@ -121,19 +127,58 @@
return super(ParagraphContainerTable, self).render()
-@adapter_config(name='properties', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
+@adapter_config(name='sorter', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
provides=IColumn)
-class ParagraphContainerPropertiesColumn(ActionColumn):
- """Paragraphs container properties column"""
+class ParagraphContainerSorterColumn(ProtectedFormObjectMixin, ActionColumn):
+ """Paragraphs container sorter column"""
+
+ cssClasses = {'th': 'action',
+ 'td': 'action sorter'}
+
+ icon_class = 'fa fa-fw fa-sort'
+ icon_hint = _("Click and drag to sort paragraphs...")
+
+ url = '#'
+ weight = 1
- icon_class = 'fa fa-fw fa-edit'
- icon_hint = _("Paragraph properties")
+ def get_url(self, item):
+ return '#'
+
- url = 'properties.html'
- modal_target = True
+@adapter_config(name='show-hide', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
+ provides=IColumn)
+class ParagraphContainerShowHideColumn(ProtectedFormObjectMixin, JsActionColumn):
+ """Paragraphs container visibility switcher column"""
+
+ cssClasses = {'th': 'action',
+ 'td': 'action switcher'}
+
+ icon_class = 'fa fa-fw fa-eye'
+ icon_hint = _("Switch paragraph visibility")
+
+ url = 'PyAMS_content.paragraphs.switchVisibility'
weight = 5
+ def get_icon(self, item):
+ if item.visible:
+ icon_class = 'fa fa-fw fa-eye'
+ else:
+ icon_class = 'fa fa-fw fa-eye-slash text-danger'
+ return '<i class="{icon_class}"></i>'.format(icon_class=icon_class)
+
+ def renderCell(self, item):
+ if self.permission and not self.request.has_permission(self.permission, context=item):
+ return self.get_icon(item)
+ else:
+ return super(ParagraphContainerShowHideColumn, self).renderCell(item)
+
+
+@adapter_config(context=ParagraphContainerShowHideColumn, provides=IFormSecurityContext)
+def ShowHideColumnSecurityContextFactory(column):
+ """Show/hide column security context factory"""
+ return column.table.context
+
@adapter_config(name='files', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
provides=IColumn)
@@ -143,6 +188,8 @@
icon_class = 'fa fa-fw fa-file-text-o'
icon_hint = _("External files")
+ cssClasses = {'td': 'action extfiles nowrap'}
+
url = 'extfile-links.html'
modal_target = True
@@ -151,7 +198,14 @@
def renderCell(self, item):
if not IExtFileLinksContainerTarget.providedBy(item):
return ''
- return super(ParagraphContainerExtFileLinksColumn, self).renderCell(item)
+ action = '{action} <span class="count">{{count}}</span>'.format(
+ action=super(ParagraphContainerExtFileLinksColumn, self).renderCell(item))
+ length = len(IExtFileLinksContainer(item).files or ())
+ if length:
+ action = action.format(count='({0})'.format(length))
+ else:
+ action = action.format(count='')
+ return action
@adapter_config(name='links', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
@@ -162,6 +216,8 @@
icon_class = 'fa fa-fw fa-link'
icon_hint = _("Useful links")
+ cssClasses = {'td': 'action links nowrap'}
+
url = 'link-links.html'
modal_target = True
@@ -170,7 +226,14 @@
def renderCell(self, item):
if not ILinkLinksContainerTarget.providedBy(item):
return ''
- return super(ParagraphContainerLinkLinksColumn, self).renderCell(item)
+ action = '{action} <span class="count">{{count}}</span>'.format(
+ action=super(ParagraphContainerLinkLinksColumn, self).renderCell(item))
+ length = len(ILinkLinksContainer(item).links or ())
+ if length:
+ action = action.format(count='({0})'.format(length))
+ else:
+ action = action.format(count='')
+ return action
@adapter_config(name='gallery', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
@@ -181,6 +244,8 @@
icon_class = 'fa fa-fw fa-picture-o'
icon_hint = _("Images galleries")
+ cssClasses = {'td': 'action galleries nowrap'}
+
url = 'gallery-links.html'
modal_target = True
@@ -189,7 +254,14 @@
def renderCell(self, item):
if not IGalleryLinksContainerTarget.providedBy(item):
return ''
- return super(ParagraphContainerGalleryLinksColumn, self).renderCell(item)
+ action = '{action} <span class="count">{{count}}</span>'.format(
+ action=super(ParagraphContainerGalleryLinksColumn, self).renderCell(item))
+ length = len(IGalleryLinksContainer(item).galleries or ())
+ if length:
+ action = action.format(count='({0})'.format(length))
+ else:
+ action = action.format(count='')
+ return action
@adapter_config(name='name', context=(IParagraphContainerTarget, IPyAMSLayer, ParagraphContainerTable),
@@ -201,6 +273,25 @@
weight = 50
+ def renderHeadCell(self):
+ return '<span class="small hint" title="{title}" data-ams-hint-gravity="e"' \
+ ' data-ams-stop-propagation="true"' \
+ ' data-ams-click-handler="PyAMS_content.paragraphs.switchAllEditors">' \
+ ' <i class="fa fa-plus-square-o"></i>' \
+ '</span> '.format(
+ title=self.request.localizer.translate(_("Click to open/close all paragraphs editors"))) + \
+ super(ParagraphContainerTitleColumn, self).renderHeadCell()
+
+ def renderCell(self, item):
+ return '<div><span class="small hint" title="{title}" data-ams-hint-gravity="e"' \
+ ' data-ams-stop-propagation="true" ' \
+ ' data-ams-click-handler="PyAMS_content.paragraphs.switchEditor">' \
+ ' <i class="fa fa-plus-square-o"></i>' \
+ '</span> '.format(
+ title=self.request.localizer.translate(_("Click to open/close paragraph editor"))) + \
+ '<span class="title">{0}</span>'.format(super(ParagraphContainerTitleColumn, self).renderCell(item)) + \
+ '</div><div class="inner-table-form editor margin-x-10 margin-bottom-0"></div>'
+
def getValue(self, obj):
return II18n(obj).query_attribute('title', request=self.request) or '--'
@@ -237,3 +328,50 @@
order = list(map(str, json.loads(request.params.get('names'))))
container.updateOrder(order)
return {'status': 'success'}
+
+
+@view_config(name='set-paragraph-visibility.json', context=IParagraphContainer, request_type=IPyAMSLayer,
+ permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+def set_paragraph_visibility(request):
+ """Set paragraph visibility"""
+ container = IParagraphContainer(request.context)
+ paragraph = container.get(str(request.params.get('object_name')))
+ if paragraph is None:
+ raise NotFound()
+ paragraph = IBaseParagraph(paragraph)
+ paragraph.visible = not paragraph.visible
+ return {'visible': paragraph.visible}
+
+
+@view_config(name='get-paragraph-editor.json', context=IParagraphContainer, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def get_paragraph_editor(request):
+ """Get paragraph editor"""
+ container = IParagraphContainer(request.context)
+ paragraph = container.get(str(request.params.get('object_name')))
+ if paragraph is None:
+ raise NotFound()
+ registry = request.registry
+ editor = registry.queryMultiAdapter((paragraph, request), IParagraphInnerEditor)
+ if editor is None:
+ editor = registry.queryAdapter(paragraph, IParagraphInnerEditor)
+ if editor is not None:
+ editor.update()
+ return editor.render()
+
+
+@view_config(name='get-paragraphs-editors.json', context=IParagraphContainer, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def get_paragraphs_editors(request):
+ """Get all paragraphs inner editors"""
+ container = IParagraphContainer(request.context)
+ registry = request.registry
+ result = {}
+ for key, paragraph in container.items():
+ editor = registry.queryMultiAdapter((paragraph, request), IParagraphInnerEditor)
+ if editor is None:
+ editor = registry.queryAdapter(paragraph, IParagraphInnerEditor)
+ if editor is not None:
+ editor.update()
+ result[key] = editor.render()
+ return result