Updated shared forms management
authorThierry Florac <tflorac@ulthar.net>
Wed, 04 Sep 2019 15:58:26 +0200
changeset 1343 530cbb970243
parent 1342 999fa08d99c2
child 1344 646ec926f14a
Updated shared forms management
src/pyams_content/shared/form/__init__.py
src/pyams_content/shared/form/field.py
src/pyams_content/shared/form/interfaces.py
src/pyams_content/shared/form/manager.py
src/pyams_content/shared/form/zmi/field.py
src/pyams_content/shared/form/zmi/paragraph.py
src/pyams_content/shared/form/zmi/properties.py
--- a/src/pyams_content/shared/form/__init__.py	Wed Sep 04 15:57:43 2019 +0200
+++ b/src/pyams_content/shared/form/__init__.py	Wed Sep 04 15:58:26 2019 +0200
@@ -13,6 +13,7 @@
 from zope.interface import alsoProvides, implementer, noLongerProvides, provider
 from zope.schema.fieldproperty import FieldProperty
 
+from pyams_content.component.paragraph import IParagraphContainerTarget
 from pyams_content.features.checker.interfaces import ERROR_VALUE, IContentChecker
 from pyams_content.features.preview.interfaces import IPreviewTarget
 from pyams_content.features.review.interfaces import IReviewTarget
@@ -29,7 +30,7 @@
 from pyams_content import _
 
 
-@implementer(IWfForm, IFormFieldContainerTarget,
+@implementer(IWfForm, IFormFieldContainerTarget, IParagraphContainerTarget,
              IPreviewTarget, IReviewTarget)
 class WfForm(WfSharedContent):
     """Base form"""
@@ -37,9 +38,10 @@
     content_type = FORM_CONTENT_TYPE
     content_name = FORM_CONTENT_NAME
 
+    form_header = FieldProperty(IWfForm['form_header'])
+    alt_title = FieldProperty(IWfForm['alt_title'])
     user_title = FieldProperty(IWfForm['user_title'])
     auth_only = FieldProperty(IWfForm['auth_only'])
-    form_header = FieldProperty(IWfForm['form_header'])
     submit_label = FieldProperty(IWfForm['submit_label'])
     submit_message = FieldProperty(IWfForm['submit_message'])
     _handler = FieldProperty(IWfForm['handler'])
--- a/src/pyams_content/shared/form/field.py	Wed Sep 04 15:57:43 2019 +0200
+++ b/src/pyams_content/shared/form/field.py	Wed Sep 04 15:58:26 2019 +0200
@@ -9,6 +9,9 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+from pyams_content.component.paragraph import BaseParagraph, IParagraphFactory, BaseParagraphFactory
+from pyams_content.features.renderer import RenderersVocabulary
+
 
 __docformat__ = 'restructuredtext'
 
@@ -24,8 +27,10 @@
 from zope.schema.fieldproperty import FieldProperty
 from zope.traversing.interfaces import ITraversable
 
-from pyams_content.shared.form.interfaces import FORM_FIELD_CONTAINER_KEY, IFormField, IFormFieldContainer, \
-    IFormFieldContainerTarget, IFormFieldFactory, IWfForm
+from pyams_content.shared.form.interfaces import FORM_FIELD_CONTAINER_KEY, IFormField, \
+    IFormFieldContainer, \
+    IFormFieldContainerTarget, IFormFieldFactory, IWfForm, IFormFieldsParagraph, \
+    FORM_FIELDS_PARAGRAPH_NAME, FORM_FIELDS_PARAGRAPH_TYPE, FORM_FIELDS_PARAGRAPH_RENDERERS
 from pyams_form.interfaces.form import IFormContextPermissionChecker
 from pyams_i18n.interfaces import II18n
 from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
@@ -272,3 +277,32 @@
                                     value_type=Choice(values=field.values))
         result.__name__ = field.name
         return result
+
+
+#
+# Form fields paragraph
+#
+
+@factory_config(provided=IFormFieldsParagraph)
+class FormFieldsParagraph(BaseParagraph):
+    """Form fields paragraph"""
+
+    icon_class = 'fa-th-list'
+    icon_hint = FORM_FIELDS_PARAGRAPH_NAME
+
+    renderer = FieldProperty(IFormFieldsParagraph['renderer'])
+
+
+@utility_config(name=FORM_FIELDS_PARAGRAPH_TYPE, provides=IParagraphFactory)
+class FormFieldsParagraphFactory(BaseParagraphFactory):
+    """Form fields paragraph factory"""
+
+    name = FORM_FIELDS_PARAGRAPH_NAME
+    content_type = FormFieldsParagraph
+
+
+@vocabulary_config(name=FORM_FIELDS_PARAGRAPH_RENDERERS)
+class FormFieldsRendererVocabulary(RenderersVocabulary):
+    """Form fields paragraph renderers vocabulary"""
+
+    content_interface = IFormFieldsParagraph
--- a/src/pyams_content/shared/form/interfaces.py	Wed Sep 04 15:57:43 2019 +0200
+++ b/src/pyams_content/shared/form/interfaces.py	Wed Sep 04 15:58:26 2019 +0200
@@ -16,6 +16,7 @@
 from zope.interface import Attribute, Interface
 from zope.schema import Bool, Choice, TextLine
 
+from pyams_content.component.paragraph import IBaseParagraph
 from pyams_content.shared.common.interfaces import ISharedContent, ISharedToolPortalContext, \
     IWfSharedContentPortalContext
 from pyams_i18n.schema import I18nTextField, I18nTextLineField, I18nHTMLField
@@ -64,11 +65,13 @@
                                 required=False)
 
     placeholder = TextLine(title=_("Placeholder"),
-                           description=_("Some field types like textline can display a 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)"),
+                               description=_("List of available values (for 'choice' and 'list' "
+                                             "field types)"),
                                required=False)
 
     default = I18nTextLineField(title=_("Default value"),
@@ -96,6 +99,20 @@
         """Get schema field matching given form field"""
 
 
+FORM_FIELDS_PARAGRAPH_TYPE = 'form-fields'
+FORM_FIELDS_PARAGRAPH_NAME = _("Form fields")
+FORM_FIELDS_PARAGRAPH_RENDERERS = 'PyAMS.paragraph.formfields.renderers'
+
+
+class IFormFieldsParagraph(IBaseParagraph):
+    """Form fields paragraph"""
+
+    renderer = Choice(title=_("Form fields template"),
+                      description=_("Presentation template used for this paaragraph"),
+                      vocabulary=FORM_FIELDS_PARAGRAPH_RENDERERS,
+                      default='default')
+
+
 class IFormFieldContainer(IContainer):
     """Form fields container interface"""
 
@@ -118,24 +135,31 @@
 class IWfForm(IWfSharedContentPortalContext):
     """Form interface"""
 
-    user_title = I18nTextLineField(title=_("Form title"),
-                                   required=True)
-
-    auth_only = Bool(title=_("Authenticated only?"),
-                     description=_("If 'yes', only authenticated users will be able to see and submit form"),
-                     required=True,
-                     default=False)
+    alt_title = I18nTextLineField(title=_("Alternate title"),
+                                  description=_("If set, this title will be displayed in "
+                                                "front-office instead of original title"),
+                                  required=False)
 
     form_header = I18nTextField(title=_("Form header"),
                                 description=_("This header is displayed just above form fields"),
                                 required=False)
 
+    user_title = I18nTextLineField(title=_("Form title"),
+                                   required=False)
+
+    auth_only = Bool(title=_("Authenticated only?"),
+                     description=_("If 'yes', only authenticated users will be able to see and "
+                                   "submit form"),
+                     required=True,
+                     default=False)
+
     submit_label = I18nTextLineField(title=_("Submit button"),
                                      description=_("Label of form submit button"),
                                      required=True)
 
     submit_message = I18nHTMLField(title=_("Submit message"),
-                                   description=_("This message will be displayed after form submission"),
+                                   description=_("This message will be displayed after form "
+                                                 "submission"),
                                    required=True)
 
     handler = Choice(title=_("Form handler"),
@@ -148,16 +172,19 @@
                        default=True)
 
     client_captcha_key = TextLine(title=_("Site key"),
-                                  description=_("This key is included into HTML code and submitted with form data"),
+                                  description=_("This key is included into HTML code and submitted "
+                                                "with form data"),
                                   required=False)
 
     server_captcha_key = TextLine(title=_("Secret key"),
-                                  description=_("This key is used to communicate with Google's reCaptcha services"),
+                                  description=_("This key is used to communicate with Google's "
+                                                "reCaptcha services"),
                                   required=False)
 
     captcha_proxy = TextLine(title=_("Recaptcha proxy"),
-                             description=_("If your server is behind a proxy, please set it's address here; "
-                                           "captcha verification requires HTTPS support..."),
+                             description=_("If your server is behind a proxy, please set it's "
+                                           "address here; captcha verification requires HTTPS "
+                                           "support..."),
                              required=False)
 
     def query_handler(self, handler=None):
--- a/src/pyams_content/shared/form/manager.py	Wed Sep 04 15:57:43 2019 +0200
+++ b/src/pyams_content/shared/form/manager.py	Wed Sep 04 15:58:26 2019 +0200
@@ -10,28 +10,26 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+from pyramid.events import subscriber
+from zope.component.interfaces import ISite
+from zope.interface import implementer
+from zope.lifecycleevent.interfaces import IObjectAddedEvent
+
+from pyams_content.component.paragraph import IParagraphFactorySettings
+from pyams_content.shared.common.interfaces import ISharedContentFactory
+from pyams_content.shared.common.manager import SharedTool
+from pyams_content.shared.form import Form
+from pyams_content.shared.form.interfaces import FORM_CONTENT_TYPE, IFormsManager, \
+    IFormsManagerFactory
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import utility_config
+from pyams_utils.traversing import get_parent
+
+
 __docformat__ = 'restructuredtext'
 
 
-# import standard library
-
-# import interfaces
-from pyams_content.shared.common.interfaces import ISharedContentFactory
-from pyams_content.shared.form.interfaces import IFormsManager, FORM_CONTENT_TYPE, IFormsManagerFactory
-from zope.component.interfaces import ISite
-from zope.lifecycleevent.interfaces import IObjectAddedEvent
-
-# import packages
-from pyams_content.shared.common.manager import SharedTool
-from pyams_content.shared.form import Form
-from pyams_utils.adapter import adapter_config
-from pyams_utils.registry import utility_config
-from pyams_utils.traversing import get_parent
-from pyramid.events import subscriber
-from zope.interface import implementer
-
-
-@implementer(IFormsManager)
+@implementer(IFormsManager, IParagraphFactorySettings)
 class FormsManager(SharedTool):
     """Forms manager class"""
 
--- a/src/pyams_content/shared/form/zmi/field.py	Wed Sep 04 15:57:43 2019 +0200
+++ b/src/pyams_content/shared/form/zmi/field.py	Wed Sep 04 15:58:26 2019 +0200
@@ -60,7 +60,7 @@
 
 
 @viewlet_config(name='form-fields.menu', context=IFormFieldContainerTarget, layer=IAdminLayer,
-                manager=IPropertiesMenu, permission=VIEW_SYSTEM_PERMISSION, weight=110)
+                manager=IPropertiesMenu, permission=VIEW_SYSTEM_PERMISSION, weight=20)
 class FormFieldsMenu(MenuItem):
     """Form fields menu"""
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/form/zmi/paragraph.py	Wed Sep 04 15:58:26 2019 +0200
@@ -0,0 +1,116 @@
+#
+# Copyright (c) 2008-2019 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.
+#
+
+from z3c.form import button
+from z3c.form.interfaces import INPUT_MODE
+from zope.interface import implementer
+
+from pyams_content.component.paragraph import IParagraphContainerTarget, IParagraphFactorySettings
+from pyams_content.component.paragraph.zmi import IParagraphContainerView, BaseParagraphAddMenu, \
+    BaseParagraphAJAXAddForm, BaseParagraphAddForm, BaseParagraphAJAXEditForm, \
+    BaseParagraphPropertiesEditForm, IParagraphInnerEditFormButtons
+from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor
+from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.form import IWfForm
+from pyams_content.shared.form.field import FormFieldsParagraph
+from pyams_content.shared.form.interfaces import FORM_FIELDS_PARAGRAPH_TYPE, IFormFieldsParagraph
+from pyams_form.form import ajax_config
+from pyams_form.interfaces.form import IInnerForm
+from pyams_form.security import ProtectedFormObjectMixin
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
+from pyams_skin.layer import IPyAMSLayer
+from pyams_skin.viewlet.menu import MenuDivider
+from pyams_utils.adapter import adapter_config
+from pyams_utils.traversing import get_parent
+from pyams_viewlet.viewlet import viewlet_config
+
+
+__docformat__ = 'restructuredtext'
+
+from pyams_content import _
+
+
+@viewlet_config(name='add-form-fields-paragraph.divider', context=IParagraphContainerTarget,
+                view=IParagraphContainerView, layer=IPyAMSLayer, manager=IToolbarAddingMenu,
+                weight=899)
+class FormFieldsParagraphAddMenuDivider(ProtectedFormObjectMixin, MenuDivider):
+    """Form fields paragraph add menu divider"""
+
+    def __new__(cls, context, request, view, manager):
+        if not IWfForm.providedBy(context):
+            return None
+        settings = get_parent(context, IParagraphFactorySettings)
+        if (settings is not None) and (
+                FORM_FIELDS_PARAGRAPH_TYPE not in (settings.allowed_paragraphs or ())):
+            return None
+        return MenuDivider.__new__(cls)
+
+
+@viewlet_config(name='add-form-fields-paragraph.menu', context=IParagraphContainerTarget,
+                view=IParagraphContainerView, layer=IPyAMSLayer, manager=IToolbarAddingMenu,
+                weight=900)
+class FormFieldsParagraphAddMenu(BaseParagraphAddMenu):
+    """Form fields paragraph add menu"""
+
+    label = _("Form: input fields...")
+    label_css_class = 'fa fa-fw ' + FormFieldsParagraph.icon_class
+    url = 'add-form-fields-paragraph.html'
+    paragraph_type = FORM_FIELDS_PARAGRAPH_TYPE
+
+    def __new__(cls, context, request, view, manager):
+        if not IWfForm.providedBy(context):
+            return None
+        return BaseParagraphAddMenu.__new__(cls, context, request, view, manager)
+
+
+@pagelet_config(name='add-form-fields-paragraph.html', context=IParagraphContainerTarget,
+                layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-form-fields-paragraph.json', context=IParagraphContainerTarget,
+             layer=IPyAMSLayer, base=BaseParagraphAJAXAddForm)
+class FormFieldsParagraphAddForm(BaseParagraphAddForm):
+    """Form fields paragraph add form"""
+
+    legend = _("Add new form fields paragraph")
+
+    content_interface = IFormFieldsParagraph
+
+
+@pagelet_config(name='properties.html', context=IFormFieldsParagraph, layer=IPyAMSLayer,
+                permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IFormFieldsParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
+class FormFieldsParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
+    """Form fields paragraph properties edit form"""
+
+    prefix = 'form_fields_properties.'
+
+    legend = _("Edit form fields paragraph properties")
+
+    content_interface = IFormFieldsParagraph
+
+
+@adapter_config(context=(IFormFieldsParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IFormFieldsParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
+@implementer(IInnerForm)
+class FormFieldsParagraphInnerEditForm(FormFieldsParagraphPropertiesEditForm):
+    """Form fields paragraph properties inner edit form"""
+
+    legend = None
+
+    @property
+    def buttons(self):
+        if self.mode == INPUT_MODE:
+            return button.Buttons(IParagraphInnerEditFormButtons)
+        else:
+            return button.Buttons()
--- a/src/pyams_content/shared/form/zmi/properties.py	Wed Sep 04 15:57:43 2019 +0200
+++ b/src/pyams_content/shared/form/zmi/properties.py	Wed Sep 04 15:58:26 2019 +0200
@@ -14,12 +14,16 @@
 from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
 from zope.interface import Interface
 
+from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common.zmi.properties import SharedContentPropertiesEditForm
 from pyams_content.shared.form.interfaces import IWfForm
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_form.interfaces.form import IInnerSubForm
+from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.adapter import adapter_config
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 from pyams_zmi.form import InnerAdminEditForm
 
 
@@ -28,10 +32,22 @@
 from pyams_content import _
 
 
+@pagelet_config(name='properties.html', context=IWfForm, layer=IPyAMSLayer,
+                permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IWfForm, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION)
+class FormPropertiesEditForm(SharedContentPropertiesEditForm):
+    """Form properties edit form"""
+
+    interface = IWfForm
+    fieldnames = ('title', 'short_name', 'content_url', 'alt_title',
+                  'header', 'description', 'notepad')
+
+
 @adapter_config(name='form-settings',
-                context=(IWfForm, IPyAMSLayer, SharedContentPropertiesEditForm),
+                context=(IWfForm, IPyAMSLayer, FormPropertiesEditForm),
                 provides=IInnerSubForm)
-class FormPropertiesEditForm(InnerAdminEditForm):
+class FormPropertiesInnerEditForm(InnerAdminEditForm):
     """Form properties edit form extension"""
 
     prefix = 'form_properties.'
@@ -39,8 +55,9 @@
     legend = _("Main form settings")
     fieldset_class = 'bordered no-x-margin margin-y-10'
 
-    fields = field.Fields(IWfForm).select('user_title', 'form_header', 'auth_only', 'submit_label',
-                                          'submit_message', 'handler', 'use_captcha', 'client_captcha_key',
+    fields = field.Fields(IWfForm).select('form_header', 'user_title', 'auth_only',
+                                          'submit_label', 'submit_message', 'handler',
+                                          'use_captcha', 'client_captcha_key',
                                           'server_captcha_key', 'captcha_proxy')
     fields['use_captcha'].widgetFactory = SingleCheckBoxFieldWidget
 
@@ -48,17 +65,18 @@
 
     def updateGroups(self):
         self.add_group(NamedWidgetsGroup(self, 'head', self.widgets,
-                                         ('user_title', 'form_header', 'auth_only',
+                                         ('form_header', 'user_title', 'auth_only',
                                           'submit_label', 'submit_message', 'handler')))
         self.add_group(NamedWidgetsGroup(self, 'captcha', self.widgets,
-                                         ('use_captcha', 'client_captcha_key', 'server_captcha_key', 'captcha_proxy'),
+                                         ('use_captcha', 'client_captcha_key',
+                                          'server_captcha_key', 'captcha_proxy'),
                                          fieldset_class='inner bordered',
                                          legend=_("Add captcha"),
                                          css_class='inner',
                                          switch=True,
                                          checkbox_switch=True,
                                          checkbox_field=IWfForm['use_captcha']))
-        super(FormPropertiesEditForm, self).updateGroups()
+        super(FormPropertiesInnerEditForm, self).updateGroups()
 
     def get_ajax_output(self, changes):
         if 'handler' in changes.get(IWfForm, ()):
@@ -67,7 +85,7 @@
                 'message': self.request.localizer.translate(self.successMessage)
             }
         else:
-            return super(FormPropertiesEditForm, self).get_ajax_output(changes)
+            return super(FormPropertiesInnerEditForm, self).get_ajax_output(changes)
 
 
 @adapter_config(name='handler-settings',
@@ -91,7 +109,8 @@
         handler = self.context.query_handler()
         if handler is not None:
             translate = self.request.localizer.translate
-            return translate(_("« {handler} » form handler settings")).format(handler=translate(handler.label))
+            return translate(_("« {handler} » form handler settings")).format(
+                handler=translate(handler.label))
         else:
             return _("Form handler settings")