import warnings from flask import current_app, request from flask import session from jinja2 import Markup from werkzeug.datastructures import CombinedMultiDict, ImmutableMultiDict from werkzeug.utils import cached_property from wtforms import Form from wtforms.meta import DefaultMeta from wtforms.widgets import HiddenInput from ._compat import FlaskWTFDeprecationWarning, string_types, text_type from .csrf import _FlaskFormCSRF try: from .i18n import translations except ImportError: translations = None # babel not installed SUBMIT_METHODS = set(('POST', 'PUT', 'PATCH', 'DELETE')) _Auto = object() class FlaskForm(Form): """Flask-specific subclass of WTForms :class:`~wtforms.form.Form`. If ``formdata`` is not specified, this will use :attr:`flask.request.form` and :attr:`flask.request.files`. Explicitly pass ``formdata=None`` to prevent this. """ class Meta(DefaultMeta): csrf_class = _FlaskFormCSRF csrf_context = session # not used, provided for custom csrf_class @cached_property def csrf(self): return current_app.config.get('WTF_CSRF_ENABLED', True) @cached_property def csrf_secret(self): return current_app.config.get( 'WTF_CSRF_SECRET_KEY', current_app.secret_key ) @cached_property def csrf_field_name(self): return current_app.config.get('WTF_CSRF_FIELD_NAME', 'csrf_token') @cached_property def csrf_time_limit(self): return current_app.config.get('WTF_CSRF_TIME_LIMIT', 3600) def wrap_formdata(self, form, formdata): if formdata is _Auto: if _is_submitted(): if request.files: return CombinedMultiDict(( request.files, request.form )) elif request.form: return request.form elif request.get_json(): return ImmutableMultiDict(request.get_json()) return None return formdata def get_translations(self, form): if not current_app.config.get('WTF_I18N_ENABLED', True): return None return translations def __init__(self, formdata=_Auto, **kwargs): csrf_enabled = kwargs.pop('csrf_enabled', None) if csrf_enabled is not None: warnings.warn(FlaskWTFDeprecationWarning( '"csrf_enabled" is deprecated and will be removed in 1.0. ' 'Set "meta.csrf" instead.' ), stacklevel=3) kwargs['meta'] = kwargs.get('meta') or {} kwargs['meta'].setdefault('csrf', csrf_enabled) super(FlaskForm, self).__init__(formdata=formdata, **kwargs) def is_submitted(self): """Consider the form submitted if there is an active request and the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. """ return _is_submitted() def validate_on_submit(self): """Call :meth:`validate` only if the form is submitted. This is a shortcut for ``form.is_submitted() and form.validate()``. """ return self.is_submitted() and self.validate() def hidden_tag(self, *fields): """Render the form's hidden fields in one call. A field is considered hidden if it uses the :class:`~wtforms.widgets.HiddenInput` widget. If ``fields`` are given, only render the given fields that are hidden. If a string is passed, render the field with that name if it exists. .. versionchanged:: 0.13 No longer wraps inputs in hidden div. This is valid HTML 5. .. versionchanged:: 0.13 Skip passed fields that aren't hidden. Skip passed names that don't exist. """ def hidden_fields(fields): for f in fields: if isinstance(f, string_types): f = getattr(self, f, None) if f is None or not isinstance(f.widget, HiddenInput): continue yield f return Markup( u'\n'.join(text_type(f) for f in hidden_fields(fields or self)) ) def _is_submitted(): """Consider the form submitted if there is an active request and the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. """ return bool(request) and request.method in SUBMIT_METHODS class Form(FlaskForm): """ .. deprecated:: 0.13 Renamed to :class:`~flask_wtf.FlaskForm`. """ def __init__(self, *args, **kwargs): warnings.warn(FlaskWTFDeprecationWarning( '"flask_wtf.Form" has been renamed to "FlaskForm" ' 'and will be removed in 1.0.' ), stacklevel=3) super(Form, self).__init__(*args, **kwargs)