# HG changeset patch # User Thierry Florac # Date 1568714583 -7200 # Node ID 88b5ce31afdcde56ca4ac3922bcca74a47c5f6dd # Parent 9b406fb98cfaa24337a3c33c9e80df7134e6dfc7 Added RGPD consent management properties diff -r 9b406fb98cfa -r 88b5ce31afdc src/pyams_content/shared/form/__init__.py --- a/src/pyams_content/shared/form/__init__.py Mon Sep 16 16:57:01 2019 +0200 +++ b/src/pyams_content/shared/form/__init__.py Tue Sep 17 12:03:03 2019 +0200 @@ -49,6 +49,9 @@ client_captcha_key = FieldProperty(IWfForm['client_captcha_key']) server_captcha_key = FieldProperty(IWfForm['server_captcha_key']) captcha_proxy = FieldProperty(IWfForm['captcha_proxy']) + rgpd_consent = FieldProperty(IWfForm['rgpd_consent']) + rgpd_warning = FieldProperty(IWfForm['rgpd_warning']) + rgpd_user_rights = FieldProperty(IWfForm['rgpd_user_rights']) @property def handler(self): diff -r 9b406fb98cfa -r 88b5ce31afdc src/pyams_content/shared/form/field.py --- a/src/pyams_content/shared/form/field.py Mon Sep 16 16:57:01 2019 +0200 +++ b/src/pyams_content/shared/form/field.py Tue Sep 17 12:03:03 2019 +0200 @@ -9,11 +9,6 @@ # 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' from collections import OrderedDict @@ -27,10 +22,12 @@ 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, IFormFieldsParagraph, \ - FORM_FIELDS_PARAGRAPH_NAME, FORM_FIELDS_PARAGRAPH_TYPE, FORM_FIELDS_PARAGRAPH_RENDERERS +from pyams_content.component.paragraph import BaseParagraph, BaseParagraphFactory, IParagraphFactory +from pyams_content.features.renderer import RenderersVocabulary +from pyams_content.shared.form.interfaces import FORM_FIELDS_PARAGRAPH_NAME, \ + FORM_FIELDS_PARAGRAPH_RENDERERS, FORM_FIELDS_PARAGRAPH_TYPE, FORM_FIELD_CONTAINER_KEY, \ + IFormField, IFormFieldContainer, IFormFieldContainerTarget, IFormFieldFactory, \ + IFormFieldsParagraph, IWfForm from pyams_form.interfaces.form import IFormContextPermissionChecker from pyams_i18n.interfaces import II18n from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter @@ -41,6 +38,9 @@ from pyams_utils.traversing import get_parent from pyams_utils.vocabulary import vocabulary_config + +__docformat__ = 'restructuredtext' + from pyams_content import _ @@ -80,7 +80,7 @@ def find_fields(self, factory): for field in self.values(): - if field.field_type == factory: + if field.visible and (field.field_type == factory): yield field diff -r 9b406fb98cfa -r 88b5ce31afdc src/pyams_content/shared/form/handler.py --- a/src/pyams_content/shared/form/handler.py Mon Sep 16 16:57:01 2019 +0200 +++ b/src/pyams_content/shared/form/handler.py Tue Sep 17 12:03:03 2019 +0200 @@ -76,6 +76,6 @@ target_interface = IMailtoHandlerTarget handler_info = IMailtoHandlerInfo - def handle(self, form, data): + def handle(self, form, data, user_data): # TODO: handle form data pass diff -r 9b406fb98cfa -r 88b5ce31afdc src/pyams_content/shared/form/interfaces.py --- a/src/pyams_content/shared/form/interfaces.py Mon Sep 16 16:57:01 2019 +0200 +++ b/src/pyams_content/shared/form/interfaces.py Tue Sep 17 12:03:03 2019 +0200 @@ -19,7 +19,7 @@ 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 +from pyams_i18n.schema import I18nHTMLField, I18nTextField, I18nTextLineField from pyams_utils.schema import MailAddressField, TextLineListField @@ -99,6 +99,13 @@ """Get schema field matching given form field""" +class IFormFieldDataConverter(Interface): + """Interface of a converter adapter which can be used to convert form data""" + + def convert(self, value): + """Convert given input value""" + + FORM_FIELDS_PARAGRAPH_TYPE = 'form-fields' FORM_FIELDS_PARAGRAPH_NAME = _("Form fields") FORM_FIELDS_PARAGRAPH_RENDERERS = 'PyAMS.paragraph.formfields.renderers' @@ -145,6 +152,8 @@ required=False) user_title = I18nTextLineField(title=_("Form title"), + description=_("If set, this title will be displayed above input " + "fields"), required=False) auth_only = Bool(title=_("Authenticated only?"), @@ -187,6 +196,27 @@ "support..."), required=False) + rgpd_consent = Bool(title=_("Required RGPD consent?"), + description=_("If 'yes', an RGPD compliance warning will be displayed " + "above form's submit button; form can't be submitted as long " + "as the associated checkbox will not be checked explicitly " + "by the user"), + required=True, + default=True) + + rgpd_warning = I18nTextField(title=_("RGPD consent text"), + description=_("User consent must be explicit, and user must be " + "warned about usage which will be made of submitted " + "data; text samples are given below"), + required=False) + + rgpd_user_rights = I18nHTMLField(title=_("RGPD user rights"), + description=_("The internet user must be able to easily " + "revoke his consent later on, so it is " + "important to inform him how to proceed; below " + "are examples of possible formulations"), + required=False) + def query_handler(self, handler=None): """Get form handler utility""" @@ -210,8 +240,13 @@ target_interface = Attribute("Handler target marker interface") handler_info = Attribute("Handler info interface") - def handle(self, form, data): - """Handle entered data""" + def handle(self, form, data, user_data): + """Handle entered data + + :param form: input form + :param data: raw form data + :param user_data: user friendly form input data + """ class IFormHandlerInfo(Interface): diff -r 9b406fb98cfa -r 88b5ce31afdc src/pyams_content/shared/form/zmi/properties.py --- a/src/pyams_content/shared/form/zmi/properties.py Mon Sep 16 16:57:01 2019 +0200 +++ b/src/pyams_content/shared/form/zmi/properties.py Tue Sep 17 12:03:03 2019 +0200 @@ -9,10 +9,11 @@ # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # - +from pyramid.events import subscriber from z3c.form import field from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget -from zope.interface import Interface +from z3c.form.interfaces import IDataExtractedEvent, INPUT_MODE +from zope.interface import Interface, Invalid from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION from pyams_content.shared.common.zmi.properties import SharedContentPropertiesEditForm @@ -58,11 +59,32 @@ 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') + 'server_captcha_key', 'captcha_proxy', + 'rgpd_consent', 'rgpd_warning', 'rgpd_user_rights') fields['use_captcha'].widgetFactory = SingleCheckBoxFieldWidget + fields['rgpd_consent'].widgetFactory = SingleCheckBoxFieldWidget weight = 1 + def updateWidgets(self, prefix=None): + super(FormPropertiesInnerEditForm, self).updateWidgets(prefix) + if self.mode == INPUT_MODE: + translate = self.request.localizer.translate + if 'rgpd_warning' in self.widgets: + self.widgets['rgpd_warning'].after_widget_notice = \ + '
{0}
'.format( + translate(_("Text samples:
" + "- By submitting this form, I agree that the information " + "entered may be used for the purpose of my request and the " + "business relationship that may result from it."))) + if 'rgpd_user_rights' in self.widgets: + self.widgets['rgpd_user_rights'].after_widget_notice = \ + '
{0}
'.format( + translate(_("Text samples:
" + "- To know and enforce your rights, including the right to " + "withdraw your consent to the use of the data collected by " + "this form, please consult our privacy policy."))) + def updateGroups(self): self.add_group(NamedWidgetsGroup(self, 'head', self.widgets, ('form_header', 'user_title', 'auth_only', @@ -76,6 +98,14 @@ switch=True, checkbox_switch=True, checkbox_field=IWfForm['use_captcha'])) + self.add_group(NamedWidgetsGroup(self, 'rgpd', self.widgets, + ('rgpd_consent', 'rgpd_warning', 'rgpd_user_rights'), + fieldset_class='inner bordered', + legend=_("Add RGPD warning"), + css_class='inner', + switch=True, + checkbox_switch=True, + checkbox_field=IWfForm['rgpd_consent'])) super(FormPropertiesInnerEditForm, self).updateGroups() def get_ajax_output(self, changes): @@ -88,6 +118,23 @@ return super(FormPropertiesInnerEditForm, self).get_ajax_output(changes) +@subscriber(IDataExtractedEvent, form_selector=FormPropertiesInnerEditForm) +def check_form_properties_data(event): + """Check form properties input data""" + data = event.data + if data.get('rgpd_consent'): + for attr in ('rgpd_warning', 'rgpd_user_rights'): + attr_ok = False + for lang, value in data.get(attr, {}).items(): + if value: + attr_ok = True + break + if not attr_ok: + event.form.widgets.errors += (Invalid(_("You MUST set an RGPD consent text and " + "RGPD user rights to enable RGPD!")),) + return + + @adapter_config(name='handler-settings', context=(IWfForm, IPyAMSLayer, SharedContentPropertiesEditForm), provides=IInnerSubForm)