--- a/src/pyams_content/shared/form/__init__.py Tue Sep 19 11:11:12 2017 +0200
+++ b/src/pyams_content/shared/form/__init__.py Tue Sep 19 11:11:30 2017 +0200
@@ -16,22 +16,56 @@
# import standard library
# import interfaces
-from pyams_content.component.links.interfaces import ILinkContainerTarget
-from pyams_content.shared.form.interfaces import IWfForm, IForm, FORM_CONTENT_TYPE, FORM_CONTENT_NAME
+from pyams_content.shared.form.interfaces import IWfForm, IForm, FORM_CONTENT_TYPE, FORM_CONTENT_NAME, \
+ IFormFieldContainerTarget, IFormHandler
# import packages
from pyams_content.shared.common import WfSharedContent, register_content_type, SharedContent
-from zope.interface import implementer
+from zope.component.globalregistry import getGlobalSiteManager
+from zope.interface import implementer, alsoProvides, noLongerProvides
from zope.schema.fieldproperty import FieldProperty
-@implementer(IWfForm, ILinkContainerTarget)
+@implementer(IWfForm, IFormFieldContainerTarget)
class WfForm(WfSharedContent):
"""Base form"""
content_type = FORM_CONTENT_TYPE
content_name = FORM_CONTENT_NAME
+ user_title = FieldProperty(IWfForm['user_title'])
+ header = FieldProperty(IWfForm['header'])
+ _handler = FieldProperty(IWfForm['handler'])
+ auth_only = FieldProperty(IWfForm['auth_only'])
+ use_captcha = FieldProperty(IWfForm['use_captcha'])
+ submit_label = FieldProperty(IWfForm['submit_label'])
+
+ @property
+ def handler(self):
+ return self._handler
+
+ @handler.setter
+ def handler(self, value):
+ old_handler = self._handler
+ if value == old_handler:
+ return
+ if old_handler is not None:
+ handler = self.query_handler(old_handler)
+ if (handler is not None) and handler.target_interface:
+ noLongerProvides(self, handler.target_interface)
+ if value is not None:
+ handler = self.query_handler(value)
+ if (handler is not None) and handler.target_interface:
+ alsoProvides(self, handler.target_interface)
+ self._handler = value
+
+ def query_handler(self, handler=None):
+ if handler is None:
+ handler = self._handler
+ if handler:
+ registry = getGlobalSiteManager()
+ return registry.queryUtility(IFormHandler, name=handler)
+
register_content_type(WfForm)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/field.py Tue Sep 19 11:11:30 2017 +0200
@@ -0,0 +1,265 @@
+#
+# Copyright (c) 2008-2015 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 collections import OrderedDict
+
+# import interfaces
+from pyams_content.shared.form.interfaces import IFormFieldFactory, IFormField, IFormFieldContainer, \
+ IFormFieldContainerTarget, FORM_FIELD_CONTAINER_KEY
+from pyams_i18n.interfaces import II18n
+from zope.annotation.interfaces import IAnnotations
+from zope.location.interfaces import ISublocations
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from persistent import Persistent
+from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.registry import utility_config
+from pyams_utils.request import check_request
+from pyams_utils.schema import MailAddressField
+from pyams_utils.vocabulary import vocabulary_config
+from pyramid.threadlocal import get_current_registry
+from zope.component.globalregistry import getGlobalSiteManager
+from zope.container.contained import Contained
+from zope.container.ordered import OrderedContainer
+from zope.interface import implementer
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate
+from zope.schema import TextLine, Text, Bool, Int, Decimal, URI, Date, Choice, List
+from zope.schema.fieldproperty import FieldProperty
+from zope.componentvocabulary.vocabulary import UtilityVocabulary, UtilityTerm
+
+from pyams_content import _
+
+
+@implementer(IFormField)
+class FormField(Persistent, Contained):
+ """Form field definition persistent class"""
+
+ name = FieldProperty(IFormField['name'])
+ field_type = FieldProperty(IFormField['field_type'])
+ label = FieldProperty(IFormField['label'])
+ description = FieldProperty(IFormField['description'])
+ placeholder = FieldProperty(IFormField['placeholder'])
+ values = FieldProperty(IFormField['values'])
+ default = FieldProperty(IFormField['default'])
+ required = FieldProperty(IFormField['required'])
+ visible = FieldProperty(IFormField['visible'])
+
+
+@implementer(IFormFieldContainer)
+class FormFieldContainer(OrderedContainer):
+ """Form fields container persistent class"""
+
+ def get_fields(self):
+ registry = getGlobalSiteManager()
+ fields = []
+ for field in self.values():
+ if field.visible:
+ factory = registry.queryUtility(IFormFieldFactory, name=field.field_type)
+ if factory is not None:
+ fields.append(factory.get_schema_field(field))
+ return fields
+
+
+@adapter_config(context=IFormFieldContainerTarget, provides=IFormFieldContainer)
+def FormFieldContainerFactory(context):
+ """Form fields container factory"""
+ annotations = IAnnotations(context)
+ container = annotations.get(FORM_FIELD_CONTAINER_KEY)
+ if container is None:
+ container = annotations[FORM_FIELD_CONTAINER_KEY] = FormFieldContainer()
+ get_current_registry().notify(ObjectCreatedEvent(container))
+ locate(container, context, '++fields++')
+ return container
+
+
+@adapter_config(name='fields', context=IFormFieldContainerTarget, provides=ITraversable)
+class FormFieldContainerNamespace(ContextAdapter):
+ """Form fields container ++fields++ namespace"""
+
+ def traverse(self, name, firtherpath=None):
+ return IFormFieldContainer(self.context)
+
+
+@adapter_config(name='fields', context=IFormFieldContainerTarget, provides=ISublocations)
+class FormFieldsContainerSublocations(ContextAdapter):
+ """Form fields container sub-locations adapter"""
+
+ def sublocations(self):
+ return IFormFieldContainer(self.context).values()
+
+
+#
+# Form fields factories
+#
+
+@vocabulary_config(name='PyAMS form field types')
+class FormFieldTypesVocabulary(UtilityVocabulary):
+ """Form field types vocabulary"""
+
+ interface = IFormFieldFactory
+
+ def __init__(self, context, **kw):
+ request = check_request()
+ registry = request.registry
+ translate = request.localizer.translate
+ utils = [(name, translate(util.label))
+ for (name, util) in sorted(registry.getUtilitiesFor(self.interface),
+ key=lambda x: x[1].weight)]
+ self._terms = OrderedDict((title, UtilityTerm(name, title)) for name, title in utils)
+
+ def __iter__(self):
+ return iter(self._terms.values())
+
+
+class BaseFormFieldFactory(object):
+ """Base form field factory"""
+
+ field_factory = None
+
+ def get_schema_field(self, field):
+ i18n = II18n(field)
+ result = self.field_factory(title=i18n.query_attribute('label'),
+ description=i18n.query_attribute('description'),
+ required=field.required,
+ default=i18n.query_attribute('default'))
+ result.__name__ = field.name
+ return result
+
+
+@utility_config(name='textline', provides=IFormFieldFactory)
+class TextLineFieldFactory(BaseFormFieldFactory):
+ """Textline field factory"""
+
+ label = _("Text")
+ weight = 1
+
+ field_factory = TextLine
+
+
+@utility_config(name='text', provides=IFormFieldFactory)
+class TextFieldFactory(BaseFormFieldFactory):
+ """Text field factory"""
+
+ label = _("Multi-lines text")
+ weight = 2
+
+ field_factory = Text
+
+
+@utility_config(name='bool', provides=IFormFieldFactory)
+class BooleanFieldFactory(BaseFormFieldFactory):
+ """Boolean field factory"""
+
+ label = _("Boolean")
+ weight = 3
+
+ field_factory = Bool
+
+
+@utility_config(name='integer', provides=IFormFieldFactory)
+class IntegerFieldFactory(BaseFormFieldFactory):
+ """Integer field factory"""
+
+ label = _("Integer")
+ weight = 4
+
+ field_factory = Int
+
+
+@utility_config(name='decimal', provides=IFormFieldFactory)
+class DecimalFieldFactory(BaseFormFieldFactory):
+ """Decimal field factory"""
+
+ label = _("Decimal")
+ weight = 5
+
+ field_factory = Decimal
+
+
+@utility_config(name='mail', provides=IFormFieldFactory)
+class MailFieldFactory(BaseFormFieldFactory):
+ """Mail field factory"""
+
+ label = _("E-mail address")
+ weight = 10
+
+ field_factory = MailAddressField
+
+
+@utility_config(name='uri', provides=IFormFieldFactory)
+class URIFieldFactory(BaseFormFieldFactory):
+ """URI field factory"""
+
+ label = _("URI")
+ weight = 11
+
+ field_factory = URI
+
+
+@utility_config(name='date', provides=IFormFieldFactory)
+class DateFieldFactory(BaseFormFieldFactory):
+ """Date field factory"""
+
+ label = _("Date")
+ weight = 15
+
+ field_factory = Date
+
+
+class ValuesFieldFactory(BaseFormFieldFactory):
+ """Values-based field factory"""
+
+
+@utility_config(name='choice', provides=IFormFieldFactory)
+class ChoiceFieldFactory(ValuesFieldFactory):
+ """Choice field factory"""
+
+ label = _("Choice")
+ weight = 20
+
+ field_factory = Choice
+
+ def get_schema_field(self, field):
+ i18n = II18n(field)
+ result = self.field_factory(title=i18n.query_attribute('label'),
+ description=i18n.query_attribute('description'),
+ required=field.required,
+ default=i18n.query_attribute('default'),
+ values=field.values)
+ result.__name__ = field.name
+ return result
+
+
+@utility_config(name='list', provides=IFormFieldFactory)
+class ListFieldFactory(ValuesFieldFactory):
+ """List field factory"""
+
+ label = _("List")
+ weight = 51
+
+ field_factory = List
+
+ def get_schema_field(self, field):
+ i18n = II18n(field)
+ result = self.field_factory(title=i18n.query_attribute('label'),
+ description=i18n.query_attribute('description'),
+ required=field.required,
+ default=[i18n.query_attribute('default')],
+ value_type=Choice(values=field.values))
+ result.__name__ = field.name
+ return result
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/handler.py Tue Sep 19 11:11:30 2017 +0200
@@ -0,0 +1,91 @@
+#
+# Copyright (c) 2008-2015 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.form.interfaces import IFormHandler, IMailtoHandlerTarget, IMailtoHandlerInfo
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from persistent import Persistent
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import utility_config
+from pyams_utils.request import check_request
+from pyams_utils.vocabulary import vocabulary_config
+from zope.componentvocabulary.vocabulary import UtilityTerm, UtilityVocabulary
+from zope.interface import implementer
+from zope.schema.fieldproperty import FieldProperty
+
+from pyams_content import _
+
+
+@vocabulary_config(name='PyAMS form handlers')
+class FormHandlersVocabulary(UtilityVocabulary):
+ """Form handlers vocabulary"""
+
+ interface = IFormHandler
+
+ def __init__(self, context, **kw):
+ request = check_request()
+ registry = request.registry
+ translate = request.localizer.translate
+ utils = [(None, translate(_("No selected handler...")))] + \
+ [(name, translate(util.label))
+ for (name, util) in registry.getUtilitiesFor(self.interface)]
+ self._terms = dict((title, UtilityTerm(name, title)) for name, title in utils)
+
+ def __iter__(self):
+ return iter(self._terms.values())
+
+
+#
+# Mailto form handler
+#
+
+MAILTO_HANDLER_ANNOTATIONS_KEY = 'pyams_content.form.handler.mailto'
+
+
+@implementer(IMailtoHandlerInfo)
+class MailtoFormHandlerInfo(Persistent):
+ """Mailto form handler persistent info"""
+
+ source_address = FieldProperty(IMailtoHandlerInfo['source_address'])
+ source_name = FieldProperty(IMailtoHandlerInfo['source_name'])
+ target_address = FieldProperty(IMailtoHandlerInfo['target_address'])
+ target_name = FieldProperty(IMailtoHandlerInfo['target_name'])
+
+
+@adapter_config(context=IMailtoHandlerTarget, provides=IMailtoHandlerInfo)
+def mailto_form_handler_factory(context):
+ """Mailto form handler factory"""
+ annotations = IAnnotations(context)
+ info = annotations.get(MAILTO_HANDLER_ANNOTATIONS_KEY)
+ if info is None:
+ info = annotations[MAILTO_HANDLER_ANNOTATIONS_KEY] = MailtoFormHandlerInfo()
+ return info
+
+
+@utility_config(name='mailto', provides=IFormHandler)
+class MailtoFormHandler(object):
+ """Mailto form handler"""
+
+ label = _("Mailto form handler")
+ target_interface = IMailtoHandlerTarget
+ handler_info = IMailtoHandlerInfo
+
+ def handle(self, data):
+ # TODO: handle form data
+ pass
--- a/src/pyams_content/shared/form/interfaces/__init__.py Tue Sep 19 11:11:12 2017 +0200
+++ b/src/pyams_content/shared/form/interfaces/__init__.py Tue Sep 19 11:11:30 2017 +0200
@@ -17,8 +17,15 @@
# import interfaces
from pyams_content.shared.common.interfaces import ISharedTool, IWfSharedContent, ISharedContent
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.container.interfaces import IContainer, IContained
# import packages
+from pyams_i18n.schema import I18nTextLineField, I18nTextField
+from pyams_utils.schema import MailAddressField, TextLineListField
+from zope.container.constraints import containers, contains
+from zope.interface import Interface, Attribute
+from zope.schema import TextLine, Choice, Bool
from pyams_content import _
@@ -26,14 +33,157 @@
FORM_CONTENT_TYPE = 'form'
FORM_CONTENT_NAME = _('Form')
+FORM_FIELD_CONTAINER_KEY = 'pyams_content.shared.form_fields'
+
class IFormsManager(ISharedTool):
"""Formq manager interface"""
+class IFormField(IContained):
+ """Form field interface"""
+
+ containers('.IFormFieldContainer')
+
+ name = TextLine(title=_("Field name"),
+ description=_("Field internal name; must be unique for a given form"),
+ required=True)
+
+ field_type = Choice(title=_("Field type"),
+ description=_("Selected field type"),
+ vocabulary='PyAMS form field types',
+ required=True)
+
+ label = I18nTextLineField(title=_("Label"),
+ description=_("User field label"),
+ required=True)
+
+ description = I18nTextField(title=_("Description"),
+ description=_("Field description can be displayed as hint"),
+ required=False)
+
+ placeholder = TextLine(title=_("Placeholder"),
+ description=_("Some field types like textline can display a placeholder"),
+ required=False)
+
+ values = TextLineListField(title=_("Optional values"),
+ description=_("List of available values (for 'choice' and 'list' field types)"),
+ required=False)
+
+ default = I18nTextLineField(title=_("Default value"),
+ description=_("Give default value if field type can use it"),
+ required=False)
+
+ required = Bool(title=_("Required?"),
+ description=_("Select 'yes' to set field as mandatory"),
+ required=True,
+ default=False)
+
+ visible = Bool(title=_("Visible?"),
+ description=_("Select 'no' to hide given field..."),
+ required=True,
+ default=True)
+
+
+class IFormFieldFactory(Interface):
+ """Form field factory interface"""
+
+ label = Attribute("Factory label")
+ weight = Attribute("Factory weight")
+
+ def get_schema_field(self, field):
+ """Get schema field matching given form field"""
+
+
+class IFormFieldContainer(IContainer):
+ """Form fields container interface"""
+
+ contains(IFormField)
+
+ def append(self, field):
+ """Append given field to container"""
+
+ def get_fields(self):
+ """Get schema fields matching current fields"""
+
+
+class IFormFieldContainerTarget(Interface):
+ """Form fields container target marker interface"""
+
+
class IWfForm(IWfSharedContent):
"""Form interface"""
+ user_title = I18nTextLineField(title=_("Form title"),
+ required=True)
+
+ header = I18nTextField(title=_("Form header"),
+ required=False)
+
+ handler = Choice(title=_("Form handler"),
+ description=_("Select how form data is transmitted"),
+ vocabulary='PyAMS form handlers')
+
+ auth_only = Bool(title=_("Authenticated only?"),
+ description=_("If 'yes', only authenticated users will be able to see and submit form"),
+ required=True,
+ default=False)
+
+ use_captcha = Bool(title=_("Use captcha?"),
+ description=_("If 'yes', a captcha will be added automatically to the form"),
+ required=True,
+ default=True)
+
+ submit_label = I18nTextLineField(title=_("Submit label"),
+ description=_("Label of form submit button"),
+ required=True)
+
+ def query_handler(self, handler=None):
+ """Get form handler utility"""
+
class IForm(ISharedContent):
"""Workflow managed form interface"""
+
+
+#
+# Form handler
+#
+
+class IFormHandler(Interface):
+ """Form handler interface"""
+
+ label = Attribute("Handler label")
+ target_interface = Attribute("Handler target marker interface")
+ handler_info = Attribute("Handler info interface")
+
+ def handle(self, data):
+ """Handle entered data"""
+
+
+class IFormHandlerInfo(Interface):
+ """Base handler info interface"""
+
+
+class IMailtoHandlerInfo(IFormHandlerInfo):
+ """Mailto form handler info interface"""
+
+ source_address = MailAddressField(title=_("Source address"),
+ description=_("Mail address from which form data is sent"),
+ required=True)
+
+ source_name = TextLine(title=_("Source name"),
+ description=_("Name of mail data sender"),
+ required=False)
+
+ target_address = MailAddressField(title=_("Recipient address"),
+ description=_("Mail address to which form data is sent"),
+ required=True)
+
+ target_name = TextLine(title=_("Recipient name"),
+ description=_("Name of data recipient"),
+ required=False)
+
+
+class IMailtoHandlerTarget(IAttributeAnnotatable):
+ """Mailto handler target marker interface"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/zmi/__init__.py Tue Sep 19 11:11:30 2017 +0200
@@ -0,0 +1,80 @@
+#
+# Copyright (c) 2008-2015 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.interfaces import CREATE_CONTENT_PERMISSION
+from pyams_content.shared.form.interfaces import IWfForm, IFormsManager
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces import IContentTitle
+from pyams_skin.interfaces.viewlet import IMenuHeader, IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.interfaces.menu import IContentManagementMenu
+from pyams_zmi.layer import IAdminLayer
+
+# import packages
+from pyams_content.shared.common.zmi import SharedContentAddForm, SharedContentAJAXAddForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.toolbar import ToolbarAction
+from pyams_utils.adapter import adapter_config, ContextRequestAdapter, ContextRequestViewAdapter
+from pyams_viewlet.viewlet import viewlet_config
+from pyramid.view import view_config
+from zope.interface import Interface
+
+from pyams_content import _
+
+
+@adapter_config(context=(IWfForm, IContentManagementMenu), provides=IMenuHeader)
+class FormContentMenuHeader(ContextRequestAdapter):
+ """Form content menu header adapter"""
+
+ header = _("This form")
+
+
+@adapter_config(context=(IWfForm, IPyAMSLayer, Interface), provides=IContentTitle)
+class FormTitleAdapter(ContextRequestViewAdapter):
+ """Form title adapter"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("Form « {title} »")).format(
+ title=II18n(self.context).query_attribute('title', request=self.request))
+
+
+@viewlet_config(name='add-shared-content.action', context=IFormsManager, layer=IAdminLayer, view=Interface,
+ manager=IWidgetTitleViewletManager, permission=CREATE_CONTENT_PERMISSION, weight=1)
+class FormAddAction(ToolbarAction):
+ """Form adding action"""
+
+ label = _("Add form")
+ label_css_class = 'fa fa-fw fa-plus'
+ url = 'add-shared-content.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-shared-content.html', context=IFormsManager, layer=IPyAMSLayer,
+ permission=CREATE_CONTENT_PERMISSION)
+class FormAddForm(SharedContentAddForm):
+ """Form add form"""
+
+ legend = _("Add form")
+
+
+@view_config(name='add-shared-content.json', context=IFormsManager, request_type=IPyAMSLayer,
+ permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
+class FormAJAXAddForm(SharedContentAJAXAddForm, FormAddForm):
+ """Form add form, JSON renderer"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/zmi/field.py Tue Sep 19 11:11:30 2017 +0200
@@ -0,0 +1,336 @@
+#
+# Copyright (c) 2008-2015 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 json
+
+# import interfaces
+from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.common.interfaces import IWfSharedContent
+from pyams_content.shared.form.interfaces import IFormFieldContainerTarget, IFormFieldContainer, IFormField, \
+ IFormFieldFactory
+from pyams_form.interfaces.form import IFormSecurityContext
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces.viewlet import IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_zmi.interfaces.menu import IPropertiesMenu
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import DISPLAY_MODE, IDataExtractedEvent
+from z3c.table.interfaces import IColumn, IValues
+
+# import packages
+from pyams_content.shared.common.zmi import WfModifiedContentColumnMixin
+from pyams_content.shared.form.field import FormField
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.security import ProtectedFormObjectMixin
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.table import BaseTable, SorterColumn, JsActionColumn, I18nColumn, TrashColumn, I18nValueColumn
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_skin.viewlet.toolbar import ToolbarAction
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.view import ContainerAdminView
+from pyramid.decorator import reify
+from pyramid.events import subscriber
+from pyramid.exceptions import NotFound
+from pyramid.view import view_config
+from z3c.form import field
+from z3c.table.column import GetAttrColumn
+from zope.interface import Invalid
+
+from pyams_content import _
+
+
+@viewlet_config(name='form-fields.menu', context=IFormFieldContainerTarget, layer=IAdminLayer,
+ manager=IPropertiesMenu, permission=VIEW_SYSTEM_PERMISSION, weight=110)
+class FormFieldsMenu(MenuItem):
+ """Form fields menu"""
+
+ label = _("Form fields...")
+ icon_class = 'fa-pencil-square-o'
+ url = '#form-fields.html'
+
+
+#
+# Form fields container view
+#
+
+class FormFieldsContainerTable(ProtectedFormObjectMixin, BaseTable):
+ """Form fields table"""
+
+ id = 'form_fields_list'
+ hide_header = True
+ sortOn = None
+
+ @property
+ def cssClasses(self):
+ classes = ['table', 'table-bordered', 'table-striped', 'table-hover', 'table-tight']
+ permission = self.permission
+ if (not permission) or self.request.has_permission(permission, self.context):
+ classes.append('table-dnd')
+ return {'table': ' '.join(classes)}
+
+ @property
+ def data_attributes(self):
+ attributes = super(FormFieldsContainerTable, self).data_attributes
+ 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(IFormFieldContainer(self.context), self.request),
+ 'data-ams-tablednd-drag-handle': 'td.sorter',
+ 'data-ams-tablednd-drop-target': 'set-form-fields-order.json'}
+ return attributes
+
+ @reify
+ def values(self):
+ return list(super(FormFieldsContainerTable, self).values)
+
+ def render(self):
+ if not self.values:
+ translate = self.request.localizer.translate
+ return translate(_("No currently defined form field."))
+ return super(FormFieldsContainerTable, self).render()
+
+
+@adapter_config(name='sorter', context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable),
+ provides=IColumn)
+class FormFieldsContainerSorterColumn(ProtectedFormObjectMixin, SorterColumn):
+ """Form fields container sorter column"""
+
+
+@adapter_config(name='show-hide', context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable),
+ provides=IColumn)
+class FormFieldsContainerShowHideColumn(ProtectedFormObjectMixin, JsActionColumn):
+ """Form fields container visibility switcher column"""
+
+ cssClasses = {'th': 'action',
+ 'td': 'action switcher'}
+
+ icon_class = 'fa fa-fw fa-eye'
+ icon_hint = _("Switch field visibility")
+
+ url = 'PyAMS_content.fields.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(FormFieldsContainerShowHideColumn, self).renderCell(item)
+
+
+@adapter_config(context=FormFieldsContainerShowHideColumn, provides=IFormSecurityContext)
+def ShowHideColumnSecurityContextFactory(column):
+ """Show/hide column security context factory"""
+ return column.table.context
+
+
+@adapter_config(name='name', context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable),
+ provides=IColumn)
+class FormFieldsContainerNameColumn(I18nColumn, WfModifiedContentColumnMixin, GetAttrColumn):
+ """Form fields container name column"""
+
+ _header = _("Name")
+
+ attrName = 'name'
+ weight = 50
+
+
+@adapter_config(name='label', context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable),
+ provides=IColumn)
+class FormFieldsContainerLabelColumn(I18nColumn, WfModifiedContentColumnMixin, I18nValueColumn):
+ """Form fields container label column"""
+
+ _header = _("Label")
+
+ attrName = 'label'
+ weight = 55
+
+
+@adapter_config(name='type', context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable),
+ provides=IColumn)
+class FormFieldsContainerTypeColumn(I18nColumn, WfModifiedContentColumnMixin, GetAttrColumn):
+ """Form fields container label column"""
+
+ _header = _("Field type")
+
+ weight = 60
+
+ def getValue(self, obj):
+ adapter = self.request.registry.queryUtility(IFormFieldFactory, name=obj.field_type)
+ if adapter is not None:
+ label = adapter.label
+ else:
+ label = _("-- unknown field type --")
+ return self.request.localizer.translate(label)
+
+
+@adapter_config(name='trash', context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable),
+ provides=IColumn)
+class FormFieldsContainerTrashColumn(ProtectedFormObjectMixin, TrashColumn):
+ """Form fields container trash column"""
+
+
+@adapter_config(context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldsContainerTable), provides=IValues)
+class FormFieldsContainerValues(ContextRequestViewAdapter):
+ """Form fields container values"""
+
+ @property
+ def values(self):
+ return IFormFieldContainer(self.context).values()
+
+
+@pagelet_config(name='form-fields.html', context=IFormFieldContainerTarget, layer=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+class FormFieldsContainerView(ContainerAdminView):
+ """Form fields container view"""
+
+ title = _("Form fields list")
+ table_class = FormFieldsContainerTable
+
+
+@view_config(name='set-form-fields-order.json', context=IFormFieldContainer, request_type=IPyAMSLayer,
+ permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+def set_form_fields_order(request):
+ """Update form fields order"""
+ order = list(map(str, json.loads(request.params.get('names'))))
+ request.context.updateOrder(order)
+ return {'status': 'success'}
+
+
+@view_config(name='set-form-field-visibility.json', context=IFormFieldContainer, request_type=IPyAMSLayer,
+ permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+def set_form_field_visibility(request):
+ """Set form field visibility"""
+ container = IFormFieldContainer(request.context)
+ paragraph = container.get(str(request.params.get('object_name')))
+ if paragraph is None:
+ raise NotFound()
+ field = IFormField(paragraph)
+ field.visible = not field.visible
+ return {'visible': field.visible}
+
+
+#
+# Form field views
+#
+
+@viewlet_config(name='add-form-field.action', context=IFormFieldContainerTarget, layer=IAdminLayer,
+ view=FormFieldsContainerView, manager=IWidgetTitleViewletManager,
+ permission=MANAGE_CONTENT_PERMISSION, weight=1)
+class FormFieldAddAction(ProtectedFormObjectMixin, ToolbarAction):
+ """Form field add action"""
+
+ label = _("Add form field")
+ label_css_class = 'fa fa-fw fa-plus'
+ url = 'add-form-field.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-form-field.html', context=IFormFieldContainerTarget, layer=IPyAMSLayer,
+ permission=MANAGE_CONTENT_PERMISSION)
+class FormFieldAddForm(AdminDialogAddForm):
+ """Form field add form"""
+
+ legend = _("Add form field")
+ icon_css_class = 'fa fa-fw fa-pencil-square-o'
+
+ fields = field.Fields(IFormField).omit('__parent__', '__name__', 'visible')
+ ajax_handler = 'add-form-field.json'
+ edit_permission = MANAGE_CONTENT_PERMISSION
+
+ def updateWidgets(self, prefix=None):
+ super(FormFieldAddForm, self).updateWidgets(prefix)
+ if 'description' in self.widgets:
+ self.widgets['description'].widget_css_class = 'textarea'
+
+ def create(self, data):
+ return FormField()
+
+ def add(self, object):
+ IFormFieldContainer(self.context)[object.name] = object
+
+
+@subscriber(IDataExtractedEvent, form_selector=FormFieldAddForm)
+def handle_new_form_field_data_extraction(event):
+ """Handle new form field form data extraction"""
+ container = IFormFieldContainer(event.form.context)
+ name = event.data.get('name')
+ if name in container:
+ event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
+
+
+@view_config(name='add-form-field.json', context=IFormFieldContainerTarget, request_type=IPyAMSLayer,
+ permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+class FormFieldAJAXAddForm(AJAXAddForm, FormFieldAddForm):
+ """Form field add form, JSON renderer"""
+
+ def nextURL(self):
+ return '#form-fields.html'
+
+
+@pagelet_config(name='properties.html', context=IFormField, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+class FormFieldPropertiesEditForm(AdminDialogEditForm):
+ """Form field properties edit form"""
+
+ @property
+ def title(self):
+ content = get_parent(self.context, IWfSharedContent)
+ return II18n(content).query_attribute('title', request=self.request)
+
+ legend = _("Edit form field properties")
+ icon_class = 'fa fa-fw fa-pencil-square-o'
+
+ fields = field.Fields(IFormField).omit('__parent__', '__name__', 'visible')
+ ajax_handler = 'properties.json'
+ edit_permission = MANAGE_CONTENT_PERMISSION
+
+ def updateWidgets(self, prefix=None):
+ super(FormFieldPropertiesEditForm, self).updateWidgets(prefix)
+ if 'name' in self.widgets:
+ self.widgets['name'].mode = DISPLAY_MODE
+ if 'description' in self.widgets:
+ self.widgets['description'].widget_css_class = 'textarea'
+
+
+@view_config(name='properties.json', context=IFormField, request_type=IPyAMSLayer,
+ permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+class FormFieldPropertiesAJAXEditForm(AJAXEditForm, FormFieldPropertiesEditForm):
+ """Form field properties edit form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ output = super(FormFieldPropertiesAJAXEditForm, self).get_ajax_output(changes)
+ if 'label' in changes.get(IFormField, ()):
+ output.setdefault('events', []).append({
+ 'event': 'PyAMS_content.changed_item',
+ 'options': {'object_type': 'form_field',
+ 'object_name': self.context.__name__,
+ 'title': II18n(self.context).query_attribute('label', request=self.request),
+ 'visible': self.context.visible}
+ })
+ return output
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/zmi/properties.py Tue Sep 19 11:11:30 2017 +0200
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2008-2015 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.form.interfaces import IWfForm
+from pyams_form.interfaces.form import IInnerSubForm
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.shared.common.zmi.properties import SharedContentPropertiesEditForm
+from pyams_utils.adapter import adapter_config
+from pyams_zmi.form import InnerAdminEditForm
+from z3c.form import field
+from zope.interface import Interface
+
+from pyams_content import _
+
+
+@adapter_config(name='form-settings',
+ context=(IWfForm, IPyAMSLayer, SharedContentPropertiesEditForm),
+ provides=IInnerSubForm)
+class FormPropertiesEditForm(InnerAdminEditForm):
+ """Form properties edit form extension"""
+
+ legend = _("Main form settings")
+ fieldset_class = 'bordered no-x-margin margin-y-10'
+
+ fields = field.Fields(IWfForm).select('user_title', 'header', 'handler', 'auth_only', 'use_captcha', 'submit_label')
+ weight = 1
+
+ def updateWidgets(self, prefix=None):
+ super(FormPropertiesEditForm, self).updateWidgets(prefix)
+ if 'header' in self.widgets:
+ self.widgets['header'].widget_css_class = 'textarea'
+
+ def get_ajax_output(self, changes):
+ if 'handler' in changes.get(IWfForm, ()):
+ return {'status': 'reload',
+ 'message': self.request.localizer.translate(self.successMessage)}
+ else:
+ return super(FormPropertiesEditForm, self).get_ajax_output(changes)
+
+
+@adapter_config(name='handler-settings',
+ context=(IWfForm, IPyAMSLayer, SharedContentPropertiesEditForm),
+ provides=IInnerSubForm)
+class FormHandlerPropertiesEditForm(InnerAdminEditForm):
+ """Form handler properties edit form extension"""
+
+ legend = _("Form handler settings")
+ fieldset_class = 'bordered no-x-margin margin-y-10'
+
+ def __new__(cls, context, request, view):
+ handler = context.query_handler()
+ if handler is None:
+ return None
+ return InnerAdminEditForm.__new__(cls)
+
+ @property
+ def fields(self):
+ handler = self.context.query_handler()
+ if handler is None:
+ interface = Interface
+ else:
+ interface = handler.handler_info
+ return field.Fields(interface)
+
+ weight = 2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/zmi/summary.py Tue Sep 19 11:11:30 2017 +0200
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2008-2015 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.zmi import IInnerSummaryView
+from pyams_content.shared.form.interfaces import IFormFieldContainerTarget, IFormFieldContainer
+from pyams_form.interfaces.form import IInnerTabForm, IFormHelp
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+
+# import packages
+from pyams_content.shared.common.zmi.summary import SharedContentSummaryForm
+from pyams_form.help import FormHelp
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_utils.adapter import adapter_config
+from pyams_zmi.form import InnerAdminDisplayForm, InnerAdminAddForm
+from z3c.form import field, button
+from zope.interface import implementer, Interface
+
+from pyams_content import _
+
+
+@adapter_config(name='form-fields-summary',
+ context=(IFormFieldContainerTarget, IPyAMSLayer, SharedContentSummaryForm),
+ provides=IInnerTabForm)
+class FormFieldContainerSummary(InnerAdminDisplayForm):
+ """Form fields container summary"""
+
+ weight = 20
+ tab_label = _("Quick preview")
+ tab_target = 'form-fields-summary.html'
+
+ fields = field.Fields(Interface)
+
+
+@pagelet_config(name='form-fields-summary.html', context=IFormFieldContainerTarget, layer=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+@implementer(IInnerSummaryView)
+class FormFieldContainerSummaryView(InnerAdminAddForm):
+ """Form fields container summary view"""
+
+ @property
+ def legend(self):
+ return II18n(self.context).query_attribute('user_title', request=self.request)
+
+ @property
+ def fields(self):
+ fields = field.Fields(*IFormFieldContainer(self.context).get_fields())
+ if self.context.use_captcha:
+ # TODO: add captcha
+ # fields += field.Fields(Captcha(title='', description='', required=True))
+ pass
+ return fields
+
+ buttons = button.Buttons(Interface)
+
+ def updateWidgets(self, prefix=None):
+ super(FormFieldContainerSummaryView, self).updateWidgets(prefix)
+ for widget in self.widgets.values():
+ field = IFormFieldContainer(self.context).get(widget.field.__name__)
+ if field is not None:
+ widget.placeholder = field.placeholder
+
+
+@adapter_config(context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldContainerSummaryView), provides=IFormHelp)
+class FormFieldContainerSummaryHelp(FormHelp):
+ """Form field container summary help adapter"""
+
+ def __new__(cls, context, request, view):
+ if not context.header:
+ return None
+ return FormHelp.__new__(cls)
+
+ @property
+ def message(self):
+ return II18n(self.context).query_attribute('header', request=self.request)
+
+ message_format = 'text'