# HG changeset patch # User Thierry Florac # Date 1567605506 -7200 # Node ID 530cbb970243229500a221167ee78010a6307adf # Parent 999fa08d99c2d38c6e4d9e3e032085f67d1a3902 Updated shared forms management diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/__init__.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']) diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/field.py --- 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 diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/interfaces.py --- 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): diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/manager.py --- 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""" diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/zmi/field.py --- 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""" diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/zmi/paragraph.py --- /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 +# 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() diff -r 999fa08d99c2 -r 530cbb970243 src/pyams_content/shared/form/zmi/properties.py --- 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")