--- 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()