././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/0000755000175000017500000000000000000000000013476 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/AUTHORS0000644000175000017500000000012700000000000014546 0ustar00daviddavid00000000000000* Dan Jacob * Ron DuPlain * Daniel Lepage * Anthony Ford * Hsiaoming Yang * David Lord ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1581031083.013521 Flask-WTF-0.14.3/Flask_WTF.egg-info/0000755000175000017500000000000000000000000016750 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581031082.0 Flask-WTF-0.14.3/Flask_WTF.egg-info/PKG-INFO0000644000175000017500000000400500000000000020044 0ustar00daviddavid00000000000000Metadata-Version: 2.1 Name: Flask-WTF Version: 0.14.3 Summary: Simple integration of Flask and WTForms. Home-page: https://github.com/lepture/flask-wtf 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: 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. Links ----- * `Documentation `_ * `PyPI `_ * `GitHub `_ Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581031083.0 Flask-WTF-0.14.3/Flask_WTF.egg-info/SOURCES.txt0000644000175000017500000000175200000000000020641 0ustar00daviddavid00000000000000AUTHORS LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini Flask_WTF.egg-info/PKG-INFO Flask_WTF.egg-info/SOURCES.txt Flask_WTF.egg-info/dependency_links.txt Flask_WTF.egg-info/not-zip-safe Flask_WTF.egg-info/requires.txt Flask_WTF.egg-info/top_level.txt docs/Makefile docs/api.rst docs/authors.rst docs/changelog.rst docs/conf.py docs/config.rst docs/csrf.rst docs/form.rst docs/index.rst docs/install.rst docs/license.rst docs/make.bat docs/quickstart.rst docs/upgrade.rst docs/_static/flask-wtf.png docs/_templates/brand.html docs/_templates/useful-links.html flask_wtf/__init__.py flask_wtf/_compat.py flask_wtf/csrf.py flask_wtf/file.py flask_wtf/form.py flask_wtf/html5.py flask_wtf/i18n.py flask_wtf/recaptcha/__init__.py flask_wtf/recaptcha/fields.py flask_wtf/recaptcha/validators.py flask_wtf/recaptcha/widgets.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././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581031082.0 Flask-WTF-0.14.3/Flask_WTF.egg-info/dependency_links.txt0000644000175000017500000000000100000000000023016 0ustar00daviddavid00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947440.0 Flask-WTF-0.14.3/Flask_WTF.egg-info/not-zip-safe0000644000175000017500000000000100000000000021176 0ustar00daviddavid00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581031082.0 Flask-WTF-0.14.3/Flask_WTF.egg-info/requires.txt0000644000175000017500000000003300000000000021344 0ustar00daviddavid00000000000000Flask WTForms itsdangerous ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581031082.0 Flask-WTF-0.14.3/Flask_WTF.egg-info/top_level.txt0000644000175000017500000000001200000000000021473 0ustar00daviddavid00000000000000flask_wtf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/LICENSE0000644000175000017500000000272200000000000014506 0ustar00daviddavid00000000000000Copyright (c) 2010 by Dan Jacob. Copyright (c) 2013 by Hsiaoming Yang. Some rights reserved. Redistribution and use in source and binary forms, 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. THIS SOFTWARE 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/MANIFEST.in0000644000175000017500000000036000000000000015233 0ustar00daviddavid00000000000000include AUTHORS LICENSE test-requirements.txt tox.ini graft tests recursive-exclude tests *.pyc recursive-exclude tests *.pyo graft docs prune docs/_build exclude docs/_themes/.git* recursive-exclude docs *.pyc recursive-exclude docs *.pyo ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/PKG-INFO0000644000175000017500000000400500000000000014572 0ustar00daviddavid00000000000000Metadata-Version: 2.1 Name: Flask-WTF Version: 0.14.3 Summary: Simple integration of Flask and WTForms. Home-page: https://github.com/lepture/flask-wtf 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: 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. Links ----- * `Documentation `_ * `PyPI `_ * `GitHub `_ Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/README.rst0000644000175000017500000000110500000000000015162 0ustar00daviddavid00000000000000Flask-WTF ========= .. image:: https://travis-ci.org/lepture/flask-wtf.svg?branch=master :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. Links ----- * `Documentation `_ * `PyPI `_ * `GitHub `_ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/docs/0000755000175000017500000000000000000000000014426 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/Makefile0000644000175000017500000001670200000000000016074 0ustar00daviddavid00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-WTF.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-WTF.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-WTF" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-WTF" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/docs/_static/0000755000175000017500000000000000000000000016054 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/_static/flask-wtf.png0000644000175000017500000002553400000000000020471 0ustar00daviddavid00000000000000PNG  IHDRb5 +vsBIT|d pHYsa(atEXtSoftwarewww.inkscape.org< IDATxg$Uն8($EAA |P_A JQPD%Jxɒ*YA2 ~3=}:Wu0]^{g6L&d2L3d2L&3B&d2d!d2d2Lf@d2L&3l d2L&@62L& L&d L&d2n; fzxxxY"2d2P{JGhj̛ӳg2L&3tA4X`g`u`q@%yy/L&dF$b HXXO6pptL&d}3`}8sWd2LfJzn HZ=-ۧvG2L&*@.M`sG:5d2L&3ؔn HZ 8d wGٞ8؝d2Lf(@8pֵ`wdjG҇$m L8Lf4!! ,SJ=6 `FB[iy`-ۯV'f$- O,eԁ[[پpd27ex_B $ #s[r$lؕ5_S35Lei Xb[dz`I33s'~ 6!H_N%HZx8m`؝I"Vm`$EG붗wS|h.p) ̶TB;C_AsNS~(@xĶrpup?1u0oef`ZBzv$}Wҥ/1``هL5$ y3>}:fv iQۏvĀc=.zi.yc %Ȟ\M֏^OwJR)eOV7 X8x]zڡb,_;C'd۹I,+iNXBJ62 (sWăw0rZ^tp|sYM6}_Ƅ ΪLK %l.36v 'aXT4 -3ij o$8S@(ۯ?3,n_ 1erS+i/φ_<^=[`S ^ j ]SE "1^R͜J:;OsK-m!9"b0b%"qaMI_%n_i.hMC$|: R4ztl oI1*C ; 6U<%$4˾4/-il|&~`FdG8fiC@(#am!HbvwcN"ɺ?FxڡB"(fˠ Yu]Z͵3'~DJGm?{l bDtQ~KM#IؾpHҬ5$r_CqHm_l-BU;| pu@(:y!lii?t?- 艁 i)bn%5 Lr XpwV8~x 8 pK$-Y]]qlGJū^o|8(+֞^S>D^jMZBoi6 %it0xM'BIsҼ G<F)0p{&юB ࿶Yۋlof$fK"hI?N]Lm9ф(Ҵ5۾@-PoOʲi\ }};Sqw=&r.H`JB ,!ifl 4_ 53C^nJp8lwNlKR ȲZtV<͔H [&QRwf4Cmw.X:"FMc%Ԥ幅$J pxHTONYZ:ÇfJQť7RLUT]@Čx Wi4Bw žm]DI%ʭX9' MuKLSlOT! :8n4k&62C* n.hsi,Nc%<)-p'i,AqLd4T8- u<RG@<˹/$d+ؐTOx?aYݒ3mQD)@.Sq:e0mM$ƒp*n Hd2nJ 菁p0V-)IiYz(f/v^>S Ҳ0牂XϦ?Dl٫~5Ĺz0_%x^)IW!r]7&ƧvxM@(1n{62gEҟzn+|H')>_I1`x2r ph EQ D c S6$ !p$M4ZJ,LG)4CU{_tӈh0~lmdE)!\g3V=l IH:}K丷{USqq>qЊQwS@șRYXx8|XX06 ^"RT\ EGwiCæBpO2H"FSf*Lg{lSMI?}KCO'Qabtg O ^_sDH:v?pbU6XfI7Zے /a4֫q-ʔ)8Aҕ@,ҧu>zۜx[e+8^V$vĠ`-閗z EbIs4l=Z{ XR/: rLRBi{Lv!~@TBAA=+D̈́:; 5mIޏ:}[[cTB1(46]vFb޸娂}[G #"(^V/~}ϷqMMTjlRPoM`.4`tFDʅ |Qe&Ǻ ظvnC78'`~QV&a!FvNSl*qevµ x\)l;.24BI_!+7ۧ;QW3AI0'mX߮e} پ5Z+qmibo؝Ike=? 1S{2p83)@4e pJ"=p=. 5-db6==ֿO0Sz=`9R{=& fjQU8Qecb"IwYaAVJS81p<\ Fa۴9BG;F]uїBHځ|TZN P `bZf:ˈ{wa1 L۔A·ll"쒚߶Ǫ"Cx"WV8ۋ #˩SKmLs=u8Xl ?ik.drw 3dpTۘb NdTslYM݋7xI9ml_H I,ڃd`ubZ\ε xbi_*<[Hz>KY}6q"<2}]/Iђ6#"+ML)f"KЌGDAQ\8GRlۯ&a#mv8#&RX+ !23p}fn F7ɒd]cR` jNV D &t_a,p|ΐ4;Lu6MPez^۔<񜺮4:*8UcKEJSQED3u3kY5dݗG% ">{^@4(1IxmZw׮sr)~9,DxG`3=, шWKDf#mnTZDw, -ܠB<H-a$}8KOh\BL3"8߆x`YTG.JÙs~y*F: 0+ Z'>IL jzB?@Rk IErI-C"Ϸ}lz12='ou 'mp(a$+mIoo Tb_I :O.d  {;}.,Igԫq>6nLLñ:1铧%g0d`]&U_ l=hʱDZW?Y_$F!iBф$Zi]I+O%i `ʴH#14SSCHܾlR%ls6>^h#1!mmƹ.01}YYfK3*88 mz4p QrdLh0D1Ͱ!pU^2L63uQPy(Vv}+iV9KZȺP4pOJL4JS_&!jֿNxL9 "2STtFrϻ0yXֶv}F=V +* KNlP|g7\2H@VGB^,?oz}8\@6 ~v(S!6lv%Kھ3HO1aRӉsPh:8G)QV(H vA [x.Pn=kHBKZľtM4.U4PX ;,zEPWlj͹۾)Ne$ ]-Y+i#BsI:H6M)4Bo]bTwSN=Qk .NF]#F߷2~+}s~.4. #PiҧDN#Ci:d8 I0i IDAT &< SحSX Ѳ2@'BrE9ZR75wth)mhIPUm2-ohIGrEųX)+K otV"8 2H=`kR IsKmq;aX4>M%dE55%zݕ4Svam [DjNJ7H#^Vö%ɍwIKVG$&/Dj:h#ym纋nػyZOtsnEJ:k=p.pf MI`u;xh~y2+^^!Xx`l9B0e L~MꇕQ4Dޓ.mRlwl<`u sB)q"$QWS$񧳀wBʏku~3&h&gii(IU;LҦ]_I#IߣV ޟD ܍)XHn=1y[CCl?B?ڊl d STzՆ?ؒtBS~4kI۾[֯z{NiIb# / nԽmЪv+KTfNŋ nx'ST@],\v*T" /]=:-γTumؾk>U+ tFC+SeYb9S {oշlo{5؉oF(> .AI$Ucrs{BNN^Ah{A۫>S[Gjx_t:|HBը h6Z&4Y76@50IKU'$m*i'5֮8:1G|;93p[,d'mDR36P0ZJCH,sLfJl^jMO,`~LmC%g0ʵkU;'k?Ѣ_^+9灯} }fu=I#z:ioM"e/BzYoפo7Wi|!j9seښk `>^_"”V>m\ӯZWkcKhgu``Oӏ~$ziqx(`վgVpLݗ/4pkGBw!}Vu mGգx#ne&~OY@cD!W{ 5FzhmB^չߥkt1_c޲D@v(_G\y02Oӏ}Fnn8{Sk#>g:bj"/M 13>}"bu<\Lҗc/#qG9j1?H;Y^!%}Wޠ%:8(Mot`+a\o" Jjr`Yx>MCۤ@!KYA09`p2։B= ui$-N\T>ȱ 2 ;e[ mZ*!˼~=O":ude탈@< )]ǫVھ ]]wŔ$b$Pzl.uTd QfH擒ft*x 8FiK@Io_u)z"Pۇms8`$޴:1"})-/WV"gIu^}K$a?tZjoelu 7JZ ؚPڼvZ&`1~C#''&텈ij^{0+˝&ݰ8YOby6RmZ3X~}O=¹5 Pfȫhrzc$\#4&]NTp-e%0#QpR63#UZ=^NXi6v j4zoŀnTHP-*jhϐ4\jFdDu=$)@؍P'vTCTiE(d2HBIe)*L(= V<;p{F)Ku`؈lav"yF:E3]B_}rg2L&3)3HdU*Xf H&3ld2L&Sz`R Ӗ\BKu v$4_!ҺN}c3L&LIΛne^ET ڞXV3L&LIO T~About Flask-WTF

Simple integration of Flask and WTForms, including CSRF, file upload and Recaptcha integration.

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/_templates/useful-links.html0000644000175000017500000000040300000000000022067 0ustar00daviddavid00000000000000

Useful Links

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/api.rst0000644000175000017500000000116500000000000015734 0ustar00daviddavid00000000000000Developer Interface =================== Forms and Fields ---------------- .. module:: flask_wtf .. autoclass:: FlaskForm :members: .. autoclass:: Form(...) .. autoclass:: RecaptchaField .. autoclass:: Recaptcha .. autoclass:: RecaptchaWidget .. module:: flask_wtf.file .. autoclass:: FileField :members: has_file .. autoclass:: FileAllowed .. autoclass:: FileRequired CSRF Protection --------------- .. module:: flask_wtf.csrf .. autoclass:: CSRFProtect :members: .. autoclass:: CsrfProtect(...) .. autoclass:: CSRFError :members: .. autofunction:: generate_csrf .. autofunction:: validate_csrf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/docs/authors.rst0000644000175000017500000000044500000000000016650 0ustar00daviddavid00000000000000Authors ======= Flask-WTF is created by Dan Jacob, and now is maintained by Hsiaoming Yang. Contributors ------------ People who send patches and suggestions: .. include:: ../AUTHORS Find more contributors on GitHub_. .. _GitHub: https://github.com/lepture/flask-wtf/graphs/contributors ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030484.0 Flask-WTF-0.14.3/docs/changelog.rst0000644000175000017500000002121700000000000017112 0ustar00daviddavid00000000000000Flask-WTF Changelog =================== Version 0.14.3 -------------- Released 2020-02-06 - Fix deprecated imports from ``werkzeug`` and ``collections``. Version 0.14.2 -------------- Released 2017-01-10 - Fix bug where ``FlaskForm`` assumed ``meta`` argument was not ``None`` if it was passed. (`#278`_) .. _#278: https://github.com/lepture/flask-wtf/issues/278 Version 0.14.1 -------------- Released 2017-01-10 - Fix bug where the file validators would incorrectly identify an empty file as valid data. (`#276`_, `#277`_) - ``FileField`` is no longer deprecated. The data is checked during processing and only set if it's a valid file. - ``has_file`` *is* deprecated; it's now equivalent to ``bool(field.data)``. - ``FileRequired`` and ``FileAllowed`` work with both the Flask-WTF and WTForms ``FileField`` classes. - The ``Optional`` validator now works with ``FileField``. .. _#276: https://github.com/lepture/flask-wtf/issues/276 .. _#277: https://github.com/lepture/flask-wtf/pull/277 Version 0.14 ------------ Released 2017-01-06 - Use itsdangerous to sign CSRF tokens and check expiration instead of doing it ourselves. (`#264`_) - All tokens are URL safe, removing the ``url_safe`` parameter from ``generate_csrf``. (`#206`_) - All tokens store a timestamp, which is checked in ``validate_csrf``. The ``time_limit`` parameter of ``generate_csrf`` is removed. - Remove the ``app`` attribute from ``CsrfProtect``, use ``current_app``. (`#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 ``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 error. The behavior was not clear in previous docs. - (`#200`_, `#209`_, `#243`_, `#252`_) - Use ``Form.Meta`` instead of deprecated ``SecureForm`` for CSRF (and everything else). (`#216`_, `#271`_) - ``csrf_enabled`` parameter is still recognized but deprecated. All other attributes and methods from ``SecureForm`` are removed. (`#271`_) - Provide ``WTF_CSRF_FIELD_NAME`` to configure the name of the CSRF token. (`#271`_) - ``validate_csrf`` raises ``wtforms.ValidationError`` with specific messages instead of returning ``True`` or ``False``. This breaks anything that was calling the method directly. (`#239`_, `#271`_) - CSRF errors are logged as well as raised. (`#239`_) - ``CsrfProtect`` is renamed to ``CSRFProtect``. A deprecation warning is issued when using the old name. ``CsrfError`` is renamed to ``CSRFError`` without deprecation. (`#271`_) - ``FileField`` is deprecated because it no longer provides functionality over the provided validators. Use ``wtforms.FileField`` directly. (`#272`_) .. _`#200`: https://github.com/lepture/flask-wtf/issues/200 .. _`#209`: https://github.com/lepture/flask-wtf/pull/209 .. _`#216`: https://github.com/lepture/flask-wtf/issues/216 .. _`#227`: https://github.com/lepture/flask-wtf/issues/227 .. _`#239`: https://github.com/lepture/flask-wtf/issues/239 .. _`#243`: https://github.com/lepture/flask-wtf/pull/243 .. _`#252`: https://github.com/lepture/flask-wtf/pull/252 .. _`#264`: https://github.com/lepture/flask-wtf/pull/264 .. _`#271`: https://github.com/lepture/flask-wtf/pull/271 .. _`#272`: https://github.com/lepture/flask-wtf/pull/272 Version 0.13.1 -------------- Released 2016/10/6 - Deprecation warning for ``Form`` is shown during ``__init__`` instead of immediately when subclassing. (`#262`_) - Don't use ``pkg_resources`` to get version, for compatibility with GAE. (`#261`_) .. _`#261`: https://github.com/lepture/flask-wtf/issues/261 .. _`#262`: https://github.com/lepture/flask-wtf/issues/262 Version 0.13 ------------ Released 2016/09/29 - ``Form`` is renamed to ``FlaskForm`` in order to avoid name collision with WTForms's base class. Using ``Form`` will show a deprecation warning. (`#250`_) - ``hidden_tag`` no longer wraps the hidden inputs in a hidden div. This is valid HTML5 and any modern HTML parser will behave correctly. (`#217`_, `#193`_) - ``flask_wtf.html5`` is deprecated. Import directly from ``wtforms.fields.html5``. (`#251`_) - ``is_submitted`` is true for ``PATCH`` and ``DELETE`` in addition to ``POST`` and ``PUT``. (`#187`_) - ``generate_csrf`` takes a ``token_key`` parameter to specify the key stored in the session. (`#206`_) - ``generate_csrf`` takes a ``url_safe`` parameter to allow the token to be used in URLs. (`#206`_) - ``form.data`` can be accessed multiple times without raising an exception. (`#248`_) - File extension with multiple parts (``.tar.gz``) can be used in the ``FileAllowed`` validator. (`#201`_) .. _`#187`: https://github.com/lepture/flask-wtf/pull/187 .. _`#193`: https://github.com/lepture/flask-wtf/issues/193 .. _`#201`: https://github.com/lepture/flask-wtf/issues/201 .. _`#206`: https://github.com/lepture/flask-wtf/pull/206 .. _`#217`: https://github.com/lepture/flask-wtf/issues/217 .. _`#248`: https://github.com/lepture/flask-wtf/pull/248 .. _`#250`: https://github.com/lepture/flask-wtf/pull/250 .. _`#251`: https://github.com/lepture/flask-wtf/pull/251 Version 0.12 ------------ Released 2015/07/09 - Abstract protect_csrf() into a separate method - Update reCAPTCHA configuration - Fix reCAPTCHA error handle Version 0.11 ------------ Released 2015/01/21 - Use the new reCAPTCHA API via `#164`_. .. _`#164`: https://github.com/lepture/flask-wtf/pull/164 Version 0.10.3 -------------- Released 2014/11/16 - Add configuration: WTF_CSRF_HEADERS via `#159`_. - Support customize hidden tags via `#150`_. - And many more bug fixes .. _`#150`: https://github.com/lepture/flask-wtf/pull/150 .. _`#159`: https://github.com/lepture/flask-wtf/pull/159 Version 0.10.2 -------------- Released 2014/09/03 - Update translation for reCaptcha via `#146`_. .. _`#146`: https://github.com/lepture/flask-wtf/pull/146 Version 0.10.1 -------------- Released 2014/08/26 - Update RECAPTCHA API SERVER URL via `#145`_. - Update requirement Werkzeug>=0.9.5 - Fix CsrfProtect exempt for blueprints via `#143`_. .. _`#145`: https://github.com/lepture/flask-wtf/pull/145 .. _`#143`: https://github.com/lepture/flask-wtf/pull/143 Version 0.10.0 -------------- Released 2014/07/16 - Add configuration: WTF_CSRF_METHODS - Support WTForms 2.0 now - Fix csrf validation without time limit (time_limit=False) - CSRF exempt supports blueprint `#111`_. .. _`#111`: https://github.com/lepture/flask-wtf/issues/111 Version 0.9.5 ------------- Released 2014/03/21 - ``csrf_token`` for all template types `#112`_. - Make FileRequired a subclass of InputRequired `#108`_. .. _`#108`: https://github.com/lepture/flask-wtf/pull/108 .. _`#112`: https://github.com/lepture/flask-wtf/pull/112 Version 0.9.4 ------------- Released 2013/12/20 - Bugfix for csrf module when form has a prefix - Compatible support for wtforms2 - Remove file API for FileField Version 0.9.3 ------------- Released 2013/10/02 - Fix validation of recaptcha when app in testing mode `#89`_. - Bugfix for csrf module `#91`_ .. _`#89`: https://github.com/lepture/flask-wtf/pull/89 .. _`#91`: https://github.com/lepture/flask-wtf/pull/91 Version 0.9.2 ------------- Released 2013/9/11 - Upgrade wtforms to 1.0.5. - No lazy string for i18n `#77`_. - No DateInput widget in html5 `#81`_. - PUT and PATCH for CSRF `#86`_. .. _`#77`: https://github.com/lepture/flask-wtf/issues/77 .. _`#81`: https://github.com/lepture/flask-wtf/issues/81 .. _`#86`: https://github.com/lepture/flask-wtf/issues/86 Version 0.9.1 ------------- Released 2013/8/21 This is a patch version for backward compitable for Flask<0.10 `#82`_. .. _`#82`: https://github.com/lepture/flask-wtf/issues/82 Version 0.9.0 ------------- Released 2013/8/15 - Add i18n support (issue #65) - Use default html5 widgets and fields provided by wtforms - Python 3.3+ support - Redesign form, replace SessionSecureForm - CSRF protection solution - Drop wtforms imports - Fix recaptcha i18n support - Fix recaptcha validator for python 3 - More test cases, it's 90%+ coverage now - Redesign documentation Version 0.8.4 ------------- Released 2013/3/28 - Recaptcha Validator now returns provided message (issue #66) - Minor doc fixes - Fixed issue with tests barking because of nose/multiprocessing issue. Version 0.8.3 ------------- Released 2013/3/13 - Update documentation to indicate pending deprecation of WTForms namespace facade - PEP8 fixes (issue #64) - Fix Recaptcha widget (issue #49) Version 0.8.2 and prior ----------------------- Initial development by Dan Jacob and Ron Duplain. 0.8.2 and prior there was not a change log. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/docs/conf.py0000644000175000017500000002353400000000000015734 0ustar00daviddavid00000000000000# -*- coding: utf-8 -*- import os import sys from datetime import datetime from pkg_resources import get_distribution # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.join(os.path.dirname(__file__), '_themes')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Flask-WTF' copyright = '2010 - {0}'.format(datetime.utcnow().year) author = 'Dan Jacob' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. dist = get_distribution('Flask-WTF') # The short X.Y version. version = '.'.join(dist.version.split('.', 2)[:2]) # The full version, including alpha/beta/rc tags. release = dist.version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_themes'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. # pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. try: __import__('flask_sphinx_themes') html_theme = 'flask' html_theme_options = { 'index_logo': 'flask-wtf.png', 'index_logo_height': '98px' } except ImportError: print('-' * 72) 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'] # The name for this set of Sphinx documents. # " v documentation" by default. # html_title = '{0} {1}'.format(project, version) # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, Smart Quotes will be used to convert quotes and dashes to # typographically correct entities. # smartquotes = False # Custom sidebar templates, maps document names to template names. # html_sidebars = { 'index': [ 'brand.html', 'useful-links.html', 'searchbox.html' ] } # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = False # If false, no index is generated. # html_use_index = False # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = False # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'Flask-WTFdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Flask-WTF.tex', 'Flask-WTF Documentation', author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'flask-wtf', 'Flask-WTF Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Flask-WTF', 'Flask-WTF Documentation', author, 'Flask-WTF', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/3/', None), 'flask': ('http://flask.pocoo.org/docs/latest/', None), 'werkzeug': ('http://werkzeug.pocoo.org/docs/latest/', None), 'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None), 'flask_uploads': ('https://pythonhosted.org/Flask-Uploads', None), } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/docs/config.rst0000644000175000017500000000456100000000000016433 0ustar00daviddavid00000000000000Configuration ============= ========================== ===================================================== ``WTF_CSRF_ENABLED`` Set to ``False`` to disable all CSRF protection. ``WTF_CSRF_CHECK_DEFAULT`` When using the CSRF protection extension, this controls whether every view is protected by default. Default is ``True``. ``WTF_CSRF_SECRET_KEY`` Random data for generating secure tokens. If this is not set then ``SECRET_KEY`` is used. ``WTF_CSRF_METHODS`` HTTP methods to protect from CSRF. Default is ``{'POST', 'PUT', 'PATCH', 'DELETE'}``. ``WTF_CSRF_FIELD_NAME`` Name of the form field and session key that holds the CSRF token. ``WTF_CSRF_HEADERS`` HTTP headers to search for CSRF token when it is not provided in the form. Default is ``['X-CSRFToken', 'X-CSRF-Token']``. ``WTF_CSRF_TIME_LIMIT`` Max age in seconds for CSRF tokens. Default is ``3600``. If set to ``None``, the CSRF token is valid for the life of the session. ``WTF_CSRF_SSL_STRICT`` Whether to enforce the same origin policy by checking that the referrer matches the host. Only applies to HTTPS requests. Default is ``True``. ``WTF_I18N_ENABLED`` Set to ``False`` to disable Flask-Babel I18N support. ========================== ===================================================== Recaptcha --------- ========================= ============================================== ``RECAPTCHA_PUBLIC_KEY`` **required** A public key. ``RECAPTCHA_PRIVATE_KEY`` **required** A private key. 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 ------- CSRF errors are logged at the ``INFO`` level to the ``flask_wtf.csrf`` logger. You still need to configure logging in your application in order to see these messages. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030929.0 Flask-WTF-0.14.3/docs/csrf.rst0000644000175000017500000000615700000000000016126 0ustar00daviddavid00000000000000.. currentmodule:: flask_wtf.csrf .. _csrf: CSRF Protection =============== Any view using :class:`~flask_wtf.FlaskForm` to process the request is already getting CSRF protection. If you have views that don't use ``FlaskForm`` or make AJAX requests, use the provided CSRF extension to protect those requests as well. Setup ----- To enable CSRF protection globally for a Flask app, register the :class:`CSRFProtect` extension. :: from flask_wtf.csrf import CSRFProtect csrf = CSRFProtect(app) Like other Flask extensions, you can apply it lazily:: csrf = CSRFProtect() def create_app(): app = Flask(__name__) csrf.init_app(app) .. note:: CSRF protection requires a secret key to securely sign the token. By default this will use the Flask app's ``SECRET_KEY``. If you'd like to use a separate token you can set ``WTF_CSRF_SECRET_KEY``. HTML Forms ---------- When using a ``FlaskForm``, render the form's CSRF field like normal. .. sourcecode:: html+jinja
{{ form.csrf_token }}
If the template doesn't use a ``FlaskForm``, render a hidden input with the token in the form. .. sourcecode:: html+jinja
JavaScript Requests ------------------- When sending an AJAX request, add the ``X-CSRFToken`` header to it. For example, in jQuery you can configure all requests to send the token. .. sourcecode:: html+jinja Customize the error response ---------------------------- When CSRF validation fails, it will raise a :class:`CSRFError`. By default this returns a response with the failure reason and a 400 code. You can customize the error response using Flask's :meth:`~flask.Flask.errorhandler`. :: from flask_wtf.csrf import CSRFError @app.errorhandler(CSRFError) def handle_csrf_error(e): return render_template('csrf_error.html', reason=e.description), 400 Exclude views from protection ----------------------------- We strongly suggest that you protect all your views with CSRF. But if needed, you can exclude some views using a decorator. :: @app.route('/foo', methods=('GET', 'POST')) @csrf.exempt def my_handler(): # ... return 'ok' You can exclude all the views of a blueprint. :: csrf.exempt(account_blueprint) You can disable CSRF protection in all views by default, by setting ``WTF_CSRF_CHECK_DEFAULT`` to ``False``, and selectively call :meth:`~flask_wtf.csrf.CSRFProtect.protect` only when you need. This also enables you to do some pre-processing on the requests before checking for the CSRF token. :: @app.before_request def check_csrf(): if not is_oauth(request): csrf.protect() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581031026.0 Flask-WTF-0.14.3/docs/form.rst0000644000175000017500000001114700000000000016127 0ustar00daviddavid00000000000000Creating Forms ============== Secure Form ----------- .. 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(meta={'csrf': False}) You can disable it globally—though you really shouldn't—with the configuration:: WTF_CSRF_ENABLED = False In order to generate the csrf token, you must have a secret key, this is usually the same as your Flask app secret key. If you want to use another secret key, config it:: WTF_CSRF_SECRET_KEY = 'a random string' File Uploads ------------ .. 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 :class:`~werkzeug.datastructures.FileStorage`, otherwise ``data`` will be ``None``. :: from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileRequired from werkzeug.utils import secure_filename class PhotoForm(FlaskForm): photo = FileField(validators=[FileRequired()]) @app.route('/upload', methods=['GET', 'POST']) def upload(): form = PhotoForm() if form.validate_on_submit(): f = form.photo.data filename = secure_filename(f.filename) f.save(os.path.join( app.instance_path, 'photos', filename )) return redirect(url_for('index')) return render_template('upload.html', form=form) Remember to set the ``enctype`` of the HTML form to ``multipart/form-data``, otherwise ``request.files`` will be empty. .. sourcecode:: html
...
Flask-WTF handles passing form data to the form for you. If you pass in the data explicitly, remember that ``request.form`` must be combined with ``request.files`` for the form to see the file data. :: form = PhotoForm() # is equivalent to: from flask import request from werkzeug.datastructures import CombinedMultiDict form = PhotoForm(CombinedMultiDict((request.files, request.form))) Validation ~~~~~~~~~~ Flask-WTF supports validating file uploads with :class:`FileRequired` and :class:`FileAllowed`. They can be used with both Flask-WTF's and WTForms's ``FileField`` classes. :class:`FileAllowed` works well with Flask-Uploads. :: from flask_uploads import UploadSet, IMAGES from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed, FileRequired images = UploadSet('images', IMAGES) class UploadForm(FlaskForm): upload = FileField('image', validators=[ FileRequired(), FileAllowed(images, 'Images only!') ]) It can be used without Flask-Uploads by passing the extensions directly. :: class UploadForm(FlaskForm): upload = FileField('image', validators=[ FileRequired(), FileAllowed(['jpg', 'png'], 'Images only!') ]) .. _recaptcha: Recaptcha --------- .. currentmodule:: flask_wtf.recaptcha Flask-WTF also provides Recaptcha support through a :class:`RecaptchaField`:: from flask_wtf import FlaskForm, RecaptchaField from wtforms import TextField class SignupForm(FlaskForm): username = TextField('Username') recaptcha = RecaptchaField() This comes together with a number of configuration, which you have to implement them. ======================= ============================================== RECAPTCHA_PUBLIC_KEY **required** A public key. RECAPTCHA_PRIVATE_KEY **required** A private key. RECAPTCHA_API_SERVER **optional** Specify your Recaptcha API server. RECAPTCHA_PARAMETERS **optional** A dict of JavaScript (api.js) parameters. RECAPTCHA_DATA_ATTRS **optional** A dict of data attributes options. https://developers.google.com/recaptcha/docs/display ======================= ============================================== Example of RECAPTCHA_PARAMETERS, and RECAPTCHA_DATA_ATTRS:: RECAPTCHA_PARAMETERS = {'hl': 'zh', 'render': 'explicit'} RECAPTCHA_DATA_ATTRS = {'theme': 'dark'} For testing your application, if ``app.testing`` is ``True``, recaptcha field will always be valid for you convenience. And it can be easily setup in the templates: .. sourcecode:: html+jinja
{{ form.username }} {{ form.recaptcha }}
We have an example for you: `recaptcha@github`_. .. _`recaptcha@github`: https://github.com/lepture/flask-wtf/tree/master/examples/recaptcha ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/docs/index.rst0000644000175000017500000000207500000000000016273 0ustar00daviddavid00000000000000Flask-WTF ========= Simple integration of `Flask`_ and `WTForms`_, including CSRF, file upload, and reCAPTCHA. .. _Flask: https://www.palletsprojects.com/p/flask .. _WTForms: https://wtforms.readthedocs.io/en/latest/ Features -------- * Integration with WTForms. * Secure Form with CSRF token. * Global CSRF protection. * reCAPTCHA support. * File upload that works with Flask-Uploads. * Internationalization using Flask-Babel. User's Guide ------------ This part of the documentation, which is mostly prose, begins with some background information about Flask-WTF, then focuses on step-by-step instructions for getting the most out of Flask-WTF. .. toctree:: :maxdepth: 2 install quickstart form csrf config API Documentation ----------------- If you are looking for information on a specific function, class or method, this part of the documentation is for you. .. toctree:: :maxdepth: 2 api Additional Notes ---------------- Legal information and changelog are here. .. toctree:: :maxdepth: 2 upgrade changelog authors license ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/docs/install.rst0000644000175000017500000000135700000000000016634 0ustar00daviddavid00000000000000Installation ============ The `Python Packaging Guide`_ contains general information about how to manage your project and dependencies. .. _Python Packaging Guide: https://packaging.python.org/current/ Released version ---------------- Install or upgrade using pip. :: pip install -U Flask-WTF Development ----------- The latest code is available from `GitHub`_. Clone the repository then install using pip. :: git clone https://github.com/lepture/flask-wtf pip install -e ./flask-wtf Or install the latest build from an `archive`_. :: pip install -U https://github.com/lepture/flask-wtf/tarball/master .. _GitHub: https://github.com/lepture/flask-wtf .. _archive: https://github.com/lepture/flask-wtf/archive/master.tar.gz ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/license.rst0000644000175000017500000000006100000000000016577 0ustar00daviddavid00000000000000BSD License =========== .. include:: ../LICENSE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/make.bat0000644000175000017500000001707200000000000016042 0ustar00daviddavid00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-WTF.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-WTF.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/docs/quickstart.rst0000644000175000017500000000346700000000000017364 0ustar00daviddavid00000000000000Quickstart ========== Eager to get started? This page gives a good introduction to Flask-WTF. It assumes you already have Flask-WTF installed. If you do not, head over to the :doc:`install` section. Creating Forms -------------- Flask-WTF provides your Flask application integration with WTForms. For example:: from flask_wtf import FlaskForm from wtforms import StringField from wtforms.validators import DataRequired class MyForm(FlaskForm): name = StringField('name', validators=[DataRequired()]) .. note:: From version 0.9.0, Flask-WTF will not import anything from wtforms, you need to import fields from wtforms. In addition, a CSRF token hidden field is created automatically. You can render this in your template: .. sourcecode:: html+jinja
{{ form.csrf_token }} {{ form.name.label }} {{ form.name(size=20) }}
If your form has multiple hidden fields, you can render them in one block using :meth:`~flask_wtf.FlaskForm.hidden_tag`. .. sourcecode:: html+jinja
{{ form.hidden_tag() }} {{ form.name.label }} {{ form.name(size=20) }}
Validating Forms ---------------- Validating the request in your view handlers:: @app.route('/submit', methods=['GET', 'POST']) def submit(): form = MyForm() if form.validate_on_submit(): return redirect('/success') return render_template('submit.html', form=form) Note that you don't have to pass ``request.form`` to Flask-WTF; it will load automatically. And the convenience ``validate_on_submit`` will check if it is a POST request and if it is valid. Heading over to :doc:`form` to learn more skills. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/docs/upgrade.rst0000644000175000017500000000252600000000000016614 0ustar00daviddavid00000000000000Upgrading to Newer Releases =========================== Flask-WTF itself is changing like any software is changing over time. Most of the changes are the nice kind, the kind where you don't have to change anything in your code to profit from a new release. However every once in a while there are changes that do require some changes in your code or there are changes that make it possible for you to improve your own code quality by taking advantage of new features in Flask-WTF. This section of the documentation enumerates all the changes in Flask-WTF from release to release and how you can change your code to have a painless updating experience. If you want to use the easy_install command to upgrade your Flask-WTF installation, make sure to pass it the -U parameter:: $ pip install -U Flask-WTF Version 0.9.0 ------------- Dropping the imports of wtforms is a big change, it may be lots of pain for you, but the imports are hard to maintain. Instead of importing ``Fields`` from Flask-WTF, you need to import them from the original wtforms:: from wtforms import TextField Configuration name of ``CSRF_ENABLED`` is changed to ``WTF_CSRF_ENABLED``. There is a chance that you don't need to do anything if you haven't set any configuration. This version has many more features, if you don't need them, they will not break any code of yours. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/flask_wtf/0000755000175000017500000000000000000000000015456 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030484.0 Flask-WTF-0.14.3/flask_wtf/__init__.py0000644000175000017500000000024600000000000017571 0ustar00daviddavid00000000000000from __future__ import absolute_import from .csrf import CSRFProtect, CsrfProtect from .form import FlaskForm, Form from .recaptcha import * __version__ = '0.14.3' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030394.0 Flask-WTF-0.14.3/flask_wtf/_compat.py0000644000175000017500000000162500000000000017456 0ustar00daviddavid00000000000000import sys import warnings PY2 = sys.version_info[0] == 2 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 def to_bytes(text): """Transform string to bytes.""" if isinstance(text, text_type): text = text.encode('utf-8') return text def to_unicode(input_bytes, encoding='utf-8'): """Decodes input_bytes to text if needed.""" if not isinstance(input_bytes, string_types): input_bytes = input_bytes.decode(encoding) return input_bytes class FlaskWTFDeprecationWarning(DeprecationWarning): pass warnings.simplefilter('always', FlaskWTFDeprecationWarning) warnings.filterwarnings( 'ignore', category=FlaskWTFDeprecationWarning, module='wtforms|flask_wtf' ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/flask_wtf/csrf.py0000644000175000017500000002701000000000000016765 0ustar00daviddavid00000000000000import hashlib import logging import os import warnings from functools import wraps from flask import Blueprint, current_app, g, request, session from itsdangerous import BadData, SignatureExpired, URLSafeTimedSerializer from werkzeug.exceptions import BadRequest from werkzeug.security import safe_str_cmp from wtforms import ValidationError from wtforms.csrf.core import CSRF from ._compat import FlaskWTFDeprecationWarning, string_types, urlparse __all__ = ('generate_csrf', 'validate_csrf', 'CSRFProtect') logger = logging.getLogger(__name__) def generate_csrf(secret_key=None, token_key=None): """Generate a CSRF token. The token is cached for a request, so multiple calls to this function will generate the same token. During testing, it might be useful to access the signed token in ``g.csrf_token`` and the raw token in ``session['csrf_token']``. :param secret_key: Used to securely sign the token. Default is ``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``. :param token_key: Key where token is stored in session for comparision. Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``. """ secret_key = _get_config( secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key, message='A secret key is required to use CSRF.' ) field_name = _get_config( token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token', message='A field name is required to use CSRF.' ) 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() 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) def validate_csrf(data, secret_key=None, time_limit=None, token_key=None): """Check if the given data is a valid CSRF token. This compares the given signed token to the one stored in the session. :param data: The signed CSRF token to be checked. :param secret_key: Used to securely sign the token. Default is ``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``. :param time_limit: Number of seconds that the token is valid. Default is ``WTF_CSRF_TIME_LIMIT`` or 3600 seconds (60 minutes). :param token_key: Key where token is stored in session for comparision. Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``. :raises ValidationError: Contains the reason that validation failed. .. versionchanged:: 0.14 Raises ``ValidationError`` with a specific error message rather than returning ``True`` or ``False``. """ secret_key = _get_config( secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key, message='A secret key is required to use CSRF.' ) field_name = _get_config( token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token', message='A field name is required to use CSRF.' ) time_limit = _get_config( time_limit, 'WTF_CSRF_TIME_LIMIT', 3600, required=False ) if not data: raise ValidationError('The CSRF token is missing.') if field_name not in session: raise ValidationError('The CSRF session token is missing.') s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token') try: token = s.loads(data, max_age=time_limit) except SignatureExpired: raise ValidationError('The CSRF token has expired.') except BadData: raise ValidationError('The CSRF token is invalid.') if not safe_str_cmp(session[field_name], token): raise ValidationError('The CSRF tokens do not match.') def _get_config( value, config_name, default=None, required=True, message='CSRF is not configured.' ): """Find config value based on provided value, Flask config, and default value. :param value: already provided config value :param config_name: Flask ``config`` key :param default: default value if not provided or configured :param required: whether the value must not be ``None`` :param message: error message if required config is not found :raises KeyError: if required config is not found """ if value is None: value = current_app.config.get(config_name, default) if required and value is None: raise RuntimeError(message) return value class _FlaskFormCSRF(CSRF): def setup_form(self, form): self.meta = form.meta return super(_FlaskFormCSRF, self).setup_form(form) def generate_csrf_token(self, csrf_token_field): return generate_csrf( secret_key=self.meta.csrf_secret, token_key=self.meta.csrf_field_name ) def validate_csrf_token(self, form, field): if g.get('csrf_valid', False): # already validated by CSRFProtect return try: validate_csrf( field.data, self.meta.csrf_secret, self.meta.csrf_time_limit, self.meta.csrf_field_name ) except ValidationError as e: logger.info(e.args[0]) raise class CSRFProtect(object): """Enable CSRF protection globally for a Flask app. :: app = Flask(__name__) 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 ``{{ csrf_token() }}``. See the :ref:`csrf` documentation. """ def __init__(self, app=None): self._exempt_views = set() self._exempt_blueprints = set() if app: self.init_app(app) def init_app(self, app): app.extensions['csrf'] = self app.config.setdefault('WTF_CSRF_ENABLED', True) app.config.setdefault('WTF_CSRF_CHECK_DEFAULT', True) app.config['WTF_CSRF_METHODS'] = set(app.config.get( 'WTF_CSRF_METHODS', ['POST', 'PUT', 'PATCH', 'DELETE'] )) app.config.setdefault('WTF_CSRF_FIELD_NAME', 'csrf_token') app.config.setdefault( 'WTF_CSRF_HEADERS', ['X-CSRFToken', 'X-CSRF-Token'] ) app.config.setdefault('WTF_CSRF_TIME_LIMIT', 3600) app.config.setdefault('WTF_CSRF_SSL_STRICT', True) app.jinja_env.globals['csrf_token'] = generate_csrf app.context_processor(lambda: {'csrf_token': generate_csrf}) @app.before_request def csrf_protect(): if not app.config['WTF_CSRF_ENABLED']: return if not app.config['WTF_CSRF_CHECK_DEFAULT']: return if request.method not in app.config['WTF_CSRF_METHODS']: return if not request.endpoint: return if request.blueprint in self._exempt_blueprints: return view = app.view_functions.get(request.endpoint) dest = '{0}.{1}'.format(view.__module__, view.__name__) if dest in self._exempt_views: return self.protect() def _get_csrf_token(self): # 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] 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) if csrf_token: return csrf_token return None def protect(self): if request.method not in current_app.config['WTF_CSRF_METHODS']: return try: validate_csrf(self._get_csrf_token()) except ValidationError as e: logger.info(e.args[0]) self._error_response(e.args[0]) if request.is_secure and current_app.config['WTF_CSRF_SSL_STRICT']: if not request.referrer: self._error_response('The referrer header is missing.') good_referrer = 'https://{0}/'.format(request.host) if not same_origin(request.referrer, good_referrer): self._error_response('The referrer does not match the host.') g.csrf_valid = True # mark this request as CSRF valid def exempt(self, view): """Mark a view or blueprint to be excluded from CSRF protection. :: @app.route('/some-view', methods=['POST']) @csrf.exempt def some_view(): ... :: bp = Blueprint(...) csrf.exempt(bp) """ if isinstance(view, Blueprint): self._exempt_blueprints.add(view.name) return view if isinstance(view, string_types): view_location = view else: view_location = '.'.join((view.__module__, view.__name__)) self._exempt_views.add(view_location) return view def _error_response(self, reason): raise CSRFError(reason) def error_handler(self, view): """Register a function that will generate the response for CSRF errors. .. deprecated:: 0.14 Use the standard Flask error system with ``@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`. :: @csrf.error_handler def csrf_error(reason): return render_template('error.html', reason=reason) Due to historical reasons, the function may either return a response or raise an exception with :func:`flask.abort`. """ 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.' ), stacklevel=2) @wraps(view) def handler(reason): response = current_app.make_response(view(reason)) raise CSRFError(response=response) self._error_response = handler return view class CsrfProtect(CSRFProtect): """ .. deprecated:: 0.14 Renamed to :class:`~flask_wtf.csrf.CSRFProtect`. """ def __init__(self, app=None): warnings.warn(FlaskWTFDeprecationWarning( '"flask_wtf.CsrfProtect" has been renamed to "CSRFProtect" ' 'and will be removed in 1.0.' ), stacklevel=2) super(CsrfProtect, self).__init__(app=app) class CSRFError(BadRequest): """Raise if the client sends invalid CSRF data with the request. Generates a 400 Bad Request response with the failure reason by default. Customize the response by registering a handler with :meth:`flask.Flask.errorhandler`. """ description = 'CSRF validation failed.' def same_origin(current_uri, compare_uri): current = urlparse(current_uri) compare = urlparse(compare_uri) return ( current.scheme == compare.scheme and current.hostname == compare.hostname and current.port == compare.port ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030394.0 Flask-WTF-0.14.3/flask_wtf/file.py0000644000175000017500000000550600000000000016755 0ustar00daviddavid00000000000000import warnings 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 class FileField(_FileField): """Werkzeug-aware subclass of :class:`wtforms.fields.FileField`.""" def process_formdata(self, valuelist): valuelist = (x for x in valuelist if isinstance(x, FileStorage) and x) data = next(valuelist, None) if data is not None: self.data = data else: self.raw_data = () def has_file(self): """Return ``True`` if ``self.data`` is a :class:`~werkzeug.datastructures.FileStorage` object. .. deprecated:: 0.14.1 ``data`` is no longer set if the input is not a non-empty ``FileStorage``. Check ``form.data is not None`` instead. """ warnings.warn(FlaskWTFDeprecationWarning( '"has_file" is deprecated and will be removed in 1.0. The data is ' 'checked during processing instead.' )) return bool(self.data) class FileRequired(DataRequired): """Validates that the data is a Werkzeug :class:`~werkzeug.datastructures.FileStorage` object. :param message: error message You can also use the synonym ``file_required``. """ def __call__(self, form, field): if not (isinstance(field.data, FileStorage) and field.data): raise StopValidation(self.message or field.gettext( 'This field is required.' )) file_required = FileRequired class FileAllowed(object): """Validates that the uploaded file is allowed by a given list of extensions or a Flask-Uploads :class:`~flaskext.uploads.UploadSet`. :param upload_set: A list of extensions or an :class:`~flaskext.uploads.UploadSet` :param message: error message You can also use the synonym ``file_allowed``. """ def __init__(self, upload_set, message=None): self.upload_set = upload_set self.message = message def __call__(self, form, field): if not (isinstance(field.data, FileStorage) and field.data): return filename = field.data.filename.lower() if isinstance(self.upload_set, abc.Iterable): if any(filename.endswith('.' + x) for x in self.upload_set): return raise StopValidation(self.message or field.gettext( 'File does not have an approved extension: {extensions}' ).format(extensions=', '.join(self.upload_set))) if not self.upload_set.file_allowed(field.data, filename): raise StopValidation(self.message or field.gettext( 'File does not have an approved extension.' )) file_allowed = FileAllowed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/flask_wtf/form.py0000644000175000017500000001157300000000000017002 0ustar00daviddavid00000000000000import warnings from flask import current_app, request, session from jinja2 import Markup from werkzeug.datastructures import CombinedMultiDict, ImmutableMultiDict from werkzeug.utils import cached_property from wtforms import Form from wtforms.meta import DefaultMeta from wtforms.widgets import HiddenInput from ._compat import FlaskWTFDeprecationWarning, string_types, text_type from .csrf import _FlaskFormCSRF try: from .i18n import translations except ImportError: translations = None # babel not installed SUBMIT_METHODS = set(('POST', 'PUT', 'PATCH', 'DELETE')) _Auto = object() class FlaskForm(Form): """Flask-specific subclass of WTForms :class:`~wtforms.form.Form`. If ``formdata`` is not specified, this will use :attr:`flask.request.form` and :attr:`flask.request.files`. Explicitly pass ``formdata=None`` to prevent this. """ class Meta(DefaultMeta): csrf_class = _FlaskFormCSRF csrf_context = session # not used, provided for custom csrf_class @cached_property def csrf(self): return current_app.config.get('WTF_CSRF_ENABLED', True) @cached_property def csrf_secret(self): return current_app.config.get( 'WTF_CSRF_SECRET_KEY', current_app.secret_key ) @cached_property def csrf_field_name(self): return current_app.config.get('WTF_CSRF_FIELD_NAME', 'csrf_token') @cached_property def csrf_time_limit(self): return current_app.config.get('WTF_CSRF_TIME_LIMIT', 3600) def wrap_formdata(self, form, formdata): if formdata is _Auto: if _is_submitted(): if request.files: return CombinedMultiDict(( request.files, request.form )) elif request.form: return request.form elif request.get_json(): return ImmutableMultiDict(request.get_json()) return None return formdata def get_translations(self, form): if not current_app.config.get('WTF_I18N_ENABLED', True): return super(FlaskForm.Meta, self).get_translations(form) return translations def __init__(self, formdata=_Auto, **kwargs): csrf_enabled = kwargs.pop('csrf_enabled', None) if csrf_enabled is not None: warnings.warn(FlaskWTFDeprecationWarning( '"csrf_enabled" is deprecated and will be removed in 1.0. ' "Pass meta={'csrf': False} instead." ), stacklevel=3) kwargs['meta'] = kwargs.get('meta') or {} kwargs['meta'].setdefault('csrf', csrf_enabled) super(FlaskForm, self).__init__(formdata=formdata, **kwargs) def is_submitted(self): """Consider the form submitted if there is an active request and the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. """ return _is_submitted() def validate_on_submit(self): """Call :meth:`validate` only if the form is submitted. This is a shortcut for ``form.is_submitted() and form.validate()``. """ return self.is_submitted() and self.validate() def hidden_tag(self, *fields): """Render the form's hidden fields in one call. A field is considered hidden if it uses the :class:`~wtforms.widgets.HiddenInput` widget. If ``fields`` are given, only render the given fields that are hidden. If a string is passed, render the field with that name if it exists. .. versionchanged:: 0.13 No longer wraps inputs in hidden div. This is valid HTML 5. .. versionchanged:: 0.13 Skip passed fields that aren't hidden. Skip passed names that don't exist. """ def hidden_fields(fields): for f in fields: if isinstance(f, string_types): f = getattr(self, f, None) if f is None or not isinstance(f.widget, HiddenInput): continue yield f return Markup( u'\n'.join(text_type(f) for f in hidden_fields(fields or self)) ) def _is_submitted(): """Consider the form submitted if there is an active request and the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. """ return bool(request) and request.method in SUBMIT_METHODS class Form(FlaskForm): """ .. deprecated:: 0.13 Renamed to :class:`~flask_wtf.FlaskForm`. """ def __init__(self, *args, **kwargs): warnings.warn(FlaskWTFDeprecationWarning( '"flask_wtf.Form" has been renamed to "FlaskForm" ' 'and will be removed in 1.0.' ), stacklevel=3) super(Form, self).__init__(*args, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/flask_wtf/html5.py0000644000175000017500000000051500000000000017062 0ustar00daviddavid00000000000000import warnings from ._compat import FlaskWTFDeprecationWarning warnings.warn(FlaskWTFDeprecationWarning( '"flask_wtf.html5" will be removed in 1.0. ' 'Import directly from "wtforms.fields.html5" ' 'and "wtforms.widgets.html5".' ), stacklevel=2) from wtforms.widgets.html5 import * from wtforms.fields.html5 import * ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/flask_wtf/i18n.py0000644000175000017500000000231400000000000016607 0ustar00daviddavid00000000000000from 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 __all__ = ('Translations', 'translations') def _get_translations(): """Returns the correct gettext translations. Copy from flask-babel with some modifications. """ if not request: return None # babel should be in extensions for get_locale if 'babel' not in current_app.extensions: return None translations = getattr(request, 'wtforms_translations', None) if translations is None: translations = support.Translations.load( messages_path(), [get_locale()], domain='wtforms' ) request.wtforms_translations = translations return translations class Translations(object): def gettext(self, string): t = _get_translations() return string if t is None else t.ugettext(string) def ngettext(self, singular, plural, n): t = _get_translations() if t is None: return singular if n == 1 else plural return t.ungettext(singular, plural, n) translations = Translations() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/flask_wtf/recaptcha/0000755000175000017500000000000000000000000017410 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/flask_wtf/recaptcha/__init__.py0000644000175000017500000000012600000000000021520 0ustar00daviddavid00000000000000# flake8: noqa from .fields import * from .validators import * from .widgets import * ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1541947388.0 Flask-WTF-0.14.3/flask_wtf/recaptcha/fields.py0000644000175000017500000000070500000000000021232 0ustar00daviddavid00000000000000from wtforms.fields import Field from . import widgets from .validators import Recaptcha __all__ = ["RecaptchaField"] class RecaptchaField(Field): widget = widgets.RecaptchaWidget() # error message if recaptcha validation fails recaptcha_error = None def __init__(self, label='', validators=None, **kwargs): validators = validators or [Recaptcha()] super(RecaptchaField, self).__init__(label, validators, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/flask_wtf/recaptcha/validators.py0000644000175000017500000000454500000000000022142 0ustar00daviddavid00000000000000try: import urllib2 as http except ImportError: # Python 3 from urllib import request as http import json from flask import current_app, request from werkzeug.urls import url_encode from wtforms import ValidationError from .._compat import to_bytes, to_unicode RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify' RECAPTCHA_ERROR_CODES = { 'missing-input-secret': 'The secret parameter is missing.', 'invalid-input-secret': 'The secret parameter is invalid or malformed.', 'missing-input-response': 'The response parameter is missing.', 'invalid-input-response': 'The response parameter is invalid or malformed.' } __all__ = ["Recaptcha"] class Recaptcha(object): """Validates a ReCaptcha.""" def __init__(self, message=None): if message is None: message = RECAPTCHA_ERROR_CODES['missing-input-response'] self.message = message def __call__(self, form, field): if current_app.testing: return True if request.json: response = request.json.get('g-recaptcha-response', '') else: response = request.form.get('g-recaptcha-response', '') remote_ip = request.remote_addr if not response: raise ValidationError(field.gettext(self.message)) if not self._validate_recaptcha(response, remote_ip): field.recaptcha_error = 'incorrect-captcha-sol' raise ValidationError(field.gettext(self.message)) def _validate_recaptcha(self, response, remote_addr): """Performs the actual validation.""" try: private_key = current_app.config['RECAPTCHA_PRIVATE_KEY'] except KeyError: raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set") data = url_encode({ 'secret': private_key, 'remoteip': remote_addr, 'response': response }) http_response = http.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data)) if http_response.code != 200: return False json_resp = json.loads(to_unicode(http_response.read())) if json_resp["success"]: return True for error in json_resp.get("error-codes", []): if error in RECAPTCHA_ERROR_CODES: raise ValidationError(RECAPTCHA_ERROR_CODES[error]) return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/flask_wtf/recaptcha/widgets.py0000644000175000017500000000234700000000000021436 0ustar00daviddavid00000000000000# -*- coding: utf-8 -*- 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'] class RecaptchaWidget(object): def recaptcha_html(self, public_key): html = current_app.config.get('RECAPTCHA_HTML') if html: return Markup(html) params = current_app.config.get('RECAPTCHA_PARAMETERS') script = RECAPTCHA_SCRIPT if params: script += u'?' + url_encode(params) attrs = current_app.config.get('RECAPTCHA_DATA_ATTRS', {}) attrs['sitekey'] = public_key snippet = u' '.join([u'data-%s="%s"' % (k, attrs[k]) for k in attrs]) return Markup(RECAPTCHA_TEMPLATE % (script, snippet)) def __call__(self, field, error=None, **kwargs): """Returns the recaptcha input HTML.""" try: public_key = current_app.config['RECAPTCHA_PUBLIC_KEY'] except KeyError: raise RuntimeError('RECAPTCHA_PUBLIC_KEY config not set') return self.recaptcha_html(public_key) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/setup.cfg0000644000175000017500000000030300000000000015313 0ustar00daviddavid00000000000000[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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030799.0 Flask-WTF-0.14.3/setup.py0000755000175000017500000000314200000000000015213 0ustar00daviddavid00000000000000#!/usr/bin/env python from setuptools import find_packages, setup with open('README.rst') as f: readme = f.read() setup( name='Flask-WTF', version='0.14.3', url='https://github.com/lepture/flask-wtf', license='BSD', author='Dan Jacob', author_email='danjac354@gmail.com', maintainer='Hsiaoming Yang', maintainer_email='me@lepture.com', description='Simple integration of Flask and WTForms.', long_description=readme, packages=find_packages(exclude=('tests',)), zip_safe=False, platforms='any', install_requires=[ 'Flask', 'WTForms', 'itsdangerous', ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Flask', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 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', 'Topic :: Software Development :: Libraries :: Python Modules' ] ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581031083.0168543 Flask-WTF-0.14.3/tests/0000755000175000017500000000000000000000000014640 5ustar00daviddavid00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/conftest.py0000644000175000017500000000110300000000000017032 0ustar00daviddavid00000000000000import 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_csrf_extension.py0000644000175000017500000001300500000000000021301 0ustar00daviddavid00000000000000import 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_csrf_form.py0000644000175000017500000000523700000000000020240 0ustar00daviddavid00000000000000import 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.' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_file.py0000644000175000017500000000476100000000000017200 0ustar00daviddavid00000000000000import 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_form.py0000644000175000017500000001002100000000000017206 0ustar00daviddavid00000000000000from 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': ' '}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_html5.py0000644000175000017500000000034500000000000017304 0ustar00daviddavid00000000000000from 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_i18n.py0000644000175000017500000000406200000000000017032 0ustar00daviddavid00000000000000# coding=utf8 import pytest from flask import request from wtforms import StringField from wtforms.validators import DataRequired, Length from flask_wtf import FlaskForm class NameForm(FlaskForm): class Meta: csrf = False 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.' client.post( '/', headers={'Accept-Language': 'zh-CN,zh;q=0.8'} ) 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030213.0 Flask-WTF-0.14.3/tests/test_recaptcha.py0000644000175000017500000000755000000000000020212 0ustar00daviddavid00000000000000import 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 flask_wtf.recaptcha.validators import Recaptcha, http class RecaptchaForm(FlaskForm): class Meta: csrf = False recaptcha = RecaptchaField() @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 [] }) self.read_bytes = read_bytes def read(self): if self.read_bytes: return to_bytes(self.data) 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) monkeypatch.setattr(http, 'urlopen', mock_urlopen) f = RecaptchaForm() f.recaptcha.validators = [Recaptcha('custom')] f.validate() assert f.recaptcha.errors[0] == 'custom' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581030799.0 Flask-WTF-0.14.3/tox.ini0000644000175000017500000000175000000000000015014 0ustar00daviddavid00000000000000[tox] envlist = py{27,34,35,36,37,38} pypy py-babelex py-no-babel docs-html coverage-report [testenv] deps = coverage pytest>=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