flask/flask-docs/patterns/templateinheritance.html
2025-04-11 03:04:22 +00:00

214 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en" data-content_root="../">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Form Validation with WTForms &#8212; Flask Documentation (3.2.x)</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=6625fa76" />
<link rel="stylesheet" type="text/css" href="../_static/flask.css?v=b87c8d14" />
<script src="../_static/documentation_options.js?v=56528222"></script>
<script src="../_static/doctools.js?v=9bcbadda"></script>
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
<script data-project="flask" data-version="3.2.x" src="../_static/describe_version.js?v=fa7f30d0"></script>
<link rel="icon" href="../_static/shortcut-icon.png"/>
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Template Inheritance" href="templateinheritance.html" />
<link rel="prev" title="View Decorators" href="viewdecorators.html" />
</head><body>
<div class="related" role="navigation" aria-label="Related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="templateinheritance.html" title="Template Inheritance"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="viewdecorators.html" title="View Decorators"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> &#187;</li>
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Patterns for Flask</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Form Validation with WTForms</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<section id="form-validation-with-wtforms">
<h1>Form Validation with WTForms<a class="headerlink" href="#form-validation-with-wtforms" title="Link to this heading"></a></h1>
<p>When you have to work with form data submitted by a browser view, code
quickly becomes very hard to read. There are libraries out there designed
to make this process easier to manage. One of them is <a class="reference external" href="https://wtforms.readthedocs.io/">WTForms</a> which we
will handle here. If you find yourself in the situation of having many
forms, you might want to give it a try.</p>
<p>When you are working with WTForms you have to define your forms as classes
first. I recommend breaking up the application into multiple modules
(<a class="reference internal" href="packages.html"><span class="doc">Large Applications as Packages</span></a>) for that and adding a separate module for the
forms.</p>
<div class="admonition-getting-the-most-out-of-wtforms-with-an-extension admonition">
<p class="admonition-title">Getting the most out of WTForms with an Extension</p>
<p>The <a class="reference external" href="https://flask-wtf.readthedocs.io/">Flask-WTF</a> extension expands on this pattern and adds a
few little helpers that make working with forms and Flask more
fun. You can get it from <a class="reference external" href="https://pypi.org/project/Flask-WTF/">PyPI</a>.</p>
</div>
<section id="the-forms">
<h2>The Forms<a class="headerlink" href="#the-forms" title="Link to this heading"></a></h2>
<p>This is an example form for a typical registration page:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">wtforms</span><span class="w"> </span><span class="kn">import</span> <span class="n">Form</span><span class="p">,</span> <span class="n">BooleanField</span><span class="p">,</span> <span class="n">StringField</span><span class="p">,</span> <span class="n">PasswordField</span><span class="p">,</span> <span class="n">validators</span>
<span class="k">class</span><span class="w"> </span><span class="nc">RegistrationForm</span><span class="p">(</span><span class="n">Form</span><span class="p">):</span>
<span class="n">username</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="s1">&#39;Username&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">validators</span><span class="o">.</span><span class="n">Length</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">25</span><span class="p">)])</span>
<span class="n">email</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="s1">&#39;Email Address&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">validators</span><span class="o">.</span><span class="n">Length</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">6</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">35</span><span class="p">)])</span>
<span class="n">password</span> <span class="o">=</span> <span class="n">PasswordField</span><span class="p">(</span><span class="s1">&#39;New Password&#39;</span><span class="p">,</span> <span class="p">[</span>
<span class="n">validators</span><span class="o">.</span><span class="n">DataRequired</span><span class="p">(),</span>
<span class="n">validators</span><span class="o">.</span><span class="n">EqualTo</span><span class="p">(</span><span class="s1">&#39;confirm&#39;</span><span class="p">,</span> <span class="n">message</span><span class="o">=</span><span class="s1">&#39;Passwords must match&#39;</span><span class="p">)</span>
<span class="p">])</span>
<span class="n">confirm</span> <span class="o">=</span> <span class="n">PasswordField</span><span class="p">(</span><span class="s1">&#39;Repeat Password&#39;</span><span class="p">)</span>
<span class="n">accept_tos</span> <span class="o">=</span> <span class="n">BooleanField</span><span class="p">(</span><span class="s1">&#39;I accept the TOS&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">validators</span><span class="o">.</span><span class="n">DataRequired</span><span class="p">()])</span>
</pre></div>
</div>
</section>
<section id="in-the-view">
<h2>In the View<a class="headerlink" href="#in-the-view" title="Link to this heading"></a></h2>
<p>In the view function, the usage of this form looks like this:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">&#39;/register&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;GET&#39;</span><span class="p">,</span> <span class="s1">&#39;POST&#39;</span><span class="p">])</span>
<span class="k">def</span><span class="w"> </span><span class="nf">register</span><span class="p">():</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">RegistrationForm</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">)</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;POST&#39;</span> <span class="ow">and</span> <span class="n">form</span><span class="o">.</span><span class="n">validate</span><span class="p">():</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">User</span><span class="p">(</span><span class="n">form</span><span class="o">.</span><span class="n">username</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="n">form</span><span class="o">.</span><span class="n">email</span><span class="o">.</span><span class="n">data</span><span class="p">,</span>
<span class="n">form</span><span class="o">.</span><span class="n">password</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="n">db_session</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="n">flash</span><span class="p">(</span><span class="s1">&#39;Thanks for registering&#39;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s1">&#39;login&#39;</span><span class="p">))</span>
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">&#39;register.html&#39;</span><span class="p">,</span> <span class="n">form</span><span class="o">=</span><span class="n">form</span><span class="p">)</span>
</pre></div>
</div>
<p>Notice were implying that the view is using SQLAlchemy here
(<a class="reference internal" href="sqlalchemy.html"><span class="doc">SQLAlchemy in Flask</span></a>), but thats not a requirement, of course. Adapt
the code as necessary.</p>
<p>Things to remember:</p>
<ol class="arabic simple">
<li><p>create the form from the request <code class="xref py py-attr docutils literal notranslate"><span class="pre">form</span></code> value if
the data is submitted via the HTTP <code class="docutils literal notranslate"><span class="pre">POST</span></code> method and
<code class="xref py py-attr docutils literal notranslate"><span class="pre">args</span></code> if the data is submitted as <code class="docutils literal notranslate"><span class="pre">GET</span></code>.</p></li>
<li><p>to validate the data, call the <code class="xref py py-func docutils literal notranslate"><span class="pre">validate()</span></code>
method, which will return <code class="docutils literal notranslate"><span class="pre">True</span></code> if the data validates, <code class="docutils literal notranslate"><span class="pre">False</span></code>
otherwise.</p></li>
<li><p>to access individual values from the form, access <code class="code docutils literal notranslate"><span class="pre">form.&lt;NAME&gt;.data</span></code>.</p></li>
</ol>
</section>
<section id="forms-in-templates">
<h2>Forms in Templates<a class="headerlink" href="#forms-in-templates" title="Link to this heading"></a></h2>
<p>Now to the template side. When you pass the form to the templates, you can
easily render them there. Look at the following example template to see
how easy this is. WTForms does half the form generation for us already.
To make it even nicer, we can write a macro that renders a field with
label and a list of errors if there are any.</p>
<p>Heres an example <code class="file docutils literal notranslate"><span class="pre">_formhelpers.html</span></code> template with such a macro:</p>
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">macro</span> <span class="nv">render_field</span><span class="o">(</span><span class="nv">field</span><span class="o">)</span> <span class="cp">%}</span>
<span class="p">&lt;</span><span class="nt">dt</span><span class="p">&gt;</span><span class="cp">{{</span> <span class="nv">field.label</span> <span class="cp">}}</span>
<span class="p">&lt;</span><span class="nt">dd</span><span class="p">&gt;</span><span class="cp">{{</span> <span class="nv">field</span><span class="o">(**</span><span class="nv">kwargs</span><span class="o">)|</span><span class="nf">safe</span> <span class="cp">}}</span>
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">field.errors</span> <span class="cp">%}</span>
<span class="p">&lt;</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">errors</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">error</span> <span class="k">in</span> <span class="nv">field.errors</span> <span class="cp">%}</span>
<span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span><span class="cp">{{</span> <span class="nv">error</span> <span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
<span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
<span class="p">&lt;/</span><span class="nt">dd</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">endmacro</span> <span class="cp">%}</span>
</pre></div>
</div>
<p>This macro accepts a couple of keyword arguments that are forwarded to
WTForms field function, which renders the field for us. The keyword
arguments will be inserted as HTML attributes. So, for example, you can
call <code class="docutils literal notranslate"><span class="pre">render_field(form.username,</span> <span class="pre">class='username')</span></code> to add a class to
the input element. Note that WTForms returns standard Python strings,
so we have to tell Jinja2 that this data is already HTML-escaped with
the <code class="docutils literal notranslate"><span class="pre">|safe</span></code> filter.</p>
<p>Here is the <code class="file docutils literal notranslate"><span class="pre">register.html</span></code> template for the function we used above, which
takes advantage of the <code class="file docutils literal notranslate"><span class="pre">_formhelpers.html</span></code> template:</p>
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">from</span> <span class="s2">&quot;_formhelpers.html&quot;</span> <span class="k">import</span> <span class="nv">render_field</span> <span class="cp">%}</span>
<span class="p">&lt;</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">post</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">dl</span><span class="p">&gt;</span>
<span class="cp">{{</span> <span class="nv">render_field</span><span class="o">(</span><span class="nv">form.username</span><span class="o">)</span> <span class="cp">}}</span>
<span class="cp">{{</span> <span class="nv">render_field</span><span class="o">(</span><span class="nv">form.email</span><span class="o">)</span> <span class="cp">}}</span>
<span class="cp">{{</span> <span class="nv">render_field</span><span class="o">(</span><span class="nv">form.password</span><span class="o">)</span> <span class="cp">}}</span>
<span class="cp">{{</span> <span class="nv">render_field</span><span class="o">(</span><span class="nv">form.confirm</span><span class="o">)</span> <span class="cp">}}</span>
<span class="cp">{{</span> <span class="nv">render_field</span><span class="o">(</span><span class="nv">form.accept_tos</span><span class="o">)</span> <span class="cp">}}</span>
<span class="p">&lt;/</span><span class="nt">dl</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">submit</span> <span class="na">value</span><span class="o">=</span><span class="s">Register</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</pre></div>
</div>
<p>For more information about WTForms, head over to the <a class="reference external" href="https://wtforms.readthedocs.io/">WTForms
website</a>.</p>
</section>
</section>
<div class="clearer"></div>
</div>
</div>
</div>
<span id="sidebar-top"></span>
<div class="sphinxsidebar" role="navigation" aria-label="Main">
<div class="sphinxsidebarwrapper">
<p class="logo"><a href="../index.html">
<img class="logo" src="../_static/flask-vertical.png" alt="Logo of Flask"/>
</a></p>
<h3>Contents</h3>
<ul>
<li><a class="reference internal" href="#">Form Validation with WTForms</a><ul>
<li><a class="reference internal" href="#the-forms">The Forms</a></li>
<li><a class="reference internal" href="#in-the-view">In the View</a></li>
<li><a class="reference internal" href="#forms-in-templates">Forms in Templates</a></li>
</ul>
</li>
</ul>
<h3>Navigation</h3>
<ul>
<li><a href="../index.html">Overview</a>
<ul>
<li><a href="index.html">Patterns for Flask</a>
<ul>
<li>Previous: <a href="viewdecorators.html" title="previous chapter">View Decorators</a>
<li>Next: <a href="templateinheritance.html" title="next chapter">Template Inheritance</a></ul>
</li>
</ul>
</li>
</ul>
<search id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="../search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</search>
<script>document.getElementById('searchbox').style.display = "block"</script><div id="ethical-ad-placement"></div>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2010 Pallets.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
</div>
</body>
</html>