diff --git a/docs/api.rst b/docs/api.rst index b9a0dc70..881f8bf1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -8,8 +8,10 @@ Forms and Fields .. module:: flask_wtf -.. autoclass:: Form - :members: +.. autoclass:: FlaskForm + :members: + +.. autoclass:: Form(...) .. autoclass:: RecaptchaField diff --git a/examples/babel/app.py b/examples/babel/app.py index 0a001ad3..cb9373df 100644 --- a/examples/babel/app.py +++ b/examples/babel/app.py @@ -1,12 +1,12 @@ from flask import Flask, render_template, request from wtforms import TextField from wtforms.validators import DataRequired -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_babel import Babel from flask_babel import lazy_gettext as _ -class BabelForm(Form): +class BabelForm(FlaskForm): name = TextField(_('Name'), validators=[DataRequired()]) diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 49d43fce..75ca6de2 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -14,7 +14,7 @@ from __future__ import with_statement from flask import (Flask, session, redirect, url_for, abort, render_template, flash) -from flask_wtf import Form +from flask_wtf import FlaskForm from wtforms import TextField, TextAreaField, PasswordField, SubmitField from wtforms.validators import DataRequired, ValidationError from flask_sqlalchemy import SQLAlchemy @@ -46,14 +46,14 @@ class Entry(db.Model): db.create_all() -class EntryForm(Form): +class EntryForm(FlaskForm): title = TextField("Title", validators=[DataRequired()]) text = TextAreaField("Text") submit = SubmitField("Share") -class LoginForm(Form): +class LoginForm(FlaskForm): username = TextField("Username") password = PasswordField("Password") diff --git a/examples/recaptcha/app.py b/examples/recaptcha/app.py index 8082dac7..e2bda74b 100644 --- a/examples/recaptcha/app.py +++ b/examples/recaptcha/app.py @@ -1,7 +1,7 @@ from flask import Flask, render_template, flash, session, redirect, url_for from wtforms import TextAreaField from wtforms.validators import DataRequired -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_wtf.recaptcha import RecaptchaField @@ -17,7 +17,7 @@ app.config.from_object(__name__) -class CommentForm(Form): +class CommentForm(FlaskForm): comment = TextAreaField("Comment", validators=[DataRequired()]) recaptcha = RecaptchaField() diff --git a/examples/uploadr/app.py b/examples/uploadr/app.py index 7c606771..c65892ef 100644 --- a/examples/uploadr/app.py +++ b/examples/uploadr/app.py @@ -1,9 +1,9 @@ from flask import Flask, render_template -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_wtf.file import FileField from wtforms import FieldList -class FileUploadForm(Form): +class FileUploadForm(FlaskForm): uploads = FieldList(FileField()) DEBUG = True diff --git a/flask_wtf/__init__.py b/flask_wtf/__init__.py index 5e0c2f22..e00fb98e 100644 --- a/flask_wtf/__init__.py +++ b/flask_wtf/__init__.py @@ -12,7 +12,7 @@ # flake8: noqa from __future__ import absolute_import -from .form import Form +from .form import FlaskForm, Form from .csrf import CsrfProtect from .recaptcha import * diff --git a/flask_wtf/_compat.py b/flask_wtf/_compat.py index bb6e9edd..178e9950 100644 --- a/flask_wtf/_compat.py +++ b/flask_wtf/_compat.py @@ -1,4 +1,6 @@ import sys +import warnings + if sys.version_info[0] == 3: text_type = str string_types = (str,) @@ -19,3 +21,11 @@ def to_unicode(input_bytes, encoding='utf-8'): if not isinstance(input_bytes, string_types): input_bytes = input_bytes.decode(encoding) return input_bytes + + +class FlaskWTFDeprecationWarning(DeprecationWarning): + pass + + +warnings.simplefilter('always', FlaskWTFDeprecationWarning) +warnings.filterwarnings('ignore', category=FlaskWTFDeprecationWarning, module='wtforms|flask_wtf') diff --git a/flask_wtf/form.py b/flask_wtf/form.py index d2d4c29d..df88367b 100644 --- a/flask_wtf/form.py +++ b/flask_wtf/form.py @@ -1,14 +1,17 @@ # coding: utf-8 +import warnings import werkzeug.datastructures - -from jinja2 import Markup, escape from flask import request, session, current_app +from jinja2 import Markup, escape +from wtforms.compat import with_metaclass +from wtforms.ext.csrf.form import SecureForm from wtforms.fields import HiddenField -from wtforms.widgets import HiddenInput +from wtforms.form import FormMeta from wtforms.validators import ValidationError -from wtforms.ext.csrf.form import SecureForm -from ._compat import text_type, string_types +from wtforms.widgets import HiddenInput + +from ._compat import text_type, string_types, FlaskWTFDeprecationWarning from .csrf import generate_csrf, validate_csrf try: @@ -19,11 +22,11 @@ SUBMIT_METHODS = set(('POST', 'PUT', 'PATCH', 'DELETE')) -class _Auto(): - '''Placeholder for unspecified variables that should be set to defaults. +class _Auto(object): + """Placeholder for unspecified variables that should be set to defaults. Used when None is a valid option and should not be replaced by a default. - ''' + """ pass @@ -36,28 +39,27 @@ def _is_hidden(field): return False -class Form(SecureForm): - """ - Flask-specific subclass of WTForms **SecureForm** class. +class FlaskForm(SecureForm): + """Flask-specific subclass of WTForms :class:`~wtforms.ext.csrf.form.SecureForm` class. - If formdata is not specified, this will use flask.request.form. - Explicitly pass formdata = None to prevent this. + If ``formdata`` is not specified, this will use :attr:`flask.request.form` and + :attr:`flask.request.files`. Explicitly pass ``formdata=None`` to prevent this. :param csrf_context: a session or dict-like object to use when making - CSRF tokens. Default: flask.session. + CSRF tokens. Default: :data:`flask.session`. :param secret_key: a secret key for building CSRF tokens. If this isn't - specified, the form will take the first of these - that is defined: + specified, the form will take the first of these + that is defined: - * SECRET_KEY attribute on this class - * WTF_CSRF_SECRET_KEY config of flask app - * SECRET_KEY config of flask app - * session secret key + * SECRET_KEY attribute on this class + * WTF_CSRF_SECRET_KEY config of Flask app + * SECRET_KEY config of Flask app + * session secret key :param csrf_enabled: whether to use CSRF protection. If False, all - csrf behavior is suppressed. - Default: WTF_CSRF_ENABLED config value + csrf behavior is suppressed. + Default: WTF_CSRF_ENABLED config value """ SECRET_KEY = None @@ -93,7 +95,7 @@ def __init__(self, formdata=_Auto, obj=None, prefix='', csrf_context=None, else: csrf_context = {} self.SECRET_KEY = '' - super(Form, self).__init__( + super(FlaskForm, self).__init__( formdata, obj, prefix, csrf_context=csrf_context, **kwargs @@ -169,7 +171,7 @@ def validate_on_submit(self): @property def data(self): - d = super(Form, self).data + d = super(FlaskForm, self).data # https://github.com/lepture/flask-wtf/issues/208 if self.csrf_enabled: d.pop('csrf_token', None) @@ -179,3 +181,19 @@ def _get_translations(self): if not current_app.config.get('WTF_I18N_ENABLED', True): return None return translations + + +class DeprecatedFormMeta(FormMeta): + def __init__(cls, name, bases, attrs): + warnings.warn(FlaskWTFDeprecationWarning( + '"flask_wtf.Form" has been renamed to "FlaskForm" ' + 'and will be removed in 1.0.' + ), stacklevel=2) + type.__init__(cls, name, bases, attrs) + + +class Form(with_metaclass(DeprecatedFormMeta, FlaskForm)): + """ + .. deprecated:: 0.13 + Renamed to :class:`~flask_wtf.FlaskForm`. + """ diff --git a/tests/base.py b/tests/base.py index b8ae17b7..717c174c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -3,7 +3,7 @@ from flask import Flask, render_template, jsonify from wtforms import StringField, HiddenField, SubmitField from wtforms.validators import DataRequired -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_wtf._compat import text_type @@ -13,13 +13,13 @@ def to_unicode(text): return text -class MyForm(Form): +class MyForm(FlaskForm): SECRET_KEY = "a poorly kept secret." name = StringField("Name", validators=[DataRequired()]) submit = SubmitField("Submit") -class HiddenFieldsForm(Form): +class HiddenFieldsForm(FlaskForm): SECRET_KEY = "a poorly kept secret." name = HiddenField() url = HiddenField() @@ -32,7 +32,7 @@ def __init__(self, *args, **kwargs): self.method.name = '_method' -class SimpleForm(Form): +class SimpleForm(FlaskForm): SECRET_KEY = "a poorly kept secret." pass diff --git a/tests/test_form.py b/tests/test_form.py new file mode 100644 index 00000000..e98b32ac --- /dev/null +++ b/tests/test_form.py @@ -0,0 +1,15 @@ +import warnings +from unittest import TestCase +from flask_wtf import Form +from flask_wtf._compat import FlaskWTFDeprecationWarning + + +class TestForm(TestCase): + def test_deprecated_form(self): + def define_deprecated_form(): + class F(Form): + pass + + with warnings.catch_warnings(): + warnings.simplefilter('error', FlaskWTFDeprecationWarning) + self.assertRaises(FlaskWTFDeprecationWarning, define_deprecated_form) diff --git a/tests/test_recaptcha.py b/tests/test_recaptcha.py index 644dceab..d4070742 100644 --- a/tests/test_recaptcha.py +++ b/tests/test_recaptcha.py @@ -3,7 +3,7 @@ from .base import TestCase from flask import json from flask import Flask, render_template -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_wtf.recaptcha import RecaptchaField @@ -11,7 +11,7 @@ RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu' -class RecaptchaFrom(Form): +class RecaptchaFrom(FlaskForm): SECRET_KEY = "a poorly kept secret." recaptcha = RecaptchaField() diff --git a/tests/test_uploads.py b/tests/test_uploads.py index 27c031b2..62bb2435 100644 --- a/tests/test_uploads.py +++ b/tests/test_uploads.py @@ -8,7 +8,7 @@ from flask import render_template, request from wtforms import StringField, FieldList -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_wtf.file import FileField from flask_wtf.file import file_required, file_allowed @@ -30,21 +30,21 @@ def file_allowed(self, storage, basename): images = UploadSet('images', ['jpg', 'png']) -class FileUploadForm(Form): +class FileUploadForm(FlaskForm): upload = FileField("Upload file") -class MultipleFileUploadForm(Form): +class MultipleFileUploadForm(FlaskForm): uploads = FieldList(FileField("upload"), min_entries=3) -class ImageUploadForm(Form): +class ImageUploadForm(FlaskForm): upload = FileField("Upload file", validators=[file_required(), file_allowed(images)]) -class TextUploadForm(Form): +class TextUploadForm(FlaskForm): upload = FileField("Upload file", validators=[file_required(), file_allowed(['txt'])]) @@ -152,7 +152,7 @@ def test_invalid_image_file(self): assert b'invalid' in response.data -class BrokenForm(Form): +class BrokenForm(FlaskForm): text_fields = FieldList(StringField()) file_fields = FieldList(FileField())