# HG changeset patch # User Thierry Florac # Date 1564139404 -7200 # Node ID 0c41d132ccb7d3b3cfba5c1599e0f340a58edc92 # Parent 3dd8901d34cf3a8865774a4fb26e72a165d5ce53 Updated shared form rendering diff -r 3dd8901d34cf -r 0c41d132ccb7 src/pyams_default_theme/shared/form/__init__.py --- a/src/pyams_default_theme/shared/form/__init__.py Fri Jul 26 13:08:32 2019 +0200 +++ b/src/pyams_default_theme/shared/form/__init__.py Fri Jul 26 13:10:04 2019 +0200 @@ -10,23 +10,42 @@ # FOR A PARTICULAR PURPOSE. # -__docformat__ = 'restructuredtext' - +import requests +from pyramid.csrf import get_csrf_token from z3c.form import button, field +from z3c.form.interfaces import HIDDEN_MODE from zope.interface import Interface +from zope.schema import TextLine from pyams_content.features.renderer.interfaces import ISharedContentRenderer -from pyams_content.shared.form import IFormFieldContainer, IFormFieldContainerTarget +from pyams_content.shared.form import IFormFieldContainer, IFormFieldContainerTarget, IWfForm +from pyams_default_theme.component.paragraph.interfaces import IParagraphContainerPortletRenderer from pyams_default_theme.features.renderer import BaseContentRenderer -from pyams_form.form import InnerAddForm +from pyams_default_theme.layer import IPyAMSDefaultLayer +from pyams_form.form import AddForm from pyams_form.help import FormHelp -from pyams_form.interfaces.form import IFormHelp +from pyams_form.interfaces.form import IFormHelp, IFormSuffixViewletsManager from pyams_i18n.interfaces import II18n +from pyams_pagelet.pagelet import pagelet_config +from pyams_portal.views import PortalContextIndexPage from pyams_skin.layer import IPyAMSLayer +from pyams_template.template import template_config from pyams_utils.adapter import adapter_config +from pyams_utils.interfaces import PUBLIC_PERMISSION, VIEW_PERMISSION +from pyams_utils.url import relative_url +from pyams_viewlet.viewlet import ViewContentProvider, Viewlet, viewlet_config -class FormFieldContainerDisplayForm(InnerAddForm): +__docformat__ = 'restructuredtext' + +from pyams_default_theme import _ + + +CSRF_FIELD_NAME = 'csrf_token' +RECAPTCHA_FIELD_NAME = 'g-recaptcha-response' + + +class FormFieldContainerDisplayForm(AddForm): """Form fields container display form""" @property @@ -34,22 +53,80 @@ return II18n(self.context).query_attribute('user_title', request=self.request) @property + def edit_permission(self): + if self.context.auth_only: + return VIEW_PERMISSION + else: + return PUBLIC_PERMISSION + + def get_form_action(self): + return relative_url(self.context, self.request, view_name='submit.html') + + @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 get_fields(): + token = TextLine(title=_("CSRF token"), required=True) + token.__name__ = CSRF_FIELD_NAME + yield token + if self.context.use_captcha: + captcha = TextLine(title=_("Captcha"), required=True) + captcha.__name__ = RECAPTCHA_FIELD_NAME + yield captcha + yield from IFormFieldContainer(self.context).get_fields() + + return field.Fields(*tuple(get_fields())) + + def updateActions(self): + super(FormFieldContainerDisplayForm, self).updateActions() + if 'submit' in self.actions: + self.actions['submit'].title = II18n(self.context).query_attribute('submit_label', + request=self.request) def updateWidgets(self, prefix=None): super(FormFieldContainerDisplayForm, 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 + if widget.field.__name__ == CSRF_FIELD_NAME: + widget.name = CSRF_FIELD_NAME + widget.mode = HIDDEN_MODE + widget.value = get_csrf_token(self.request) + elif widget.field.__name__ == RECAPTCHA_FIELD_NAME: + widget.name = RECAPTCHA_FIELD_NAME + widget.mode = HIDDEN_MODE + else: + field = IFormFieldContainer(self.context).get(widget.field.__name__) + if field is not None: + widget.placeholder = field.placeholder + + @button.buttonAndHandler('title', name='submit') + def update_content(self, action): + form = IWfForm(self.context) + handler = form.query_handler() + if handler is not None: + data, errors = self.extractData() + if errors: + self.status = self.formErrorsMessage + else: + if CSRF_FIELD_NAME in data: + del data[CSRF_FIELD_NAME] + if self.context.use_captcha: + if RECAPTCHA_FIELD_NAME not in data: + self.status = self.request.localizer.translate(_("Missing recaptcha token!")) + return + proxies = {'https': self.context.captcha_proxy} if self.context.captcha_proxy else {} + recaptcha_verify_api = self.request.registry.settings.get('pyams.recaptcha.verify') + if not recaptcha_verify_api: + recaptcha_verify_api = 'https://www.google.com/recaptcha/api/siteverify' + verify = requests.post(recaptcha_verify_api, { + 'secret': self.context.server_captcha_key, + 'response': data[RECAPTCHA_FIELD_NAME] + }, proxies=proxies).json() + if not verify['success']: + self.status = self.request.localizer.translate( + _("Can't verify recaptcha token! Are you a robot?")) + return + del data[RECAPTCHA_FIELD_NAME] + handler.handle(form, data) @adapter_config(context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldContainerDisplayForm), @@ -64,26 +141,69 @@ @property def message(self): - return II18n(self.context).query_attribute('header', request=self.request) + return II18n(self.context).query_attribute('form_header', request=self.request) or '' message_format = 'text' +@viewlet_config(name='recaptcha', context=IFormFieldContainerTarget, layer=IPyAMSDefaultLayer, + view=FormFieldContainerDisplayForm, manager=IFormSuffixViewletsManager) +@template_config(template='templates/recaptcha.pt', layer=IPyAMSDefaultLayer) +class FormCaptchaViewlet(Viewlet): + """Form captcha viewlet""" + + def __new__(cls, context, request, view, manager): + if not context.use_captcha: + return None + return Viewlet.__new__(cls) + + @adapter_config(name='form-render', context=(IFormFieldContainerTarget, IPyAMSLayer), provides=ISharedContentRenderer) class FormFieldContainerRenderer(BaseContentRenderer): """Form field container renderer""" weight = 20 - display_form = None + input_form = None def __init__(self, context, request): super(FormFieldContainerRenderer, self).__init__(context, request) - self.display_form = FormFieldContainerDisplayForm(context, self.request) + self.input_form = FormFieldContainerDisplayForm(context, self.request) def update(self): super(FormFieldContainerRenderer, self).update() - self.display_form.update() + self.input_form.update() def render(self): - return self.display_form.render() + return self.input_form.render() + + +@pagelet_config(name='submit.html', context=IWfForm, layer=IPyAMSDefaultLayer) +class FormSubmitPage(PortalContextIndexPage): + """Form submit page""" + + input_form = None + + def __init__(self, context, request): + super(FormSubmitPage, self).__init__(context, request) + self.input_form = FormFieldContainerDisplayForm(context, self.request) + + def update(self): + super(FormSubmitPage, self).update() + self.input_form.update() + + +@adapter_config(name='submit.html', context=(IWfForm, IPyAMSDefaultLayer, Interface), + provides=IParagraphContainerPortletRenderer) +@template_config(template='templates/form-submit.pt', layer=IPyAMSDefaultLayer) +class FormSubmitPortletRenderer(ViewContentProvider): + """Form submit message portlet renderer""" + + use_portlets_cache = True + + def render(self): + form = self.view.input_form + if form.widgets.errors: + return form.render() + else: + return super(FormSubmitPortletRenderer, self).render() diff -r 3dd8901d34cf -r 0c41d132ccb7 src/pyams_default_theme/shared/form/templates/form-submit.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_default_theme/shared/form/templates/form-submit.pt Fri Jul 26 13:10:04 2019 +0200 @@ -0,0 +1,1 @@ +
${structure:i18n:context.submit_message}
\ No newline at end of file diff -r 3dd8901d34cf -r 0c41d132ccb7 src/pyams_default_theme/shared/form/templates/recaptcha.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_default_theme/shared/form/templates/recaptcha.pt Fri Jul 26 13:10:04 2019 +0200 @@ -0,0 +1,8 @@ + + \ No newline at end of file