diff -Nru flask-wtf-0.14.2/debian/changelog flask-wtf-0.14.3/debian/changelog --- flask-wtf-0.14.2/debian/changelog 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/changelog 2020-03-23 16:49:54.000000000 +0000 @@ -1,3 +1,27 @@ +flask-wtf (0.14.3-1) unstable; urgency=medium + + [ Emmanuel Arias ] + * Team Upload. + * New upstream version 0.14.3. + * d/control: Bump Standard-Versions to 4.5.0. + * Remove not applied patch. + * d/rules: Override test execution during the build. + * d/tests/test: using py3versions -s to make sure all + supported version are installed. + - Change the autopkgtest test for a non-trivial test. + * d/salsa-ci.yml: enable salsa-ci. + * Add patch to deactivate intersphinx extension. + + [ Debian Janitor ] + * Use correct machine-readable copyright file URI. + * Use secure URI in Homepage field. + * Bump debhelper from old 9 to 12. + * Remove patches 0001-Disable-test-that-uses-internet.patch that are + missing from debian/patches/series. + * Set upstream metadata fields: Repository. + + -- Emmanuel Arias Mon, 23 Mar 2020 12:49:54 -0400 + flask-wtf (0.14.2-4) unstable; urgency=medium * Team upload. diff -Nru flask-wtf-0.14.2/debian/control flask-wtf-0.14.3/debian/control --- flask-wtf-0.14.2/debian/control 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/control 2020-03-23 16:38:06.000000000 +0000 @@ -3,7 +3,7 @@ Uploaders: Bernd Zeimetz Section: python Priority: optional -Build-Depends: debhelper-compat (= 9), +Build-Depends: debhelper-compat (= 12), dh-python, python3-all, python3-setuptools, @@ -12,8 +12,8 @@ python3-wtforms, python3-nose, python3-flask-babel -Standards-Version: 4.1.2 -Homepage: http://packages.python.org/Flask-WTF/ +Standards-Version: 4.5.0 +Homepage: https://packages.python.org/Flask-WTF/ Vcs-Git: https://salsa.debian.org/python-team/modules/flask-wtf.git Vcs-Browser: https://salsa.debian.org/python-team/modules/flask-wtf diff -Nru flask-wtf-0.14.2/debian/copyright flask-wtf-0.14.3/debian/copyright --- flask-wtf-0.14.2/debian/copyright 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/copyright 2020-03-23 16:38:06.000000000 +0000 @@ -1,4 +1,4 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: flask-wtf Source: https://pypi.python.org/pypi/Flask-WTF diff -Nru flask-wtf-0.14.2/debian/patches/0001-Disable-test-that-uses-internet.patch flask-wtf-0.14.3/debian/patches/0001-Disable-test-that-uses-internet.patch --- flask-wtf-0.14.2/debian/patches/0001-Disable-test-that-uses-internet.patch 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/patches/0001-Disable-test-that-uses-internet.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -From 97ca4f193494097d713a6309e162431fe3fef45c Mon Sep 17 00:00:00 2001 -From: Orestis Ioannou -Date: Thu, 4 Feb 2016 00:15:37 +0100 -Subject: Disable test that uses internet - ---- - tests/test_recaptcha.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/tests/test_recaptcha.py b/tests/test_recaptcha.py -index bd8c93a..d71c490 100644 ---- a/tests/test_recaptcha.py -+++ b/tests/test_recaptcha.py -@@ -39,7 +39,7 @@ class TestRecaptcha(TestCase): - response = self.client.post('/', data={}) - assert b'missing' in response.data - -- def test_send_recaptcha_request(self): -+ """def test_send_recaptcha_request(self): - response = self.client.post('/', data={ - 'g-recaptcha-response': 'test' - }) -@@ -48,7 +48,7 @@ class TestRecaptcha(TestCase): - response = self.client.post('/', data=json.dumps({ - 'g-recaptcha-response': 'test' - }), content_type='application/json') -- assert b'invalid' in response.data -+ assert b'invalid' in response.data""" - - def test_testing(self): - self.app.testing = True diff -Nru flask-wtf-0.14.2/debian/patches/0001-Remove-intersphinx-extension-to-avoid-external-conne.patch flask-wtf-0.14.3/debian/patches/0001-Remove-intersphinx-extension-to-avoid-external-conne.patch --- flask-wtf-0.14.2/debian/patches/0001-Remove-intersphinx-extension-to-avoid-external-conne.patch 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/debian/patches/0001-Remove-intersphinx-extension-to-avoid-external-conne.patch 2020-03-23 16:38:06.000000000 +0000 @@ -0,0 +1,20 @@ +From: Emmanuel Arias +Date: Mon, 23 Mar 2020 13:26:16 -0300 +Subject: Remove intersphinx extension to avoid external connections. + +--- + docs/conf.py | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/docs/conf.py b/docs/conf.py +index 4940ed0..464537e 100644 +--- a/docs/conf.py ++++ b/docs/conf.py +@@ -22,7 +22,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '_themes')) + # ones. + extensions = [ + 'sphinx.ext.autodoc', +- 'sphinx.ext.intersphinx', + ] + + # Add any paths that contain templates here, relative to this directory. diff -Nru flask-wtf-0.14.2/debian/patches/0002-disable-i18n-test.patch flask-wtf-0.14.3/debian/patches/0002-disable-i18n-test.patch --- flask-wtf-0.14.2/debian/patches/0002-disable-i18n-test.patch 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/patches/0002-disable-i18n-test.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ ---- a/tests/test_i18n.py -+++ b/tests/test_i18n.py -@@ -29,7 +29,7 @@ class TestI18NCase(TestCase): - headers={'Accept-Language': 'zh-CN,zh;q=0.8'}, - data={} - ) -- assert '\u8be5\u5b57\u6bb5\u662f' in to_unicode(response.data) -+ #assert '\u8be5\u5b57\u6bb5\u662f' in to_unicode(response.data) - - response = self.client.post("/", data={}) - assert b'This field is required.' in response.data diff -Nru flask-wtf-0.14.2/debian/patches/series flask-wtf-0.14.3/debian/patches/series --- flask-wtf-0.14.2/debian/patches/series 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/patches/series 2020-03-23 16:38:06.000000000 +0000 @@ -1,2 +1 @@ -#0001-Disable-test-that-uses-internet.patch -0002-disable-i18n-test.patch +0001-Remove-intersphinx-extension-to-avoid-external-conne.patch diff -Nru flask-wtf-0.14.2/debian/rules flask-wtf-0.14.3/debian/rules --- flask-wtf-0.14.2/debian/rules 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/rules 2020-03-23 16:38:06.000000000 +0000 @@ -5,6 +5,9 @@ %: dh $@ --with sphinxdoc,python3 --buildsystem=pybuild +override_dh_auto_test: + python3 setup.py test + override_dh_auto_build: # remove failing test file rm -f tests/test_recaptcha.py diff -Nru flask-wtf-0.14.2/debian/salsa-ci.yml flask-wtf-0.14.3/debian/salsa-ci.yml --- flask-wtf-0.14.2/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/debian/salsa-ci.yml 2020-03-23 16:38:06.000000000 +0000 @@ -0,0 +1,4 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru flask-wtf-0.14.2/debian/tests/control flask-wtf-0.14.3/debian/tests/control --- flask-wtf-0.14.2/debian/tests/control 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/tests/control 2020-03-23 16:38:06.000000000 +0000 @@ -1,3 +1,6 @@ -Depends: @ -Tests: import +Depends: python3-all, + python3-flask, + python3-wtforms, + python3-flask-babel +Tests: test Restrictions: allow-stderr diff -Nru flask-wtf-0.14.2/debian/tests/import flask-wtf-0.14.3/debian/tests/import --- flask-wtf-0.14.2/debian/tests/import 2019-08-07 16:33:45.000000000 +0000 +++ flask-wtf-0.14.3/debian/tests/import 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#!/bin/sh - -ext="flask_wtf" - -set -eux - -for py in $(py3versions -i); do - $py --version - $py -c "import $ext; print($ext)" -done diff -Nru flask-wtf-0.14.2/debian/tests/test flask-wtf-0.14.3/debian/tests/test --- flask-wtf-0.14.2/debian/tests/test 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/debian/tests/test 2020-03-23 16:38:06.000000000 +0000 @@ -0,0 +1,16 @@ +#!/bin/sh + +ext="flask_wtf" + +set -eux + +cat << EOF > test.py +from flask_wtf import FlaskForm + +class MyForm(FlaskForm): + pass +EOF + +for py in $(py3versions -s); do + $py "test.py" +done diff -Nru flask-wtf-0.14.2/debian/upstream/metadata flask-wtf-0.14.3/debian/upstream/metadata --- flask-wtf-0.14.2/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/debian/upstream/metadata 2020-03-23 16:38:06.000000000 +0000 @@ -0,0 +1 @@ +Repository: https://github.com/lepture/flask-wtf diff -Nru flask-wtf-0.14.2/docs/authors.rst flask-wtf-0.14.3/docs/authors.rst --- flask-wtf-0.14.2/docs/authors.rst 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/docs/authors.rst 2020-02-06 23:03:33.000000000 +0000 @@ -12,4 +12,4 @@ Find more contributors on GitHub_. -.. _GitHub: http://github.com/lepture/flask-wtf/contributors +.. _GitHub: https://github.com/lepture/flask-wtf/graphs/contributors diff -Nru flask-wtf-0.14.2/docs/changelog.rst flask-wtf-0.14.3/docs/changelog.rst --- flask-wtf-0.14.2/docs/changelog.rst 2017-01-10 09:55:28.000000000 +0000 +++ flask-wtf-0.14.3/docs/changelog.rst 2020-02-06 23:08:04.000000000 +0000 @@ -1,6 +1,14 @@ Flask-WTF Changelog =================== +Version 0.14.3 +-------------- + +Released 2020-02-06 + +- Fix deprecated imports from ``werkzeug`` and ``collections``. + + Version 0.14.2 -------------- @@ -46,7 +54,7 @@ (`#264`_) - ``CsrfProtect`` protects the ``DELETE`` method by default. (`#264`_) - The same CSRF token is generated for the lifetime of a request. It is exposed - as ``request.csrf_token`` for use during testing. (`#227`_, `#264`_) + as ``g.csrf_token`` for use during testing. (`#227`_, `#264`_) - ``CsrfProtect.error_handler`` is deprecated. (`#264`_) - Handlers that return a response work in addition to those that raise an @@ -191,8 +199,8 @@ - ``csrf_token`` for all template types `#112`_. - Make FileRequired a subclass of InputRequired `#108`_. -.. _`#108`: https://github.com/lepture/flask-wtf/issues/108 -.. _`#112`: https://github.com/lepture/flask-wtf/issues/112 +.. _`#108`: https://github.com/lepture/flask-wtf/pull/108 +.. _`#112`: https://github.com/lepture/flask-wtf/pull/112 Version 0.9.4 ------------- @@ -212,8 +220,8 @@ - Fix validation of recaptcha when app in testing mode `#89`_. - Bugfix for csrf module `#91`_ -.. _`#89`: https://github.com/lepture/flask-wtf/issues/89 -.. _`#91`: https://github.com/lepture/flask-wtf/issues/91 +.. _`#89`: https://github.com/lepture/flask-wtf/pull/89 +.. _`#91`: https://github.com/lepture/flask-wtf/pull/91 Version 0.9.2 diff -Nru flask-wtf-0.14.2/docs/config.rst flask-wtf-0.14.3/docs/config.rst --- flask-wtf-0.14.2/docs/config.rst 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/docs/config.rst 2020-02-06 23:03:33.000000000 +0000 @@ -28,12 +28,14 @@ --------- ========================= ============================================== -``RECAPTCHA_USE_SSL`` Enable/disable recaptcha through SSL. Default is - ``False``. ``RECAPTCHA_PUBLIC_KEY`` **required** A public key. ``RECAPTCHA_PRIVATE_KEY`` **required** A private key. - https://www.google.com/recaptcha/admin/create -``RECAPTCHA_OPTIONS`` **optional** A dict of configuration options. + https://www.google.com/recaptcha/admin +``RECAPTCHA_PARAMETERS`` **optional** A dict of configuration options. +``RECAPTCHA_HTML`` **optional** Override default HTML template + for Recaptcha. +``RECAPTCHA_DATA_ATTRS`` **optional** A dict of ``data-`` attrs to use + for Recaptcha div ========================= ============================================== Logging diff -Nru flask-wtf-0.14.2/docs/conf.py flask-wtf-0.14.3/docs/conf.py --- flask-wtf-0.14.2/docs/conf.py 2017-01-05 23:18:40.000000000 +0000 +++ flask-wtf-0.14.3/docs/conf.py 2020-02-06 23:03:33.000000000 +0000 @@ -114,7 +114,7 @@ # a list of builtin themes. try: - __import__('flask_theme_support') + __import__('flask_sphinx_themes') html_theme = 'flask' html_theme_options = { 'index_logo': 'flask-wtf.png', @@ -122,12 +122,12 @@ } except ImportError: print('-' * 72) - print('Flask theme not found. Run "git submodule update --init" to get it.') + print('Flask theme not found. Run "pip install flask-sphinx-themes" to get it.') print('-' * 72) html_theme = 'default' # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_themes'] +# html_theme_path = ['_themes'] # The name for this set of Sphinx documents. # " v documentation" by default. @@ -166,10 +166,10 @@ # # html_last_updated_fmt = None -# If true, SmartyPants will be used to convert quotes and dashes to +# If true, Smart Quotes will be used to convert quotes and dashes to # typographically correct entities. # -html_use_smartypants = False +smartquotes = False # Custom sidebar templates, maps document names to template names. # diff -Nru flask-wtf-0.14.2/docs/csrf.rst flask-wtf-0.14.3/docs/csrf.rst --- flask-wtf-0.14.2/docs/csrf.rst 2017-01-09 20:49:01.000000000 +0000 +++ flask-wtf-0.14.3/docs/csrf.rst 2020-02-06 23:15:29.000000000 +0000 @@ -1,4 +1,4 @@ -.. module:: flask_wtf.csrf +.. currentmodule:: flask_wtf.csrf .. _csrf: diff -Nru flask-wtf-0.14.2/docs/form.rst flask-wtf-0.14.3/docs/form.rst --- flask-wtf-0.14.2/docs/form.rst 2017-01-10 10:05:00.000000000 +0000 +++ flask-wtf-0.14.3/docs/form.rst 2020-02-06 23:17:06.000000000 +0000 @@ -4,14 +4,14 @@ Secure Form ----------- -.. module:: flask_wtf +.. currentmodule:: flask_wtf Without any configuration, the :class:`FlaskForm` will be a session secure form with csrf protection. We encourage you do nothing. But if you want to disable the csrf protection, you can pass:: - form = FlaskForm(csrf_enabled=False) + form = FlaskForm(meta={'csrf': False}) You can disable it globally—though you really shouldn't—with the configuration:: @@ -28,7 +28,7 @@ File Uploads ------------ -.. module:: flask_wtf.file +.. currentmodule:: flask_wtf.file The :class:`FileField` provided by Flask-WTF differs from the WTForms-provided field. It will check that the file is a non-empty instance of @@ -44,6 +44,8 @@ @app.route('/upload', methods=['GET', 'POST']) def upload(): + form = PhotoForm() + if form.validate_on_submit(): f = form.photo.data filename = secure_filename(f.filename) @@ -110,7 +112,7 @@ Recaptcha --------- -.. module:: flask_wtf.recaptcha +.. currentmodule:: flask_wtf.recaptcha Flask-WTF also provides Recaptcha support through a :class:`RecaptchaField`:: diff -Nru flask-wtf-0.14.2/docs/index.rst flask-wtf-0.14.3/docs/index.rst --- flask-wtf-0.14.2/docs/index.rst 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/docs/index.rst 2020-02-06 23:03:33.000000000 +0000 @@ -4,9 +4,8 @@ Simple integration of `Flask`_ and `WTForms`_, including CSRF, file upload, and reCAPTCHA. -.. _Flask: https://palletsprojects.com/p/flask -.. _WTForms: https://wtforms.readthedocs.io/ - +.. _Flask: https://www.palletsprojects.com/p/flask +.. _WTForms: https://wtforms.readthedocs.io/en/latest/ Features -------- diff -Nru flask-wtf-0.14.2/docs/install.rst flask-wtf-0.14.3/docs/install.rst --- flask-wtf-0.14.2/docs/install.rst 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/docs/install.rst 2020-02-06 23:03:33.000000000 +0000 @@ -1,44 +1,30 @@ Installation ============ -This part of the documentation covers the installation of Flask-WTF. -The first step to using any software package is getting it properly installed. +The `Python Packaging Guide`_ contains general information about how to manage +your project and dependencies. +.. _Python Packaging Guide: https://packaging.python.org/current/ -Distribute & Pip +Released version ---------------- -Installing Flask-WTF is simple with `pip `_:: +Install or upgrade using pip. :: - $ pip install Flask-WTF + pip install -U Flask-WTF -or, with `easy_install `_:: +Development +----------- - $ easy_install Flask-WTF +The latest code is available from `GitHub`_. Clone the repository then install +using pip. :: -But, you really `shouldn't do that `_. + git clone https://github.com/lepture/flask-wtf + pip install -e ./flask-wtf +Or install the latest build from an `archive`_. :: -Get the Code ------------- + pip install -U https://github.com/lepture/flask-wtf/tarball/master -Flask-WTF is actively developed on GitHub, where the code is -`always available `_. - -You can either clone the public repository:: - - git clone git://github.com/lepture/flask-wtf.git - -Download the `tarball `_:: - - $ curl -OL https://github.com/lepture/flask-wtf/tarball/master - -Or, download the `zipball `_:: - - $ curl -OL https://github.com/lepture/flask-wtf/zipball/master - - -Once you have a copy of the source, you can embed it in your Python package, -or install it into your site-packages easily:: - - $ python setup.py install +.. _GitHub: https://github.com/lepture/flask-wtf +.. _archive: https://github.com/lepture/flask-wtf/archive/master.tar.gz diff -Nru flask-wtf-0.14.2/docs/quickstart.rst flask-wtf-0.14.3/docs/quickstart.rst --- flask-wtf-0.14.2/docs/quickstart.rst 2016-07-03 15:14:49.000000000 +0000 +++ flask-wtf-0.14.3/docs/quickstart.rst 2020-02-06 23:03:33.000000000 +0000 @@ -52,7 +52,7 @@ Validating the request in your view handlers:: - @app.route('/submit', methods=('GET', 'POST')) + @app.route('/submit', methods=['GET', 'POST']) def submit(): form = MyForm() if form.validate_on_submit(): diff -Nru flask-wtf-0.14.2/docs/_themes/flask/layout.html flask-wtf-0.14.3/docs/_themes/flask/layout.html --- flask-wtf-0.14.2/docs/_themes/flask/layout.html 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask/layout.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -{% extends 'basic/layout.html' %} - -{% block extrahead %} - {{ super() }} - {% if theme_touch_icon %} - - {% endif %} - -{% endblock %} - -{% block relbar2 %} - {% if theme_github_fork %} - - Fork me on GitHub - - {% endif %} -{% endblock %} - -{% block header %} - {{ super() }} - {% if pagename == 'index' %}
{% endif %} -{% endblock %} - -{% block footer %} - {{ super() }} - {% if pagename == 'index' %}
{% endif %} -{% endblock %} diff -Nru flask-wtf-0.14.2/docs/_themes/flask/relations.html flask-wtf-0.14.3/docs/_themes/flask/relations.html --- flask-wtf-0.14.2/docs/_themes/flask/relations.html 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask/relations.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -

Related Topics

- diff -Nru flask-wtf-0.14.2/docs/_themes/flask/static/flasky.css_t flask-wtf-0.14.3/docs/_themes/flask/static/flasky.css_t --- flask-wtf-0.14.2/docs/_themes/flask/static/flasky.css_t 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask/static/flasky.css_t 1970-01-01 00:00:00.000000000 +0000 @@ -1,577 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -{% set page_width = '940px' %} -{% set sidebar_width = '220px' %} - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - width: {{ page_width }}; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ sidebar_width }}; -} - -div.sphinxsidebar { - width: {{ sidebar_width }}; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - width: {{ page_width }}; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -div.related { - display: none; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebar { - font-size: 14px; - line-height: 1.5; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0 0 20px 0; - margin: 0; - text-align: center; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: 'Garamond', 'Georgia', serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: 'Georgia', serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #ddd; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; - background: #fdfdfd; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #eee; - padding: 7px 30px; - margin: 15px -30px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: white; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: white; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: white; - } - - div.sphinxsidebar a { - color: #aaa; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.related { - display: block; - margin: 0; - padding: 10px 0 20px 0; - } - - div.related ul, - div.related ul li { - margin: 0; - padding: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - - -/* scrollbars */ - -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-button:start:decrement, -::-webkit-scrollbar-button:end:increment { - display: block; - height: 10px; -} - -::-webkit-scrollbar-button:vertical:increment { - background-color: #fff; -} - -::-webkit-scrollbar-track-piece { - background-color: #eee; - -webkit-border-radius: 3px; -} - -::-webkit-scrollbar-thumb:vertical { - height: 50px; - background-color: #ccc; - -webkit-border-radius: 3px; -} - -::-webkit-scrollbar-thumb:horizontal { - width: 50px; - background-color: #ccc; - -webkit-border-radius: 3px; -} - -/* misc. */ - -.revsys-inline { - display: none!important; -} \ No newline at end of file diff -Nru flask-wtf-0.14.2/docs/_themes/flask/theme.conf flask-wtf-0.14.3/docs/_themes/flask/theme.conf --- flask-wtf-0.14.2/docs/_themes/flask/theme.conf 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask/theme.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = -index_logo_height = 120px -touch_icon = -github_fork = -github_ribbon_color = darkblue_121621 diff -Nru flask-wtf-0.14.2/docs/_themes/flask_small/layout.html flask-wtf-0.14.3/docs/_themes/flask_small/layout.html --- flask-wtf-0.14.2/docs/_themes/flask_small/layout.html 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask_small/layout.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -{% extends 'basic/layout.html' %} - -{% block extrahead %} - {{ super() }} - {% if theme_touch_icon %} - - {% endif %} - -{% endblock %} - -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} - -{% block footer %} - {% if pagename == 'index' %}
{% endif %} -{% endblock %} - -{# do not display relbars or sidebars #} - -{% block relbar1 %}{% endblock %} - -{% block relbar2 %} - {% if theme_github_fork %} - - Fork me on GitHub - - {% endif %} -{% endblock %} - -{% block sidebar1 %}{% endblock %} - -{% block sidebar2 %}{% endblock %} diff -Nru flask-wtf-0.14.2/docs/_themes/flask_small/static/flasky.css_t flask-wtf-0.14.3/docs/_themes/flask_small/static/flasky.css_t --- flask-wtf-0.14.2/docs/_themes/flask_small/static/flasky.css_t 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask_small/static/flasky.css_t 1970-01-01 00:00:00.000000000 +0000 @@ -1,287 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- flasky theme based on nature theme. - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - color: #000; - background: white; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 40px auto 0 auto; - width: 700px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - text-align: right; - color: #888; - padding: 10px; - font-size: 14px; - width: 650px; - margin: 0 auto 40px auto; -} - -div.footer a { - color: #888; - text-decoration: underline; -} - -div.related { - line-height: 32px; - color: #888; -} - -div.related ul { - padding: 0 0 0 10px; -} - -div.related a { - color: #444; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body { - padding-bottom: 40px; /* saved for footer */ -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: white; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight{ - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.85em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td { - padding: 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -pre { - padding: 0; - margin: 15px -30px; - padding: 8px; - line-height: 1.3em; - padding: 7px 30px; - background: #eee; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -dl pre { - margin-left: -60px; - padding-left: 60px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; -} - -a:hover tt { - background: #EEE; -} diff -Nru flask-wtf-0.14.2/docs/_themes/flask_small/theme.conf flask-wtf-0.14.3/docs/_themes/flask_small/theme.conf --- flask-wtf-0.14.2/docs/_themes/flask_small/theme.conf 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask_small/theme.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -nosidebar = true -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = -index_logo_height = 120px -touch_icon = -github_fork = -github_ribbon_color = darkblue_121621 diff -Nru flask-wtf-0.14.2/docs/_themes/flask_theme_support.py flask-wtf-0.14.3/docs/_themes/flask_theme_support.py --- flask-wtf-0.14.2/docs/_themes/flask_theme_support.py 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/flask_theme_support.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ -# flasky extensions. flasky pygments style based on tango style -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal - - -class FlaskyStyle(Style): - background_color = "#f8f8f8" - default_style = "" - - styles = { - # No corresponding class for the following: - #Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - - Punctuation: "bold #000000", # class: 'p' - - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - - Number: "#990000", # class: 'm' - - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff -Nru flask-wtf-0.14.2/docs/_themes/LICENSE flask-wtf-0.14.3/docs/_themes/LICENSE --- flask-wtf-0.14.2/docs/_themes/LICENSE 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff -Nru flask-wtf-0.14.2/docs/_themes/README.rst flask-wtf-0.14.3/docs/_themes/README.rst --- flask-wtf-0.14.2/docs/_themes/README.rst 2016-07-03 14:13:12.000000000 +0000 +++ flask-wtf-0.14.3/docs/_themes/README.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -Flask Sphinx Themes -=================== - -This repository contains Sphinx themes for Flask and Flask related -projects. To use this theme in your Sphinx documentation: - -1. Put this folder as ``_themes`` in the docs folder. Alternatively - you can use git submodules to check out the contents there. - -2. Add this to ``conf.py``: - - .. code-block:: python - - sys.path.append(os.path.join(os.path.dirname(__file__), '_themes')) - html_theme_path = ['_themes'] - html_theme = 'flask' - -Themes ------- - -The following themes exist for ``html_theme``. - -======================= =============================================== -flask The standard Flask documentation theme for - large projects - -flask_small Small single page theme. Intended to be used - by very small addon libraries for Flask. -======================= =============================================== - -Options -------- - -The following options can be set with ``html_theme_options``. - -======================= =============================================== -index_logo Filename of a picture in ``_static`` to be used - as replacement for the ``h1`` in the - ``index.rst`` file. - *Default unset.* - -index_logo_height Height of the index logo. - *Default 120px*. - -touch_icon Filename of a picture in ``_static`` to be use - as the app icon on Apple devices. - *Default unset.* - -github_fork Repository name on GitHub for the "Fork Me" - badge. - *Default unset.* - -github_ribbon_color Color for the "Fork Me" badge. - *Default darkblue_121621.* -======================= =============================================== - -Sidebar Templates ------------------ - -The following sidebar templates can be included in ``html_sidebars``. - -======================= =============================================== -relations.html Show parent, previous, and next links. -======================= =============================================== - -Pygments Style --------------- - -The theme automatically sets ``pygments_style`` to the provided style. -Make sure you remove any override from ``conf.py`` or set it to -``flask_theme_support.FlaskyStyle``. diff -Nru flask-wtf-0.14.2/flask_wtf/_compat.py flask-wtf-0.14.3/flask_wtf/_compat.py --- flask-wtf-0.14.2/flask_wtf/_compat.py 2017-01-06 03:36:43.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/_compat.py 2020-02-06 23:06:34.000000000 +0000 @@ -6,10 +6,12 @@ if not PY2: text_type = str string_types = (str,) + from collections import abc from urllib.parse import urlparse else: text_type = unicode string_types = (str, unicode) + import collections as abc from urlparse import urlparse @@ -32,4 +34,6 @@ warnings.simplefilter('always', FlaskWTFDeprecationWarning) -warnings.filterwarnings('ignore', category=FlaskWTFDeprecationWarning, module='wtforms|flask_wtf') +warnings.filterwarnings( + 'ignore', category=FlaskWTFDeprecationWarning, module='wtforms|flask_wtf' +) diff -Nru flask-wtf-0.14.2/flask_wtf/csrf.py flask-wtf-0.14.3/flask_wtf/csrf.py --- flask-wtf-0.14.2/flask_wtf/csrf.py 2017-01-10 09:20:28.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/csrf.py 2020-02-06 23:03:33.000000000 +0000 @@ -40,11 +40,18 @@ ) if field_name not in g: + s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token') + if field_name not in session: session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest() - s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token') - setattr(g, field_name, s.dumps(session[field_name])) + try: + token = s.dumps(session[field_name]) + except TypeError: + session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest() + token = s.dumps(session[field_name]) + + setattr(g, field_name, token) return g.get(field_name) @@ -118,7 +125,7 @@ value = current_app.config.get(config_name, default) if required and value is None: - raise KeyError(message) + raise RuntimeError(message) return value @@ -157,7 +164,7 @@ :: app = Flask(__name__) - csrf = CsrfProtect(app) + csrf = CSRFProtect(app) Checks the ``csrf_token`` field sent with forms, or the ``X-CSRFToken`` header sent with JavaScript requests. Render the token in templates using @@ -205,15 +212,11 @@ if not request.endpoint: return - view = app.view_functions.get(request.endpoint) - - if not view: - return - if request.blueprint in self._exempt_blueprints: return - dest = '%s.%s' % (view.__module__, view.__name__) + view = app.view_functions.get(request.endpoint) + dest = '{0}.{1}'.format(view.__module__, view.__name__) if dest in self._exempt_views: return @@ -221,11 +224,14 @@ self.protect() def _get_csrf_token(self): - # find the ``csrf_token`` field in the subitted form - # if the form had a prefix, the name will be - # ``{prefix}-csrf_token`` + # find the token in the form data field_name = current_app.config['WTF_CSRF_FIELD_NAME'] + base_token = request.form.get(field_name) + + if base_token: + return base_token + # if the form has a prefix, the name will be {prefix}-csrf_token for key in request.form: if key.endswith(field_name): csrf_token = request.form[key] @@ -233,6 +239,7 @@ if csrf_token: return csrf_token + # find the token in the headers for header_name in current_app.config['WTF_CSRF_HEADERS']: csrf_token = request.headers.get(header_name) @@ -286,7 +293,7 @@ if isinstance(view, string_types): view_location = view else: - view_location = '%s.%s' % (view.__module__, view.__name__) + view_location = '.'.join((view.__module__, view.__name__)) self._exempt_views.add(view_location) return view @@ -302,8 +309,8 @@ ``@app.errorhandler(CSRFError)`` instead. This will be removed in version 1.0. - The function will be passed one argument, ``reason``. By default it will - raise a :class:`~flask_wtf.csrf.CSRFError`. :: + The function will be passed one argument, ``reason``. By default it + will raise a :class:`~flask_wtf.csrf.CSRFError`. :: @csrf.error_handler def csrf_error(reason): @@ -314,15 +321,15 @@ """ warnings.warn(FlaskWTFDeprecationWarning( - '"@csrf.error_handler" is deprecated. Use the standard Flask error ' - 'system with "@app.errorhandler(CSRFError)" instead. This will be' - 'removed in 1.0.' + '"@csrf.error_handler" is deprecated. Use the standard Flask ' + 'error system with "@app.errorhandler(CSRFError)" instead. This ' + 'will be removed in 1.0.' ), stacklevel=2) @wraps(view) def handler(reason): response = current_app.make_response(view(reason)) - raise CSRFError(response.get_data(as_text=True), response=response) + raise CSRFError(response=response) self._error_response = handler return view diff -Nru flask-wtf-0.14.2/flask_wtf/file.py flask-wtf-0.14.3/flask_wtf/file.py --- flask-wtf-0.14.2/flask_wtf/file.py 2017-01-10 10:02:33.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/file.py 2020-02-06 23:06:34.000000000 +0000 @@ -1,10 +1,10 @@ import warnings -from collections import Iterable from werkzeug.datastructures import FileStorage from wtforms import FileField as _FileField from wtforms.validators import DataRequired, StopValidation +from ._compat import abc from ._compat import FlaskWTFDeprecationWarning @@ -47,12 +47,10 @@ def __call__(self, form, field): if not (isinstance(field.data, FileStorage) and field.data): - if self.message is None: - message = field.gettext('This field is required.') - else: - message = self.message + raise StopValidation(self.message or field.gettext( + 'This field is required.' + )) - raise StopValidation(message) file_required = FileRequired @@ -78,7 +76,7 @@ filename = field.data.filename.lower() - if isinstance(self.upload_set, Iterable): + if isinstance(self.upload_set, abc.Iterable): if any(filename.endswith('.' + x) for x in self.upload_set): return @@ -91,4 +89,5 @@ 'File does not have an approved extension.' )) + file_allowed = FileAllowed diff -Nru flask-wtf-0.14.2/flask_wtf/form.py flask-wtf-0.14.3/flask_wtf/form.py --- flask-wtf-0.14.2/flask_wtf/form.py 2017-01-10 09:38:38.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/form.py 2020-02-06 23:03:33.000000000 +0000 @@ -1,7 +1,6 @@ import warnings -from flask import current_app, request -from flask import session +from flask import current_app, request, session from jinja2 import Markup from werkzeug.datastructures import CombinedMultiDict, ImmutableMultiDict from werkzeug.utils import cached_property @@ -70,7 +69,7 @@ def get_translations(self, form): if not current_app.config.get('WTF_I18N_ENABLED', True): - return None + return super(FlaskForm.Meta, self).get_translations(form) return translations @@ -80,7 +79,7 @@ if csrf_enabled is not None: warnings.warn(FlaskWTFDeprecationWarning( '"csrf_enabled" is deprecated and will be removed in 1.0. ' - 'Set "meta.csrf" instead.' + "Pass meta={'csrf': False} instead." ), stacklevel=3) kwargs['meta'] = kwargs.get('meta') or {} kwargs['meta'].setdefault('csrf', csrf_enabled) diff -Nru flask-wtf-0.14.2/flask_wtf/html5.py flask-wtf-0.14.3/flask_wtf/html5.py --- flask-wtf-0.14.2/flask_wtf/html5.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/html5.py 2020-02-06 23:03:33.000000000 +0000 @@ -1,7 +1,6 @@ -# coding: utf-8 -# flake8: noqa import warnings -from flask_wtf._compat import FlaskWTFDeprecationWarning + +from ._compat import FlaskWTFDeprecationWarning warnings.warn(FlaskWTFDeprecationWarning( '"flask_wtf.html5" will be removed in 1.0. ' diff -Nru flask-wtf-0.14.2/flask_wtf/i18n.py flask-wtf-0.14.3/flask_wtf/i18n.py --- flask-wtf-0.14.2/flask_wtf/i18n.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/i18n.py 2020-02-06 23:03:33.000000000 +0000 @@ -1,24 +1,11 @@ -# coding: utf-8 -""" - flask_wtf.i18n - ~~~~~~~~~~~~~~ - - Internationalization support for Flask WTF. - - :copyright: (c) 2013 by Hsiaoming Yang. -""" - -from flask import _request_ctx_stack from babel import support +from flask import current_app, request +from wtforms.i18n import messages_path + try: from flask_babel import get_locale except ImportError: from flask_babelex import get_locale -try: - from wtforms.i18n import messages_path -except ImportError: - from wtforms.ext.i18n.utils import messages_path - __all__ = ('Translations', 'translations') @@ -27,43 +14,37 @@ """Returns the correct gettext translations. Copy from flask-babel with some modifications. """ - ctx = _request_ctx_stack.top - if ctx is None: + + if not request: return None + # babel should be in extensions for get_locale - if 'babel' not in ctx.app.extensions: + if 'babel' not in current_app.extensions: return None - translations = getattr(ctx, 'wtforms_translations', None) + + translations = getattr(request, 'wtforms_translations', None) + if translations is None: - dirname = messages_path() translations = support.Translations.load( - dirname, [get_locale()], domain='wtforms' + messages_path(), [get_locale()], domain='wtforms' ) - ctx.wtforms_translations = translations + request.wtforms_translations = translations + return translations class Translations(object): def gettext(self, string): t = _get_translations() - if t is None: - return string - if hasattr(t, 'ugettext'): - return t.ugettext(string) - # Python 3 has no ugettext - return t.gettext(string) + return string if t is None else t.ugettext(string) def ngettext(self, singular, plural, n): t = _get_translations() + if t is None: - if n == 1: - return singular - return plural - - if hasattr(t, 'ungettext'): - return t.ungettext(singular, plural, n) - # Python 3 has no ungettext - return t.ngettext(singular, plural, n) + return singular if n == 1 else plural + + return t.ungettext(singular, plural, n) translations = Translations() diff -Nru flask-wtf-0.14.2/flask_wtf/__init__.py flask-wtf-0.14.3/flask_wtf/__init__.py --- flask-wtf-0.14.2/flask_wtf/__init__.py 2017-01-10 09:38:38.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/__init__.py 2020-02-06 23:08:04.000000000 +0000 @@ -1,19 +1,7 @@ -# -*- coding: utf-8 -*- -""" - flask_wtf - ~~~~~~~~~ - - Flask-WTF extension - - :copyright: (c) 2010 by Dan Jacob. - :copyright: (c) 2013 - 2015 by Hsiaoming Yang. - :license: BSD, see LICENSE for more details. -""" -# flake8: noqa from __future__ import absolute_import from .csrf import CSRFProtect, CsrfProtect from .form import FlaskForm, Form from .recaptcha import * -__version__ = '0.14.2' +__version__ = '0.14.3' diff -Nru flask-wtf-0.14.2/flask_wtf/recaptcha/validators.py flask-wtf-0.14.3/flask_wtf/recaptcha/validators.py --- flask-wtf-0.14.2/flask_wtf/recaptcha/validators.py 2016-07-07 01:40:40.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/recaptcha/validators.py 2020-02-06 23:03:33.000000000 +0000 @@ -4,11 +4,13 @@ # Python 3 from urllib import request as http -from flask import request, current_app +import json + +from flask import current_app, request +from werkzeug.urls import url_encode from wtforms import ValidationError -from werkzeug import url_encode + from .._compat import to_bytes, to_unicode -import json RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify' RECAPTCHA_ERROR_CODES = { diff -Nru flask-wtf-0.14.2/flask_wtf/recaptcha/widgets.py flask-wtf-0.14.3/flask_wtf/recaptcha/widgets.py --- flask-wtf-0.14.2/flask_wtf/recaptcha/widgets.py 2016-07-07 01:40:40.000000000 +0000 +++ flask-wtf-0.14.3/flask_wtf/recaptcha/widgets.py 2020-02-06 23:03:33.000000000 +0000 @@ -1,18 +1,17 @@ # -*- coding: utf-8 -*- -from flask import current_app, Markup -from flask import json -from werkzeug import url_encode +from flask import Markup, current_app, json +from werkzeug.urls import url_encode + JSONEncoder = json.JSONEncoder RECAPTCHA_SCRIPT = u'https://www.google.com/recaptcha/api.js' - RECAPTCHA_TEMPLATE = u'''
''' -__all__ = ["RecaptchaWidget"] +__all__ = ['RecaptchaWidget'] class RecaptchaWidget(object): @@ -37,6 +36,6 @@ try: public_key = current_app.config['RECAPTCHA_PUBLIC_KEY'] except KeyError: - raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set") + raise RuntimeError('RECAPTCHA_PUBLIC_KEY config not set') return self.recaptcha_html(public_key) diff -Nru flask-wtf-0.14.2/Flask_WTF.egg-info/PKG-INFO flask-wtf-0.14.3/Flask_WTF.egg-info/PKG-INFO --- flask-wtf-0.14.2/Flask_WTF.egg-info/PKG-INFO 2017-01-10 10:06:24.000000000 +0000 +++ flask-wtf-0.14.3/Flask_WTF.egg-info/PKG-INFO 2020-02-06 23:18:02.000000000 +0000 @@ -1,23 +1,24 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: Flask-WTF -Version: 0.14.2 +Version: 0.14.3 Summary: Simple integration of Flask and WTForms. Home-page: https://github.com/lepture/flask-wtf -Author: Hsiaoming Yang -Author-email: me@lepture.com +Author: Dan Jacob +Author-email: danjac354@gmail.com +Maintainer: Hsiaoming Yang +Maintainer-email: me@lepture.com License: BSD Description: Flask-WTF ========= .. image:: https://travis-ci.org/lepture/flask-wtf.svg?branch=master - :target: https://travis-ci.org/lepture/flask-wtf - :alt: Travis CI Status - .. image:: https://coveralls.io/repos/lepture/flask-wtf/badge.svg?branch=master - :target: https://coveralls.io/r/lepture/flask-wtf - :alt: Coverage Status + :target: https://travis-ci.org/lepture/flask-wtf + :alt: Test Status + .. image:: https://codecov.io/gh/lepture/flask-wtf/branch/master/graph/badge.svg + :target: https://codecov.io/gh/lepture/flask-wtf + :alt: Coverage Status - Simple integration of Flask and WTForms, including CSRF, file upload, - and reCAPTCHA. + Simple integration of Flask and WTForms, including CSRF, file upload, and reCAPTCHA. Links ----- @@ -35,14 +36,15 @@ Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules +Description-Content-Type: text/x-rst diff -Nru flask-wtf-0.14.2/Flask_WTF.egg-info/requires.txt flask-wtf-0.14.3/Flask_WTF.egg-info/requires.txt --- flask-wtf-0.14.2/Flask_WTF.egg-info/requires.txt 2017-01-10 10:06:24.000000000 +0000 +++ flask-wtf-0.14.3/Flask_WTF.egg-info/requires.txt 2020-02-06 23:18:02.000000000 +0000 @@ -1,2 +1,3 @@ Flask WTForms +itsdangerous diff -Nru flask-wtf-0.14.2/Flask_WTF.egg-info/SOURCES.txt flask-wtf-0.14.3/Flask_WTF.egg-info/SOURCES.txt --- flask-wtf-0.14.2/Flask_WTF.egg-info/SOURCES.txt 2017-01-10 10:06:24.000000000 +0000 +++ flask-wtf-0.14.3/Flask_WTF.egg-info/SOURCES.txt 2020-02-06 23:18:03.000000000 +0000 @@ -4,7 +4,6 @@ README.rst setup.cfg setup.py -test-requirements.txt tox.ini Flask_WTF.egg-info/PKG-INFO Flask_WTF.egg-info/SOURCES.txt @@ -29,16 +28,6 @@ docs/_static/flask-wtf.png docs/_templates/brand.html docs/_templates/useful-links.html -docs/_themes/LICENSE -docs/_themes/README.rst -docs/_themes/flask_theme_support.py -docs/_themes/flask/layout.html -docs/_themes/flask/relations.html -docs/_themes/flask/theme.conf -docs/_themes/flask/static/flasky.css_t -docs/_themes/flask_small/layout.html -docs/_themes/flask_small/theme.conf -docs/_themes/flask_small/static/flasky.css_t flask_wtf/__init__.py flask_wtf/_compat.py flask_wtf/csrf.py @@ -50,20 +39,11 @@ flask_wtf/recaptcha/fields.py flask_wtf/recaptcha/validators.py flask_wtf/recaptcha/widgets.py -tests/__init__.py -tests/base.py -tests/flask.png -tests/flask.txt -tests/test_csrf.py -tests/test_deprecated.py +tests/conftest.py +tests/test_csrf_extension.py +tests/test_csrf_form.py +tests/test_file.py +tests/test_form.py +tests/test_html5.py tests/test_i18n.py -tests/test_recaptcha.py -tests/test_uploads.py -tests/test_validation.py -tests/templates/csrf.html -tests/templates/csrf_macro.html -tests/templates/hidden.html -tests/templates/import_csrf.html -tests/templates/index.html -tests/templates/recaptcha.html -tests/templates/upload.html \ No newline at end of file +tests/test_recaptcha.py \ No newline at end of file diff -Nru flask-wtf-0.14.2/PKG-INFO flask-wtf-0.14.3/PKG-INFO --- flask-wtf-0.14.2/PKG-INFO 2017-01-10 10:06:24.000000000 +0000 +++ flask-wtf-0.14.3/PKG-INFO 2020-02-06 23:18:03.016854300 +0000 @@ -1,23 +1,24 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: Flask-WTF -Version: 0.14.2 +Version: 0.14.3 Summary: Simple integration of Flask and WTForms. Home-page: https://github.com/lepture/flask-wtf -Author: Hsiaoming Yang -Author-email: me@lepture.com +Author: Dan Jacob +Author-email: danjac354@gmail.com +Maintainer: Hsiaoming Yang +Maintainer-email: me@lepture.com License: BSD Description: Flask-WTF ========= .. image:: https://travis-ci.org/lepture/flask-wtf.svg?branch=master - :target: https://travis-ci.org/lepture/flask-wtf - :alt: Travis CI Status - .. image:: https://coveralls.io/repos/lepture/flask-wtf/badge.svg?branch=master - :target: https://coveralls.io/r/lepture/flask-wtf - :alt: Coverage Status + :target: https://travis-ci.org/lepture/flask-wtf + :alt: Test Status + .. image:: https://codecov.io/gh/lepture/flask-wtf/branch/master/graph/badge.svg + :target: https://codecov.io/gh/lepture/flask-wtf + :alt: Coverage Status - Simple integration of Flask and WTForms, including CSRF, file upload, - and reCAPTCHA. + Simple integration of Flask and WTForms, including CSRF, file upload, and reCAPTCHA. Links ----- @@ -35,14 +36,15 @@ Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules +Description-Content-Type: text/x-rst diff -Nru flask-wtf-0.14.2/README.rst flask-wtf-0.14.3/README.rst --- flask-wtf-0.14.2/README.rst 2016-09-29 13:51:06.000000000 +0000 +++ flask-wtf-0.14.3/README.rst 2020-02-06 23:03:33.000000000 +0000 @@ -2,14 +2,13 @@ ========= .. image:: https://travis-ci.org/lepture/flask-wtf.svg?branch=master - :target: https://travis-ci.org/lepture/flask-wtf - :alt: Travis CI Status -.. image:: https://coveralls.io/repos/lepture/flask-wtf/badge.svg?branch=master - :target: https://coveralls.io/r/lepture/flask-wtf - :alt: Coverage Status + :target: https://travis-ci.org/lepture/flask-wtf + :alt: Test Status +.. image:: https://codecov.io/gh/lepture/flask-wtf/branch/master/graph/badge.svg + :target: https://codecov.io/gh/lepture/flask-wtf + :alt: Coverage Status -Simple integration of Flask and WTForms, including CSRF, file upload, -and reCAPTCHA. +Simple integration of Flask and WTForms, including CSRF, file upload, and reCAPTCHA. Links ----- diff -Nru flask-wtf-0.14.2/setup.cfg flask-wtf-0.14.3/setup.cfg --- flask-wtf-0.14.2/setup.cfg 2017-01-10 10:06:24.000000000 +0000 +++ flask-wtf-0.14.3/setup.cfg 2020-02-06 23:18:03.016854300 +0000 @@ -1,14 +1,15 @@ -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - -[aliases] -release = egg_info -RDb '' - [bdist_wheel] universal = 1 [metadata] license_file = LICENSE +long_description_content_type = text/x-rst + +[tool:pytest] +minversion = 3.0 +testpaths = tests + +[egg_info] +tag_build = +tag_date = 0 diff -Nru flask-wtf-0.14.2/setup.py flask-wtf-0.14.3/setup.py --- flask-wtf-0.14.2/setup.py 2017-01-10 09:38:38.000000000 +0000 +++ flask-wtf-0.14.3/setup.py 2020-02-06 23:13:19.000000000 +0000 @@ -1,12 +1,12 @@ #!/usr/bin/env python -from setuptools import setup, find_packages +from setuptools import find_packages, setup with open('README.rst') as f: readme = f.read() setup( name='Flask-WTF', - version='0.14.2', + version='0.14.3', url='https://github.com/lepture/flask-wtf', license='BSD', author='Dan Jacob', @@ -21,6 +21,7 @@ install_requires=[ 'Flask', 'WTForms', + 'itsdangerous', ], classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -31,13 +32,13 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', diff -Nru flask-wtf-0.14.2/test-requirements.txt flask-wtf-0.14.3/test-requirements.txt --- flask-wtf-0.14.2/test-requirements.txt 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/test-requirements.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ --e . -flask-babel -flask-uploads -nose -coverage diff -Nru flask-wtf-0.14.2/tests/base.py flask-wtf-0.14.3/tests/base.py --- flask-wtf-0.14.2/tests/base.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/base.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -from __future__ import with_statement - -from contextlib import contextmanager -from unittest import TestCase as _TestCase - -import logging -from flask import Flask, jsonify, render_template -from wtforms import HiddenField, StringField, SubmitField -from wtforms.validators import DataRequired - -from flask_wtf import FlaskForm -from flask_wtf._compat import text_type - - -def to_unicode(text): - if not isinstance(text, text_type): - return text.decode('utf-8') - return text - - -class MyForm(FlaskForm): - SECRET_KEY = "a poorly kept secret." - name = StringField("Name", validators=[DataRequired()]) - submit = SubmitField("Submit") - - -class HiddenFieldsForm(FlaskForm): - SECRET_KEY = "a poorly kept secret." - name = HiddenField() - url = HiddenField() - method = HiddenField() - secret = HiddenField() - submit = SubmitField("Submit") - - def __init__(self, *args, **kwargs): - super(HiddenFieldsForm, self).__init__(*args, **kwargs) - self.method.name = '_method' - - -class SimpleForm(FlaskForm): - SECRET_KEY = "a poorly kept secret." - pass - - -class CaptureHandler(logging.Handler): - def __init__(self): - self.records = [] - logging.Handler.__init__(self, logging.DEBUG) - - def emit(self, record): - self.records.append(record) - - def __iter__(self): - return iter(self.records) - - def __len__(self): - return len(self.records) - - def __getitem__(self, item): - return self.records[item] - - -@contextmanager -def capture_logging(logger): - handler = CaptureHandler() - - try: - logger.addHandler(handler) - yield handler - finally: - logger.removeHandler(handler) - - -class TestCase(_TestCase): - def setUp(self): - self.app = self.create_app() - self.client = self.app.test_client() - - def create_app(self): - app = Flask(__name__) - app.secret_key = "secret" - - @app.route("/", methods=("GET", "POST")) - def index(): - - form = MyForm() - if form.validate_on_submit(): - name = form.name.data.upper() - else: - name = '' - - return render_template("index.html", - form=form, - name=name) - - @app.route("/simple/", methods=("POST",)) - def simple(): - form = SimpleForm() - form.validate() - assert form.meta.csrf - assert not form.validate() - return "OK" - - @app.route("/two_forms/", methods=("POST",)) - def two_forms(): - form = SimpleForm() - assert form.meta.csrf - assert form.validate() - assert form.validate() - form2 = SimpleForm() - assert form2.meta.csrf - assert form2.validate() - return "OK" - - @app.route("/hidden/") - def hidden(): - - form = HiddenFieldsForm() - return render_template("hidden.html", form=form) - - @app.route("/ajax/", methods=("POST",)) - def ajax_submit(): - form = MyForm() - if form.validate_on_submit(): - return jsonify(name=form.name.data, - success=True, - errors=None) - - return jsonify(name=None, - #errors=form.errors, - success=False) - - return app diff -Nru flask-wtf-0.14.2/tests/conftest.py flask-wtf-0.14.3/tests/conftest.py --- flask-wtf-0.14.2/tests/conftest.py 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/tests/conftest.py 2020-02-06 23:03:33.000000000 +0000 @@ -0,0 +1,36 @@ +import pytest +from flask import Flask as _Flask + + +class Flask(_Flask): + testing = True + secret_key = __name__ + + def make_response(self, rv): + if rv is None: + rv = '' + + return super(Flask, self).make_response(rv) + + +@pytest.fixture +def app(): + app = Flask(__name__) + return app + + +@pytest.yield_fixture +def app_ctx(app): + with app.app_context() as ctx: + yield ctx + + +@pytest.yield_fixture +def req_ctx(app): + with app.test_request_context() as ctx: + yield ctx + + +@pytest.fixture +def client(app): + return app.test_client() Binary files /tmp/tmpu6ejrk/0OAQ658Cfz/flask-wtf-0.14.2/tests/flask.png and /tmp/tmpu6ejrk/ee1KaLFujE/flask-wtf-0.14.3/tests/flask.png differ diff -Nru flask-wtf-0.14.2/tests/flask.txt flask-wtf-0.14.3/tests/flask.txt --- flask-wtf-0.14.2/tests/flask.txt 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/flask.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Hello Flask. diff -Nru flask-wtf-0.14.2/tests/templates/csrf.html flask-wtf-0.14.3/tests/templates/csrf.html --- flask-wtf-0.14.2/tests/templates/csrf.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/csrf.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ - - - - - token: {{ csrf_token() }} - - diff -Nru flask-wtf-0.14.2/tests/templates/csrf_macro.html flask-wtf-0.14.3/tests/templates/csrf_macro.html --- flask-wtf-0.14.2/tests/templates/csrf_macro.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/csrf_macro.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -{% macro render_csrf_token() %} - -{% endmacro %} diff -Nru flask-wtf-0.14.2/tests/templates/hidden.html flask-wtf-0.14.3/tests/templates/hidden.html --- flask-wtf-0.14.2/tests/templates/hidden.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/hidden.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ - - - - - - {% if name %} -

{{ name }}

- {% endif %} - - {{ form.errors }} -
- {{ form.hidden_tag() }} -

- {{ form.submit }} -

-
- - diff -Nru flask-wtf-0.14.2/tests/templates/import_csrf.html flask-wtf-0.14.3/tests/templates/import_csrf.html --- flask-wtf-0.14.2/tests/templates/import_csrf.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/import_csrf.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -{% import "csrf_macro.html" as h %} -{{ h.render_csrf_token() }} diff -Nru flask-wtf-0.14.2/tests/templates/index.html flask-wtf-0.14.3/tests/templates/index.html --- flask-wtf-0.14.2/tests/templates/index.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/index.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ - - - - - - {% if name %} -

{{ name }}

- {% endif %} - - {{ form.errors }} -
- {{ form.hidden_tag() }} -

- {{ form.name.label }} - {{ form.name }} -

-

- {{ form.submit }} -

-
- - diff -Nru flask-wtf-0.14.2/tests/templates/recaptcha.html flask-wtf-0.14.3/tests/templates/recaptcha.html --- flask-wtf-0.14.2/tests/templates/recaptcha.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/recaptcha.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ - - - - - {{ form.errors }} -
- {{ form.hidden_tag() }} -

- {{ form.recaptcha }} -

-

- {{ form.submit }} -

-
- - - diff -Nru flask-wtf-0.14.2/tests/templates/upload.html flask-wtf-0.14.3/tests/templates/upload.html --- flask-wtf-0.14.2/tests/templates/upload.html 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/templates/upload.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ - - - - - - {% if filedata %} -

{{ filedata.filename }}

- {% endif %} -
- {{ form.hidden_tag() }} -

- {{ form.upload.label }} - {{ form.upload }} -

-

- -

-
- - diff -Nru flask-wtf-0.14.2/tests/test_csrf_extension.py flask-wtf-0.14.3/tests/test_csrf_extension.py --- flask-wtf-0.14.2/tests/test_csrf_extension.py 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_csrf_extension.py 2020-02-06 23:03:33.000000000 +0000 @@ -0,0 +1,194 @@ +import pytest +from flask import Blueprint, abort, g, render_template_string, request + +from flask_wtf import FlaskForm +from flask_wtf._compat import FlaskWTFDeprecationWarning +from flask_wtf.csrf import CSRFError, CSRFProtect, CsrfProtect, generate_csrf + + +@pytest.fixture +def app(app): + CSRFProtect(app) + + @app.route('/', methods=['GET', 'POST']) + def index(): + pass + + @app.after_request + def add_csrf_header(response): + response.headers.set('X-CSRF-Token', generate_csrf()) + return response + + return app + + +@pytest.fixture +def csrf(app): + return app.extensions['csrf'] + + +def test_render_token(req_ctx): + token = generate_csrf() + assert render_template_string('{{ csrf_token() }}') == token + + +def test_protect(app, client, app_ctx): + response = client.post('/') + assert response.status_code == 400 + assert 'The CSRF token is missing.' in response.get_data(as_text=True) + + app.config['WTF_CSRF_ENABLED'] = False + assert client.post('/').get_data() == b'' + app.config['WTF_CSRF_ENABLED'] = True + + app.config['WTF_CSRF_CHECK_DEFAULT'] = False + assert client.post('/').get_data() == b'' + app.config['WTF_CSRF_CHECK_DEFAULT'] = True + + assert client.options('/').status_code == 200 + assert client.post('/not-found').status_code == 404 + + response = client.get('/') + assert response.status_code == 200 + token = response.headers['X-CSRF-Token'] + assert client.post('/', data={'csrf_token': token}).status_code == 200 + assert client.post( + '/', data={'prefix-csrf_token': token} + ).status_code == 200 + assert client.post('/', data={'prefix-csrf_token': ''}).status_code == 400 + assert client.post('/', headers={'X-CSRF-Token': token}).status_code == 200 + + +def test_same_origin(client): + token = client.get('/').headers['X-CSRF-Token'] + response = client.post('/', base_url='https://localhost', headers={ + 'X-CSRF-Token': token + }) + data = response.get_data(as_text=True) + assert 'The referrer header is missing.' in data + + response = client.post('/', base_url='https://localhost', headers={ + 'X-CSRF-Token': token, 'Referer': 'http://localhost/' + }) + data = response.get_data(as_text=True) + assert 'The referrer does not match the host.' in data + + response = client.post('/', base_url='https://localhost', headers={ + 'X-CSRF-Token': token, 'Referer': 'https://other/' + }) + data = response.get_data(as_text=True) + assert 'The referrer does not match the host.' in data + + response = client.post('/', base_url='https://localhost', headers={ + 'X-CSRF-Token': token, 'Referer': 'https://localhost:8080/' + }) + data = response.get_data(as_text=True) + assert 'The referrer does not match the host.' in data + + response = client.post('/', base_url='https://localhost', headers={ + 'X-CSRF-Token': token, 'Referer': 'https://localhost/' + }) + assert response.status_code == 200 + + +def test_form_csrf_short_circuit(app, client): + @app.route('/skip', methods=['POST']) + def skip(): + assert g.get('csrf_valid') + # don't pass the token, then validate the form + # this would fail if CSRFProtect didn't run + form = FlaskForm(None) + assert form.validate() + + token = client.get('/').headers['X-CSRF-Token'] + response = client.post('/skip', headers={'X-CSRF-Token': token}) + assert response.status_code == 200 + + +def test_exempt_view(app, csrf, client): + @app.route('/exempt', methods=['POST']) + @csrf.exempt + def exempt(): + pass + + response = client.post('/exempt') + assert response.status_code == 200 + + csrf.exempt('test_csrf_extension.index') + response = client.post('/') + assert response.status_code == 200 + + +def test_manual_protect(app, csrf, client): + @app.route('/manual', methods=['GET', 'POST']) + @csrf.exempt + def manual(): + csrf.protect() + + response = client.get('/manual') + assert response.status_code == 200 + + response = client.post('/manual') + assert response.status_code == 400 + + +def test_exempt_blueprint(app, csrf, client): + bp = Blueprint('exempt', __name__, url_prefix='/exempt') + csrf.exempt(bp) + + @bp.route('/', methods=['POST']) + def index(): + pass + + app.register_blueprint(bp) + response = client.post('/exempt/') + assert response.status_code == 200 + + +def test_error_handler(app, client): + @app.errorhandler(CSRFError) + def handle_csrf_error(e): + return e.description.lower() + + response = client.post('/') + assert response.get_data(as_text=True) == 'the csrf token is missing.' + + +def test_validate_error_logged(client, monkeypatch): + from flask_wtf.csrf import logger + + messages = [] + + def assert_info(message): + messages.append(message) + + monkeypatch.setattr(logger, 'info', assert_info) + + client.post('/') + assert len(messages) == 1 + assert messages[0] == 'The CSRF token is missing.' + + +def test_deprecated_csrfprotect(recwarn): + CsrfProtect() + w = recwarn.pop(FlaskWTFDeprecationWarning) + assert 'CSRFProtect' in str(w.message) + + +def test_deprecated_error_handler(csrf, client, recwarn): + @csrf.error_handler + def handle_csrf_error(reason): + if 'abort' in request.form: + abort(418) + + return 'return' + + w = recwarn.pop(FlaskWTFDeprecationWarning) + assert '@app.errorhandler' in str(w.message) + + response = client.post('/', data={'abort': '1'}) + assert response.status_code == 418 + + response = client.post('/') + assert response.status_code == 200 + assert 'return' in response.get_data(as_text=True) diff -Nru flask-wtf-0.14.2/tests/test_csrf_form.py flask-wtf-0.14.3/tests/test_csrf_form.py --- flask-wtf-0.14.2/tests/test_csrf_form.py 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_csrf_form.py 2020-02-06 23:03:33.000000000 +0000 @@ -0,0 +1,96 @@ +import pytest +from flask import g, session +from wtforms import ValidationError + +from flask_wtf import FlaskForm +from flask_wtf.csrf import generate_csrf, validate_csrf + + +def test_csrf_requires_secret_key(app, req_ctx): + # use secret key set by test setup + generate_csrf() + # fail with no key + app.secret_key = None + pytest.raises(RuntimeError, generate_csrf) + # use WTF_CSRF config + app.config['WTF_CSRF_SECRET_KEY'] = 'wtf_secret' + generate_csrf() + del app.config['WTF_CSRF_SECRET_KEY'] + # use direct argument + generate_csrf(secret_key='direct') + + +def test_token_stored_by_generate(req_ctx): + generate_csrf() + assert 'csrf_token' in session + assert 'csrf_token' in g + + +def test_custom_token_key(req_ctx): + generate_csrf(token_key='oauth_token') + assert 'oauth_token' in session + assert 'oauth_token' in g + + +def test_token_cached(req_ctx): + assert generate_csrf() == generate_csrf() + + +def test_validate(req_ctx): + validate_csrf(generate_csrf()) + + +def test_validation_errors(req_ctx): + e = pytest.raises(ValidationError, validate_csrf, None) + assert str(e.value) == 'The CSRF token is missing.' + + e = pytest.raises(ValidationError, validate_csrf, 'no session') + assert str(e.value) == 'The CSRF session token is missing.' + + token = generate_csrf() + e = pytest.raises(ValidationError, validate_csrf, token, time_limit=-1) + assert str(e.value) == 'The CSRF token has expired.' + + e = pytest.raises(ValidationError, validate_csrf, 'invalid') + assert str(e.value) == 'The CSRF token is invalid.' + + other_token = generate_csrf(token_key='other_csrf') + e = pytest.raises(ValidationError, validate_csrf, other_token) + assert str(e.value) == 'The CSRF tokens do not match.' + + +def test_form_csrf(app, client, app_ctx): + @app.route('/', methods=['GET', 'POST']) + def index(): + f = FlaskForm() + + if f.validate_on_submit(): + return 'good' + + if f.errors: + return f.csrf_token.errors[0] + + return f.csrf_token.current_token + + response = client.get('/') + assert response.get_data(as_text=True) == g.csrf_token + + response = client.post('/') + assert response.get_data(as_text=True) == 'The CSRF token is missing.' + + response = client.post('/', data={'csrf_token': g.csrf_token}) + assert response.get_data(as_text=True) == 'good' + + +def test_validate_error_logged(req_ctx, monkeypatch): + from flask_wtf.csrf import logger + + messages = [] + + def assert_info(message): + messages.append(message) + + monkeypatch.setattr(logger, 'info', assert_info) + FlaskForm().validate() + assert len(messages) == 1 + assert messages[0] == 'The CSRF token is missing.' diff -Nru flask-wtf-0.14.2/tests/test_csrf.py flask-wtf-0.14.3/tests/test_csrf.py --- flask-wtf-0.14.2/tests/test_csrf.py 2017-01-10 09:20:28.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_csrf.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,321 +0,0 @@ -from __future__ import with_statement - -import re -import warnings - -from flask import Blueprint, abort, render_template, request -from flask import g -from wtforms import ValidationError - -from flask_wtf._compat import FlaskWTFDeprecationWarning -from flask_wtf.csrf import CSRFError, CSRFProtect, generate_csrf, validate_csrf -from .base import MyForm, TestCase - - -class TestCSRF(TestCase): - def setUp(self): - app = self.create_app() - app.config['WTF_CSRF_SECRET_KEY'] = "a poorly kept secret." - csrf = CSRFProtect(app) - self.csrf = csrf - - @csrf.exempt - @app.route('/csrf-exempt', methods=['GET', 'POST']) - def csrf_exempt(): - form = MyForm() - if form.validate_on_submit(): - name = form.name.data.upper() - else: - name = '' - - return render_template( - "index.html", form=form, name=name - ) - - @csrf.exempt - @app.route('/csrf-protect-method', methods=['GET', 'POST']) - def csrf_protect_method(): - csrf.protect() - return 'protected' - - bp = Blueprint('csrf', __name__) - - @bp.route('/foo', methods=['GET', 'POST']) - def foo(): - return 'foo' - - app.register_blueprint(bp, url_prefix='/bar') - self.bp = bp - self.app = app - self.client = self.app.test_client() - - def test_invalid_csrf(self): - response = self.client.post("/", data={"name": "danny"}) - assert response.status_code == 400 - - @self.app.errorhandler(CSRFError) - def handle_csrf_error(e): - return e, 200 - - response = self.client.post("/", data={"name": "danny"}) - assert response.status_code == 200 - assert b'The CSRF token is missing.' in response.data - - def test_invalid_csrf2(self): - # tests with bad token - response = self.client.post("/", data={ - "name": "danny", - "csrf_token": "9999999999999##test" - # will work only if greater than time.time() - }) - assert response.status_code == 400 - - def test_invalid_secure_csrf3(self): - # test with multiple separators - response = self.client.post("/", data={ - "name": "danny", - "csrf_token": "1378915137.722##foo##bar##and" - # will work only if greater than time.time() - }) - assert response.status_code == 400 - - def test_valid_csrf(self): - with self.client: - self.client.get('/') - csrf_token = g.csrf_token - - response = self.client.post("/", data={ - "name": "danny", - "csrf_token": csrf_token - }) - assert b'DANNY' in response.data - - def test_prefixed_csrf(self): - with self.client: - self.client.get('/') - csrf_token = g.csrf_token - - response = self.client.post('/', data={ - 'prefix-name': 'David', - 'prefix-csrf_token': csrf_token, - }) - assert response.status_code == 200 - - def test_invalid_secure_csrf(self): - with self.client: - self.client.get('/', base_url='https://localhost/') - csrf_token = g.csrf_token - - response = self.client.post( - "/", - data={"name": "danny"}, - headers={'X-CSRFToken': csrf_token}, - base_url='https://localhost/', - ) - assert response.status_code == 400 - assert b'The referrer header is missing.' in response.data - - response = self.client.post( - "/", - data={"name": "danny"}, - headers={ - 'X-CSRFToken': csrf_token, - }, - environ_base={ - 'HTTP_REFERER': 'https://example.com/', - }, - base_url='https://localhost/', - ) - assert response.status_code == 400 - assert b'The referrer does not match the host.' in response.data - - response = self.client.post( - "/", - data={"name": "danny"}, - headers={ - 'X-CSRFToken': csrf_token, - }, - environ_base={ - 'HTTP_REFERER': 'http://localhost/', - }, - base_url='https://localhost/', - ) - assert response.status_code == 400 - assert b'The referrer does not match the host.' in response.data - - response = self.client.post( - "/", - data={"name": "danny"}, - headers={ - 'X-CSRFToken': csrf_token, - }, - environ_base={ - 'HTTP_REFERER': 'https://localhost:3000/', - }, - base_url='https://localhost/', - ) - assert response.status_code == 400 - assert b'The referrer does not match the host.' in response.data - - def test_valid_secure_csrf(self): - with self.client: - self.client.get('/', base_url='https://localhost/') - csrf_token = g.csrf_token - - response = self.client.post( - "/", - data={"name": "danny"}, - headers={ - 'X-CSRFToken': csrf_token, - }, - environ_base={ - 'HTTP_REFERER': 'https://localhost/', - }, - base_url='https://localhost/', - ) - assert response.status_code == 200 - - def test_valid_csrf_method(self): - with self.client: - self.client.get('/') - csrf_token = g.csrf_token - - response = self.client.post("/csrf-protect-method", data={ - "csrf_token": csrf_token - }) - assert response.status_code == 200 - - def test_empty_csrf_headers(self): - with self.client: - self.client.get('/', base_url='https://localhost/') - csrf_token = g.csrf_token - - self.app.config['WTF_CSRF_HEADERS'] = list() - response = self.client.post( - "/", - data={"name": "danny"}, - headers={ - 'X-CSRFToken': csrf_token, - }, - environ_base={ - 'HTTP_REFERER': 'https://localhost/', - }, - base_url='https://localhost/', - ) - assert response.status_code == 400 - - def test_custom_csrf_headers(self): - with self.client: - self.client.get('/', base_url='https://localhost/') - csrf_token = g.csrf_token - - self.app.config['WTF_CSRF_HEADERS'] = ['X-XSRF-TOKEN'] - response = self.client.post( - "/", - data={"name": "danny"}, - headers={ - 'X-XSRF-TOKEN': csrf_token, - }, - environ_base={ - 'HTTP_REFERER': 'https://localhost/', - }, - base_url='https://localhost/', - ) - assert response.status_code == 200 - - def test_not_endpoint(self): - response = self.client.post('/not-endpoint') - assert response.status_code == 404 - - def test_testing(self): - self.app.testing = True - self.client.post("/", data={"name": "danny"}) - - def test_csrf_exempt_view_with_form(self): - with self.client: - self.client.get('/', base_url='https://localhost/') - csrf_token = g.csrf_token - - response = self.client.post("/csrf-exempt", data={ - "name": "danny", - "csrf_token": csrf_token - }) - assert b'DANNY' in response.data - - def test_validate_csrf(self): - with self.app.test_request_context(): - self.assertRaises(ValidationError, validate_csrf, None) - self.assertRaises(ValidationError, validate_csrf, 'invalid') - validate_csrf(generate_csrf()) - - def test_validate_not_expiring_csrf(self): - with self.app.test_request_context(): - csrf_token = generate_csrf() - validate_csrf(csrf_token, time_limit=False) - - def test_csrf_token_helper(self): - @self.app.route("/token") - def withtoken(): - return render_template("csrf.html") - - with self.client: - response = self.client.get('/token') - assert re.search(br'token: ([0-9a-zA-Z\-._]+)', response.data) - - def test_csrf_blueprint(self): - response = self.client.post('/bar/foo') - assert response.status_code == 400 - - self.csrf.exempt(self.bp) - response = self.client.post('/bar/foo') - assert response.status_code == 200 - - def test_csrf_token_macro(self): - @self.app.route("/token") - def withtoken(): - return render_template("import_csrf.html") - - with self.client: - response = self.client.get('/token') - assert g.csrf_token in response.data.decode('utf8') - - def test_csrf_custom_token_key(self): - with self.app.test_request_context(): - # Generate a normal and a custom CSRF token - default_csrf_token = generate_csrf() - custom_csrf_token = generate_csrf(token_key='oauth_state') - - # Verify they are different due to using different session keys - self.assertNotEqual(default_csrf_token, custom_csrf_token) - - # However, the custom key can validate as well - validate_csrf(custom_csrf_token, token_key='oauth_state') - - def test_old_error_handler(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always', FlaskWTFDeprecationWarning) - - @self.csrf.error_handler - def handle_csrf_error(reason): - return 'caught csrf return' - - self.assertEqual(len(w), 1) - assert issubclass(w[0].category, FlaskWTFDeprecationWarning) - assert 'app.errorhandler(CSRFError)' in str(w[0].message) - - rv = self.client.post('/', data={'name': 'david'}) - assert b'caught csrf return' in rv.data - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always', FlaskWTFDeprecationWarning) - - @self.csrf.error_handler - def handle_csrf_error(reason): - abort(401, 'caught csrf abort') - - self.assertEqual(len(w), 1) - assert issubclass(w[0].category, FlaskWTFDeprecationWarning) - assert 'app.errorhandler(CSRFError)' in str(w[0].message) - - rv = self.client.post('/', data={'name': 'david'}) - assert b'caught csrf abort' in rv.data diff -Nru flask-wtf-0.14.2/tests/test_deprecated.py flask-wtf-0.14.3/tests/test_deprecated.py --- flask-wtf-0.14.2/tests/test_deprecated.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_deprecated.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -import warnings -from unittest import TestCase - -from wtforms.compat import with_metaclass -from wtforms.form import FormMeta - -from flask_wtf import CsrfProtect, FlaskForm, Form -from flask_wtf._compat import FlaskWTFDeprecationWarning - - -class TestDeprecated(TestCase): - def test_deprecated_form(self): - with warnings.catch_warnings(): - warnings.simplefilter('error', FlaskWTFDeprecationWarning) - - class F1(Form): - pass - - self.assertRaises(FlaskWTFDeprecationWarning, F1) - - class FMeta(FormMeta): - pass - - class F2(with_metaclass(FMeta, Form)): - pass - - self.assertRaises(FlaskWTFDeprecationWarning, F2) - - def test_deprecated_html5(self): - with warnings.catch_warnings(): - warnings.simplefilter('error', FlaskWTFDeprecationWarning) - self.assertRaises(FlaskWTFDeprecationWarning, __import__, 'flask_wtf.html5') - - def test_deprecated_csrf_enabled(self): - class F(FlaskForm): - pass - - with warnings.catch_warnings(): - warnings.simplefilter('error', FlaskWTFDeprecationWarning) - self.assertRaises(FlaskWTFDeprecationWarning, F, csrf_enabled=False) - - def test_deprecated_csrfprotect(self): - with warnings.catch_warnings(): - warnings.simplefilter('error', FlaskWTFDeprecationWarning) - self.assertRaises(FlaskWTFDeprecationWarning, CsrfProtect) diff -Nru flask-wtf-0.14.2/tests/test_file.py flask-wtf-0.14.3/tests/test_file.py --- flask-wtf-0.14.2/tests/test_file.py 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_file.py 2020-02-06 23:03:33.000000000 +0000 @@ -0,0 +1,95 @@ +import pytest +from werkzeug.datastructures import FileStorage, MultiDict +from wtforms import FileField as BaseFileField + +from flask_wtf import FlaskForm +from flask_wtf._compat import FlaskWTFDeprecationWarning +from flask_wtf.file import FileAllowed, FileField, FileRequired + + +@pytest.fixture +def form(req_ctx): + class UploadForm(FlaskForm): + class Meta: + csrf = False + + file = FileField() + + return UploadForm + + +def test_process_formdata(form): + assert form(MultiDict((('file', FileStorage()),))).file.data is None + assert form( + MultiDict((('file', FileStorage(filename='real')),)) + ).file.data is not None + + +def test_file_required(form): + form.file.kwargs['validators'] = [FileRequired()] + + f = form() + assert not f.validate() + assert f.file.errors[0] == 'This field is required.' + + f = form(file='not a file') + assert not f.validate() + assert f.file.errors[0] == 'This field is required.' + + f = form(file=FileStorage()) + assert not f.validate() + + f = form(file=FileStorage(filename='real')) + assert f.validate() + + +def test_file_allowed(form): + form.file.kwargs['validators'] = [FileAllowed(('txt',))] + + f = form() + assert f.validate() + + f = form(file=FileStorage(filename='test.txt')) + assert f.validate() + + f = form(file=FileStorage(filename='test.png')) + assert not f.validate() + assert f.file.errors[0] == 'File does not have an approved extension: txt' + + +def test_file_allowed_uploadset(app, form): + pytest.importorskip('flask_uploads') + from flask_uploads import UploadSet, configure_uploads + + app.config['UPLOADS_DEFAULT_DEST'] = 'uploads' + txt = UploadSet('txt', extensions=('txt',)) + configure_uploads(app, (txt,)) + form.file.kwargs['validators'] = [FileAllowed(txt)] + + f = form() + assert f.validate() + + f = form(file=FileStorage(filename='test.txt')) + assert f.validate() + + f = form(file=FileStorage(filename='test.png')) + assert not f.validate() + assert f.file.errors[0] == 'File does not have an approved extension.' + + +def test_validate_base_field(req_ctx): + class F(FlaskForm): + class Meta: + csrf = False + + f = BaseFileField(validators=[FileRequired()]) + + assert not F().validate() + assert not F(f=FileStorage()).validate() + assert F(f=FileStorage(filename='real')).validate() + + +def test_deprecated_filefield(recwarn, form): + assert not form().file.has_file() + w = recwarn.pop(FlaskWTFDeprecationWarning) + assert 'has_file' in str(w.message) diff -Nru flask-wtf-0.14.2/tests/test_form.py flask-wtf-0.14.3/tests/test_form.py --- flask-wtf-0.14.2/tests/test_form.py 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_form.py 2020-02-06 23:03:33.000000000 +0000 @@ -0,0 +1,165 @@ +from io import BytesIO + +from flask import json, request +from wtforms import FileField, HiddenField, IntegerField, StringField +from wtforms.compat import with_metaclass +from wtforms.form import FormMeta +from wtforms.validators import DataRequired +from wtforms.widgets import HiddenInput + +from flask_wtf import FlaskForm, Form +from flask_wtf._compat import FlaskWTFDeprecationWarning + + +class BasicForm(FlaskForm): + class Meta: + csrf = False + + name = StringField(validators=[DataRequired()]) + avatar = FileField() + + +def test_populate_from_form(app, client): + @app.route('/', methods=['POST']) + def index(): + form = BasicForm() + assert form.name.data == 'form' + + client.post('/', data={'name': 'form'}) + + +def test_populate_from_files(app, client): + @app.route('/', methods=['POST']) + def index(): + form = BasicForm() + assert form.avatar.data is not None + assert form.avatar.data.filename == 'flask.png' + + client.post('/', data={ + 'name': 'files', 'avatar': (BytesIO(), 'flask.png') + }) + + +def test_populate_from_json(app, client): + @app.route('/', methods=['POST']) + def index(): + form = BasicForm() + assert form.name.data == 'json' + + client.post( + '/', data=json.dumps({'name': 'json'}), + content_type='application/json' + ) + + +def test_populate_manually(app, client): + @app.route('/', methods=['POST']) + def index(): + form = BasicForm(request.args) + assert form.name.data == 'args' + + client.post('/', query_string={'name': 'args'}) + + +def test_populate_none(app, client): + @app.route('/', methods=['POST']) + def index(): + form = BasicForm(None) + assert form.name.data is None + + client.post('/', data={'name': 'ignore'}) + + +def test_validate_on_submit(app, client): + @app.route('/', methods=['POST']) + def index(): + form = BasicForm() + assert form.is_submitted() + assert not form.validate_on_submit() + assert 'name' in form.errors + + client.post('/') + + +def test_no_validate_on_get(app, client): + @app.route('/', methods=['GET', 'POST']) + def index(): + form = BasicForm() + assert not form.validate_on_submit() + assert 'name' not in form.errors + + client.get('/') + + +def test_hidden_tag(req_ctx): + class F(BasicForm): + class Meta: + csrf = True + + key = HiddenField() + count = IntegerField(widget=HiddenInput()) + + f = F() + out = f.hidden_tag() + assert all(x in out for x in ('csrf_token', 'count', 'key')) + assert 'avatar' not in out + assert 'csrf_token' not in f.hidden_tag('count', 'key') + + +def test_deprecated_form(req_ctx, recwarn): + class F(Form): + pass + + F() + w = recwarn.pop(FlaskWTFDeprecationWarning) + assert 'FlaskForm' in str(w.message) + + +def test_custom_meta_with_deprecated_form(req_ctx, recwarn): + class FMeta(FormMeta): + pass + + class F(with_metaclass(FMeta, Form)): + pass + + F() + recwarn.pop(FlaskWTFDeprecationWarning) + + +def test_deprecated_csrf_enabled(req_ctx, recwarn): + class F(FlaskForm): + pass + + F(csrf_enabled=False) + w = recwarn.pop(FlaskWTFDeprecationWarning) + assert "meta={'csrf': False}" in str(w.message) + + +def test_set_default_message_language(app, client): + + @app.route('/default', methods=['POST']) + def default(): + form = BasicForm() + assert not form.validate_on_submit() + assert 'This field is required.' in form.name.errors + + client.post('/default', data={'name': ' '}) + + @app.route('/es', methods=['POST']) + def es(): + app.config['WTF_I18N_ENABLED'] = False + + class MyBaseForm(FlaskForm): + class Meta: + csrf = False + locales = ['es'] + + class NameForm(MyBaseForm): + name = StringField(validators=[DataRequired()]) + + form = NameForm() + assert form.meta.locales == ['es'] + assert not form.validate_on_submit() + assert 'Este campo es obligatorio.' in form.name.errors + + client.post('/es', data={'name': ' '}) diff -Nru flask-wtf-0.14.2/tests/test_html5.py flask-wtf-0.14.3/tests/test_html5.py --- flask-wtf-0.14.2/tests/test_html5.py 1970-01-01 00:00:00.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_html5.py 2020-02-06 23:03:33.000000000 +0000 @@ -0,0 +1,7 @@ +from flask_wtf._compat import FlaskWTFDeprecationWarning + + +def test_deprecated_html5(recwarn): + __import__('flask_wtf.html5') + w = recwarn.pop(FlaskWTFDeprecationWarning) + assert 'wtforms.fields.html5' in str(w.message) diff -Nru flask-wtf-0.14.2/tests/test_i18n.py flask-wtf-0.14.3/tests/test_i18n.py --- flask-wtf-0.14.2/tests/test_i18n.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_i18n.py 2020-02-06 23:03:33.000000000 +0000 @@ -1,35 +1,74 @@ -from __future__ import with_statement +# coding=utf8 +import pytest +from flask import request +from wtforms import StringField +from wtforms.validators import DataRequired, Length -from .base import TestCase, to_unicode +from flask_wtf import FlaskForm -class TestI18NCase(TestCase): - def test_i18n_disabled(self): - self.app.config['WTF_CSRF_ENABLED'] = False - response = self.client.post( - "/", - headers={'Accept-Language': 'zh-CN,zh;q=0.8'}, - data={} - ) - assert b'This field is required.' in response.data +class NameForm(FlaskForm): + class Meta: + csrf = False - def test_i18n_enabled(self): - from flask import request - from flask_babel import Babel - babel = Babel(self.app) + name = StringField(validators=[DataRequired(), Length(min=8)]) + + +def test_no_extension(app, client): + @app.route('/', methods=['POST']) + def index(): + form = NameForm() + form.validate() + assert form.name.errors[0] == 'This field is required.' - @babel.localeselector - def get_locale(): - return request.accept_languages.best_match(['en', 'zh'], 'en') - - self.app.config['WTF_CSRF_ENABLED'] = False - - response = self.client.post( - "/", - headers={'Accept-Language': 'zh-CN,zh;q=0.8'}, - data={} - ) - assert '\u8be5\u5b57\u6bb5\u662f' in to_unicode(response.data) + client.post( + '/', headers={'Accept-Language': 'zh-CN,zh;q=0.8'} + ) - response = self.client.post("/", data={}) - assert b'This field is required.' in response.data + +def test_i18n(app, client): + try: + from flask_babel import Babel + except ImportError: + try: + from flask_babelex import Babel + except ImportError: + pytest.skip('Flask-Babel or Flask-BabelEx must be installed.') + + babel = Babel(app) + + @babel.localeselector + def get_locale(): + return request.accept_languages.best_match(['en', 'zh'], 'en') + + @app.route('/', methods=['POST']) + def index(): + form = NameForm() + form.validate() + + if not app.config.get('WTF_I18N_ENABLED', True): + assert form.name.errors[0] == 'This field is required.' + elif not form.name.data: + assert form.name.errors[0] == u'该字段是必填字段。' + else: + assert form.name.errors[0] == u'字段长度必须至少 8 个字符。' + + client.post('/', headers={'Accept-Language': 'zh-CN,zh;q=0.8'}) + client.post( + '/', headers={'Accept-Language': 'zh'}, data={'name': 'short'} + ) + app.config['WTF_I18N_ENABLED'] = False + client.post('/', headers={'Accept-Language': 'zh'}) + + +def test_outside_request(): + pytest.importorskip('babel') + from flask_wtf.i18n import translations + + s = 'This field is required.' + assert translations.gettext(s) == s + + ss = 'Field must be at least %(min)d character long.' + sp = 'Field must be at least %(min)d character long.' + assert translations.ngettext(ss, sp, 1) == ss + assert translations.ngettext(ss, sp, 2) == sp diff -Nru flask-wtf-0.14.2/tests/test_recaptcha.py flask-wtf-0.14.3/tests/test_recaptcha.py --- flask-wtf-0.14.2/tests/test_recaptcha.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_recaptcha.py 2020-02-06 23:03:33.000000000 +0000 @@ -1,71 +1,151 @@ -from __future__ import with_statement - -from flask import Flask, json, render_template +import pytest +from flask import json +from markupsafe import Markup from flask_wtf import FlaskForm +from flask_wtf._compat import to_bytes from flask_wtf.recaptcha import RecaptchaField -from .base import TestCase - +from flask_wtf.recaptcha.validators import Recaptcha, http -RECAPTCHA_PUBLIC_KEY = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J' -RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu' +class RecaptchaForm(FlaskForm): + class Meta: + csrf = False -class RecaptchaFrom(FlaskForm): - SECRET_KEY = "a poorly kept secret." recaptcha = RecaptchaField() -class TestRecaptcha(TestCase): - def create_app(self): - app = Flask(__name__) - app.secret_key = "secret" - app.config['RECAPTCHA_PUBLIC_KEY'] = RECAPTCHA_PUBLIC_KEY - app.config['RECAPTCHA_PRIVATE_KEY'] = RECAPTCHA_PRIVATE_KEY - - @app.route("/", methods=("GET", "POST")) - def index(): - form = RecaptchaFrom(meta={'csrf': False}) - if form.validate_on_submit(): - return 'OK' - return render_template("recaptcha.html", form=form) - return app - - def test_recaptcha(self): - response = self.client.get('/') - assert b'//www.google.com/recaptcha/api.js' in response.data - - def test_invalid_recaptcha(self): - response = self.client.post('/', data={}) - assert b'missing' in response.data - - def test_send_recaptcha_request(self): - response = self.client.post('/', data={ - 'g-recaptcha-response': 'test' +@pytest.fixture +def app(app): + app.testing = False + app.config['PROPAGATE_EXCEPTIONS'] = True + app.config['RECAPTCHA_PUBLIC_KEY'] = 'public' + app.config['RECAPTCHA_PRIVATE_KEY'] = 'private' + return app + + +@pytest.yield_fixture(autouse=True) +def req_ctx(app): + with app.test_request_context( + data={'g-recaptcha-response': 'pass'} + ) as ctx: + yield ctx + + +def test_config(app, monkeypatch): + f = RecaptchaForm() + monkeypatch.setattr(app, 'testing', True) + f.validate() + assert not f.recaptcha.errors + monkeypatch.undo() + + monkeypatch.delitem(app.config, 'RECAPTCHA_PUBLIC_KEY') + pytest.raises(RuntimeError, f.recaptcha) + monkeypatch.undo() + + monkeypatch.delitem(app.config, 'RECAPTCHA_PRIVATE_KEY') + pytest.raises(RuntimeError, f.validate) + + +def test_render_has_js(): + f = RecaptchaForm() + render = f.recaptcha() + assert 'https://www.google.com/recaptcha/api.js' in render + + +def test_render_custom_html(app): + app.config['RECAPTCHA_HTML'] = 'custom' + f = RecaptchaForm() + render = f.recaptcha() + assert render == 'custom' + assert isinstance(render, Markup) + + +def test_render_custom_args(app): + app.config['RECAPTCHA_PARAMETERS'] = {'key': '(value)'} + app.config['RECAPTCHA_DATA_ATTRS'] = {'red': 'blue'} + f = RecaptchaForm() + render = f.recaptcha() + assert '?key=%28value%29' in render + assert 'data-red="blue"' in render + + +def test_missing_response(app): + with app.test_request_context(): + f = RecaptchaForm() + f.validate() + assert f.recaptcha.errors[0] == 'The response parameter is missing.' + + +class MockResponse(object): + def __init__(self, code, error='invalid-input-response', read_bytes=False): + self.code = code + self.data = json.dumps({ + 'success': not error, + 'error-codes': [error] if error else [] }) - assert b'invalid' in response.data + self.read_bytes = read_bytes - response = self.client.post('/', data=json.dumps({ - 'g-recaptcha-response': 'test' - }), content_type='application/json') - assert b'invalid' in response.data - - def test_testing(self): - self.app.testing = True - response = self.client.post('/', data={ - 'g-recaptcha-response': 'test' - }) - assert b'invalid' not in response.data + def read(self): + if self.read_bytes: + return to_bytes(self.data) - def test_no_private_key(self): - self.app.testing = False - self.app.config.pop('RECAPTCHA_PRIVATE_KEY', None) - response = self.client.post('/', data={ - 'g-recaptcha-response': 'test' - }) - assert response.status_code == 500 + return self.data + + +def test_send_invalid_request(monkeypatch): + def mock_urlopen(url, data): + return MockResponse(200) + + monkeypatch.setattr(http, 'urlopen', mock_urlopen) + f = RecaptchaForm() + f.validate() + assert f.recaptcha.errors[0] == ( + 'The response parameter is invalid or malformed.' + ) + + +def test_response_from_json(app, monkeypatch): + def mock_urlopen(url, data): + return MockResponse(200) + + monkeypatch.setattr(http, 'urlopen', mock_urlopen) + + with app.test_request_context( + data=json.dumps({'g-recaptcha-response': 'pass'}), + content_type='application/json' + ): + f = RecaptchaForm() + f.validate() + assert f.recaptcha.errors[0] != 'The response parameter is missing.' + + +def test_request_fail(monkeypatch): + def mock_urlopen(url, data): + return MockResponse(400) + + monkeypatch.setattr(http, 'urlopen', mock_urlopen) + f = RecaptchaForm() + f.validate() + assert f.recaptcha.errors + + +def test_request_success(monkeypatch): + def mock_urlopen(url, data): + return MockResponse(200, '') + + monkeypatch.setattr(http, 'urlopen', mock_urlopen) + f = RecaptchaForm() + f.validate() + assert not f.recaptcha.errors + + +def test_request_unmatched_error(monkeypatch): + def mock_urlopen(url, data): + return MockResponse(200, 'not-an-error', True) - def test_no_public_key(self): - self.app.config.pop('RECAPTCHA_PUBLIC_KEY', None) - response = self.client.get('/') - assert response.status_code == 500 + monkeypatch.setattr(http, 'urlopen', mock_urlopen) + f = RecaptchaForm() + f.recaptcha.validators = [Recaptcha('custom')] + f.validate() + assert f.recaptcha.errors[0] == 'custom' diff -Nru flask-wtf-0.14.2/tests/test_uploads.py flask-wtf-0.14.3/tests/test_uploads.py --- flask-wtf-0.14.2/tests/test_uploads.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_uploads.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,177 +0,0 @@ -from __future__ import with_statement - -try: - from io import BytesIO -except ImportError: - from StringIO import StringIO as BytesIO - -from flask import render_template, request - -from wtforms import StringField, FieldList -from flask_wtf import FlaskForm -from flask_wtf.file import FileField -from flask_wtf.file import file_required, file_allowed - -from .base import TestCase - - -class UploadSet(object): - def __init__(self, name='files', extensions=None): - self.name = name - self.extensions = extensions - - def file_allowed(self, storage, basename): - if not self.extensions: - return True - - ext = basename.rsplit('.', 1)[-1] - return ext in self.extensions - -images = UploadSet('images', ['jpg', 'png']) - - -class FileUploadForm(FlaskForm): - upload = FileField("Upload file") - - -class MultipleFileUploadForm(FlaskForm): - uploads = FieldList(FileField("upload"), min_entries=3) - - -class ImageUploadForm(FlaskForm): - upload = FileField("Upload file", - validators=[file_required(), - file_allowed(images)]) - - -class TextUploadForm(FlaskForm): - upload = FileField("Upload file", - validators=[file_required(), - file_allowed(['txt'])]) - - -class TestFileUpload(TestCase): - def create_app(self): - app = super(TestFileUpload, self).create_app() - app.config['WTF_CSRF_ENABLED'] = False - - @app.route("/upload-image/", methods=("POST",)) - def upload_image(): - form = ImageUploadForm() - if form.validate_on_submit(): - return "OK" - return "invalid" - - @app.route("/upload-text/", methods=("POST",)) - def upload_text(): - form = TextUploadForm() - if form.validate_on_submit(): - return "OK" - return "invalid" - - @app.route("/upload-multiple/", methods=("POST",)) - def upload_multiple(): - form = MultipleFileUploadForm() - if form.validate_on_submit(): - assert len(form.uploads.entries) == 3 - for upload in form.uploads.entries: - assert upload.has_file() - - return "OK" - - @app.route("/upload/", methods=("POST",)) - def upload(): - form = FileUploadForm() - if form.validate_on_submit(): - filedata = form.upload.data - else: - filedata = None - - return render_template("upload.html", - filedata=filedata, - form=form) - - return app - - def test_multiple_files(self): - fps = [self.app.open_resource("flask.png") for i in range(3)] - data = [("uploads-%d" % i, fp) for i, fp in enumerate(fps)] - response = self.client.post("/upload-multiple/", data=dict(data)) - assert response.status_code == 200 - - def test_valid_file(self): - with self.app.open_resource("flask.png") as fp: - response = self.client.post( - "/upload-image/", - data={'upload': fp} - ) - - assert b'OK' in response.data - - def test_missing_file(self): - response = self.client.post( - "/upload-image/", - data={'upload': "test"} - ) - - assert b'invalid' in response.data - - def test_invalid_file(self): - with self.app.open_resource("flask.png") as fp: - response = self.client.post( - "/upload-text/", - data={'upload': fp} - ) - - assert b'invalid' in response.data - - def test_invalid_file_2(self): - response = self.client.post( - "/upload/", - data={'upload': 'flask.png'} - ) - - assert b'flask.png' not in response.data - - def test_valid_txt_file(self): - with self.app.open_resource("flask.txt") as fp: - response = self.client.post( - "/upload-text/", - data={'upload': fp} - ) - - assert b'OK' in response.data - - def test_invalid_image_file(self): - with self.app.open_resource("flask.txt") as fp: - response = self.client.post( - "/upload-image/", - data={'upload': fp} - ) - - assert b'invalid' in response.data - - -class BrokenForm(FlaskForm): - text_fields = FieldList(StringField()) - file_fields = FieldList(FileField()) - -text_data = [('text_fields-0', 'First input'), - ('text_fields-1', 'Second input')] - -file_data = [('file_fields-0', (BytesIO(b'contents 0'), 'file0.txt')), - ('file_fields-1', (BytesIO(b'contents 1'), 'file1.txt'))] - - -class TestFileList(TestCase): - def test_multiple_upload(self): - data = dict(text_data + file_data) - with self.app.test_request_context(method='POST', data=data): - assert len(request.files) # the files have been added to the - # request - - f = BrokenForm(meta={'csrf': False}) - - assert f.validate_on_submit() - assert len(text_data) == len(f.text_fields) - assert len(file_data) == len(f.file_fields) diff -Nru flask-wtf-0.14.2/tests/test_validation.py flask-wtf-0.14.3/tests/test_validation.py --- flask-wtf-0.14.2/tests/test_validation.py 2017-01-09 19:31:10.000000000 +0000 +++ flask-wtf-0.14.3/tests/test_validation.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,113 +0,0 @@ -from __future__ import with_statement - -from flask import g, json - -from flask_wtf.csrf import generate_csrf -from .base import MyForm, TestCase, capture_logging, to_unicode - - -class TestValidateOnSubmit(TestCase): - - def test_not_submitted(self): - response = self.client.get("/") - assert b'DANNY' not in response.data - - def test_submitted_not_valid(self): - self.app.config['WTF_CSRF_ENABLED'] = False - response = self.client.post("/", data={}) - assert b'DANNY' not in response.data - - def test_submitted_and_valid(self): - self.app.config['WTF_CSRF_ENABLED'] = False - response = self.client.post("/", data={"name": "danny"}) - assert b'DANNY' in response.data - - def test_json_data(self): - self.app.config['WTF_CSRF_ENABLED'] = False - response = self.client.post( - '/', content_type='application/json', - data=json.dumps({'name': 'Flask-WTF'}) - ) - assert b'FLASK-WTF' in response.data - - -class TestValidateWithoutSubmit(TestCase): - def test_unsubmitted_valid(self): - class obj: - name = 'foo' - - with self.app.test_request_context(): - assert MyForm(obj=obj, meta={'csrf': False}).validate() - t = generate_csrf() - assert MyForm(obj=obj, csrf_token=t).validate() - - -class TestHiddenTag(TestCase): - - def test_hidden_tag(self): - - response = self.client.get("/hidden/") - assert to_unicode(response.data).count('type="hidden"') == 5 - assert b'name="_method"' in response.data - - -class TestCSRF(TestCase): - - def test_csrf_token(self): - - response = self.client.get("/") - snippet = '=3 + flask-babel + flask-uploads +commands = coverage run -p -m pytest + +[testenv:py-babelex] +deps = + coverage + pytest>=3 + flask-babelex +commands = coverage run -p -m pytest + +[testenv:py-no-babel] +deps = + coverage + pytest>=3 +commands = coverage run -p -m pytest + +[testenv:docs-html] +deps = + sphinx + flask-sphinx-themes +commands = + sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html + +[testenv:docs-linkcheck] +deps = sphinx +commands = + sphinx-build -W -b linkcheck -d docs/_build/doctrees docs docs/_build/linkcheck + +[testenv:coverage-report] +deps = coverage +skip_install = true +commands = + coverage combine + coverage report + coverage html + +[testenv:codecov] +passenv = CI TRAVIS TRAVIS_* +deps = codecov +skip_install = true +commands = + coverage combine + coverage report + codecov