[pre-commit.ci lite] apply automatic fixes
This commit is contained in:
parent
b3ae3117f9
commit
3d83d8138c
102 changed files with 26790 additions and 26749 deletions
|
|
@ -5,7 +5,7 @@
|
|||
<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>Blog Blueprint — Flask Documentation (3.2.x)</title>
|
||||
<title>Application Setup — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Make the Project Installable" href="install.html" />
|
||||
<link rel="prev" title="Static Files" href="static.html" />
|
||||
<link rel="next" title="Define and Access the Database" href="database.html" />
|
||||
<link rel="prev" title="Project Layout" href="layout.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,337 +28,167 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="install.html" title="Make the Project Installable"
|
||||
<a href="database.html" title="Define and Access the Database"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="static.html" title="Static Files"
|
||||
<a href="layout.html" title="Project Layout"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Blog Blueprint</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Application Setup</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="blog-blueprint">
|
||||
<h1>Blog Blueprint<a class="headerlink" href="#blog-blueprint" title="Link to this heading">¶</a></h1>
|
||||
<p>You’ll use the same techniques you learned about when writing the
|
||||
authentication blueprint to write the blog blueprint. The blog should
|
||||
list all posts, allow logged in users to create posts, and allow the
|
||||
author of a post to edit or delete it.</p>
|
||||
<p>As you implement each view, keep the development server running. As you
|
||||
save your changes, try going to the URL in your browser and testing them
|
||||
out.</p>
|
||||
<section id="the-blueprint">
|
||||
<h2>The Blueprint<a class="headerlink" href="#the-blueprint" title="Link to this heading">¶</a></h2>
|
||||
<p>Define the blueprint and register it in the application factory.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/blog.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">Blueprint</span><span class="p">,</span> <span class="n">flash</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">redirect</span><span class="p">,</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">url_for</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">werkzeug.exceptions</span><span class="w"> </span><span class="kn">import</span> <span class="n">abort</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">login_required</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
<span class="n">bp</span> <span class="o">=</span> <span class="n">Blueprint</span><span class="p">(</span><span class="s1">'blog'</span><span class="p">,</span> <span class="vm">__name__</span><span class="p">)</span>
|
||||
<section id="application-setup">
|
||||
<h1>Application Setup<a class="headerlink" href="#application-setup" title="Link to this heading">¶</a></h1>
|
||||
<p>A Flask application is an instance of the <a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> class.
|
||||
Everything about the application, such as configuration and URLs, will
|
||||
be registered with this class.</p>
|
||||
<p>The most straightforward way to create a Flask application is to create
|
||||
a global <a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> instance directly at the top of your code, like
|
||||
how the “Hello, World!” example did on the previous page. While this is
|
||||
simple and useful in some cases, it can cause some tricky issues as the
|
||||
project grows.</p>
|
||||
<p>Instead of creating a <a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> instance globally, you will create
|
||||
it inside a function. This function is known as the <em>application
|
||||
factory</em>. Any configuration, registration, and other setup the
|
||||
application needs will happen inside the function, then the application
|
||||
will be returned.</p>
|
||||
<section id="the-application-factory">
|
||||
<h2>The Application Factory<a class="headerlink" href="#the-application-factory" title="Link to this heading">¶</a></h2>
|
||||
<p>It’s time to start coding! Create the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> directory and add the
|
||||
<code class="docutils literal notranslate"><span class="pre">__init__.py</span></code> file. The <code class="docutils literal notranslate"><span class="pre">__init__.py</span></code> serves double duty: it will
|
||||
contain the application factory, and it tells Python that the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code>
|
||||
directory should be treated as a package.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ mkdir flaskr
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Import and register the blueprint from the factory using
|
||||
<a class="reference internal" href="../api.html#flask.Flask.register_blueprint" title="flask.Flask.register_blueprint"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.register_blueprint()</span></code></a>. Place the
|
||||
new code at the end of the factory function before returning the app.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/__init__.py</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">create_app</span><span class="p">():</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="o">...</span>
|
||||
<span class="c1"># existing code omitted</span>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/__init__.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.</span><span class="w"> </span><span class="kn">import</span> <span class="n">blog</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">register_blueprint</span><span class="p">(</span><span class="n">blog</span><span class="o">.</span><span class="n">bp</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="s1">'/'</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s1">'index'</span><span class="p">)</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">create_app</span><span class="p">(</span><span class="n">test_config</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="c1"># create and configure the app</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">instance_relative_config</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_mapping</span><span class="p">(</span>
|
||||
<span class="n">SECRET_KEY</span><span class="o">=</span><span class="s1">'dev'</span><span class="p">,</span>
|
||||
<span class="n">DATABASE</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">instance_path</span><span class="p">,</span> <span class="s1">'flaskr.sqlite'</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">test_config</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="c1"># load the instance config, if it exists, when not testing</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_pyfile</span><span class="p">(</span><span class="s1">'config.py'</span><span class="p">,</span> <span class="n">silent</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># load the test config if passed in</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_mapping</span><span class="p">(</span><span class="n">test_config</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># ensure the instance folder exists</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">instance_path</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
|
||||
<span class="k">pass</span>
|
||||
|
||||
<span class="c1"># a simple page that says hello</span>
|
||||
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
||||
<span class="k">return</span> <span class="s1">'Hello, World!'</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">app</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Unlike the auth blueprint, the blog blueprint does not have a
|
||||
<code class="docutils literal notranslate"><span class="pre">url_prefix</span></code>. So the <code class="docutils literal notranslate"><span class="pre">index</span></code> view will be at <code class="docutils literal notranslate"><span class="pre">/</span></code>, the <code class="docutils literal notranslate"><span class="pre">create</span></code>
|
||||
view at <code class="docutils literal notranslate"><span class="pre">/create</span></code>, and so on. The blog is the main feature of Flaskr,
|
||||
so it makes sense that the blog index will be the main index.</p>
|
||||
<p>However, the endpoint for the <code class="docutils literal notranslate"><span class="pre">index</span></code> view defined below will be
|
||||
<code class="docutils literal notranslate"><span class="pre">blog.index</span></code>. Some of the authentication views referred to a plain
|
||||
<code class="docutils literal notranslate"><span class="pre">index</span></code> endpoint. <a class="reference internal" href="../api.html#flask.Flask.add_url_rule" title="flask.Flask.add_url_rule"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.add_url_rule()</span></code></a>
|
||||
associates the endpoint name <code class="docutils literal notranslate"><span class="pre">'index'</span></code> with the <code class="docutils literal notranslate"><span class="pre">/</span></code> url so that
|
||||
<code class="docutils literal notranslate"><span class="pre">url_for('index')</span></code> or <code class="docutils literal notranslate"><span class="pre">url_for('blog.index')</span></code> will both work,
|
||||
generating the same <code class="docutils literal notranslate"><span class="pre">/</span></code> URL either way.</p>
|
||||
<p>In another application you might give the blog blueprint a
|
||||
<code class="docutils literal notranslate"><span class="pre">url_prefix</span></code> and define a separate <code class="docutils literal notranslate"><span class="pre">index</span></code> view in the application
|
||||
factory, similar to the <code class="docutils literal notranslate"><span class="pre">hello</span></code> view. Then the <code class="docutils literal notranslate"><span class="pre">index</span></code> and
|
||||
<code class="docutils literal notranslate"><span class="pre">blog.index</span></code> endpoints and URLs would be different.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">create_app</span></code> is the application factory function. You’ll add to it
|
||||
later in the tutorial, but it already does a lot.</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">app</span> <span class="pre">=</span> <span class="pre">Flask(__name__,</span> <span class="pre">instance_relative_config=True)</span></code> creates the
|
||||
<a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> instance.</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">__name__</span></code> is the name of the current Python module. The app
|
||||
needs to know where it’s located to set up some paths, and
|
||||
<code class="docutils literal notranslate"><span class="pre">__name__</span></code> is a convenient way to tell it that.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">instance_relative_config=True</span></code> tells the app that
|
||||
configuration files are relative to the
|
||||
<a class="reference internal" href="../config.html#instance-folders"><span class="std std-ref">instance folder</span></a>. The instance folder
|
||||
is located outside the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package and can hold local
|
||||
data that shouldn’t be committed to version control, such as
|
||||
configuration secrets and the database file.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Config.from_mapping" title="flask.Config.from_mapping"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.config.from_mapping()</span></code></a> sets
|
||||
some default configuration that the app will use:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference internal" href="../config.html#SECRET_KEY" title="SECRET_KEY"><code class="xref py py-data docutils literal notranslate"><span class="pre">SECRET_KEY</span></code></a> is used by Flask and extensions to keep data
|
||||
safe. It’s set to <code class="docutils literal notranslate"><span class="pre">'dev'</span></code> to provide a convenient value
|
||||
during development, but it should be overridden with a random
|
||||
value when deploying.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> is the path where the SQLite database file will be
|
||||
saved. It’s under
|
||||
<a class="reference internal" href="../api.html#flask.Flask.instance_path" title="flask.Flask.instance_path"><code class="xref py py-attr docutils literal notranslate"><span class="pre">app.instance_path</span></code></a>, which is the
|
||||
path that Flask has chosen for the instance folder. You’ll learn
|
||||
more about the database in the next section.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Config.from_pyfile" title="flask.Config.from_pyfile"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.config.from_pyfile()</span></code></a> overrides
|
||||
the default configuration with values taken from the <code class="docutils literal notranslate"><span class="pre">config.py</span></code>
|
||||
file in the instance folder if it exists. For example, when
|
||||
deploying, this can be used to set a real <code class="docutils literal notranslate"><span class="pre">SECRET_KEY</span></code>.</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">test_config</span></code> can also be passed to the factory, and will be
|
||||
used instead of the instance configuration. This is so the tests
|
||||
you’ll write later in the tutorial can be configured
|
||||
independently of any development values you have configured.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><a class="reference external" href="https://docs.python.org/3/library/os.html#os.makedirs" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">os.makedirs()</span></code></a> ensures that
|
||||
<a class="reference internal" href="../api.html#flask.Flask.instance_path" title="flask.Flask.instance_path"><code class="xref py py-attr docutils literal notranslate"><span class="pre">app.instance_path</span></code></a> exists. Flask
|
||||
doesn’t create the instance folder automatically, but it needs to be
|
||||
created because your project will create the SQLite database file
|
||||
there.</p></li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Flask.route" title="flask.Flask.route"><code class="xref py py-meth docutils literal notranslate"><span class="pre">@app.route()</span></code></a> creates a simple route so you can
|
||||
see the application working before getting into the rest of the
|
||||
tutorial. It creates a connection between the URL <code class="docutils literal notranslate"><span class="pre">/hello</span></code> and a
|
||||
function that returns a response, the string <code class="docutils literal notranslate"><span class="pre">'Hello,</span> <span class="pre">World!'</span></code> in
|
||||
this case.</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
<section id="index">
|
||||
<h2>Index<a class="headerlink" href="#index" title="Link to this heading">¶</a></h2>
|
||||
<p>The index will show all of the posts, most recent first. A <code class="docutils literal notranslate"><span class="pre">JOIN</span></code> is
|
||||
used so that the author information from the <code class="docutils literal notranslate"><span class="pre">user</span></code> table is
|
||||
available in the result.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/blog.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">index</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">posts</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s1">'SELECT p.id, title, body, created, author_id, username'</span>
|
||||
<span class="s1">' FROM post p JOIN user u ON p.author_id = u.id'</span>
|
||||
<span class="s1">' ORDER BY created DESC'</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">'blog/index.html'</span><span class="p">,</span> <span class="n">posts</span><span class="o">=</span><span class="n">posts</span><span class="p">)</span>
|
||||
<section id="run-the-application">
|
||||
<h2>Run The Application<a class="headerlink" href="#run-the-application" title="Link to this heading">¶</a></h2>
|
||||
<p>Now you can run your application using the <code class="docutils literal notranslate"><span class="pre">flask</span></code> command. From the
|
||||
terminal, tell Flask where to find your application, then run it in
|
||||
debug mode. Remember, you should still be in the top-level
|
||||
<code class="docutils literal notranslate"><span class="pre">flask-tutorial</span></code> directory, not the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package.</p>
|
||||
<p>Debug mode shows an interactive debugger whenever a page raises an
|
||||
exception, and restarts the server whenever you make changes to the
|
||||
code. You can leave it running and just reload the browser page as you
|
||||
follow the tutorial.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ flask --app flaskr run --debug
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/blog/index.html</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>Posts<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">g.user</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">a</span> <span class="na">class</span><span class="o">=</span><span class="s">"action"</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'blog.create'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>New<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">post</span> <span class="k">in</span> <span class="nv">posts</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">article</span> <span class="na">class</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{{</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'title'</span><span class="o">]</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"about"</span><span class="p">></span>by <span class="cp">{{</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'username'</span><span class="o">]</span> <span class="cp">}}</span> on <span class="cp">{{</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'created'</span><span class="o">]</span><span class="nv">.strftime</span><span class="o">(</span><span class="s1">'%Y-%m-%d'</span><span class="o">)</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">div</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">g.user</span><span class="o">[</span><span class="s1">'id'</span><span class="o">]</span> <span class="o">==</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'author_id'</span><span class="o">]</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">a</span> <span class="na">class</span><span class="o">=</span><span class="s">"action"</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'blog.update'</span><span class="o">,</span> <span class="nv">id</span><span class="o">=</span><span class="nv">post</span><span class="o">[</span><span class="s1">'id'</span><span class="o">])</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Edit<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">p</span> <span class="na">class</span><span class="o">=</span><span class="s">"body"</span><span class="p">></span><span class="cp">{{</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'body'</span><span class="o">]</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">p</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">article</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">if</span> <span class="k">not</span> <span class="nb">loop</span><span class="nv">.last</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">hr</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
|
||||
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<p>You’ll see output similar to this:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>* Serving Flask app "flaskr"
|
||||
* Debug mode: on
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger PIN: nnn-nnn-nnn
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>When a user is logged in, the <code class="docutils literal notranslate"><span class="pre">header</span></code> block adds a link to the
|
||||
<code class="docutils literal notranslate"><span class="pre">create</span></code> view. When the user is the author of a post, they’ll see an
|
||||
“Edit” link to the <code class="docutils literal notranslate"><span class="pre">update</span></code> view for that post. <code class="docutils literal notranslate"><span class="pre">loop.last</span></code> is a
|
||||
special variable available inside <a class="reference external" href="https://jinja.palletsprojects.com/templates/#for">Jinja for loops</a>. It’s used to
|
||||
display a line after each post except the last one, to visually separate
|
||||
them.</p>
|
||||
</section>
|
||||
<section id="create">
|
||||
<h2>Create<a class="headerlink" href="#create" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">create</span></code> view works the same as the auth <code class="docutils literal notranslate"><span class="pre">register</span></code> view. Either
|
||||
the form is displayed, or the posted data is validated and the post is
|
||||
added to the database or an error is shown.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">login_required</span></code> decorator you wrote earlier is used on the blog
|
||||
views. A user must be logged in to visit these views, otherwise they
|
||||
will be redirected to the login page.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/blog.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'POST'</span><span class="p">))</span>
|
||||
<span class="nd">@login_required</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">create</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">'POST'</span><span class="p">:</span>
|
||||
<span class="n">title</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span>
|
||||
<span class="n">body</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'body'</span><span class="p">]</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">title</span><span class="p">:</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="s1">'Title is required.'</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">error</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">flash</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s1">'INSERT INTO post (title, body, author_id)'</span>
|
||||
<span class="s1">' VALUES (?, ?, ?)'</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span><span class="p">[</span><span class="s1">'id'</span><span class="p">])</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</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">'blog.index'</span><span class="p">))</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">'blog/create.html'</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="literal-block-wrapper docutils container" id="id6">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/blog/create.html</span></code></span><a class="headerlink" href="#id6" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>New Post<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"title"</span><span class="p">></span>Title<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">"title"</span> <span class="na">id</span><span class="o">=</span><span class="s">"title"</span> <span class="na">value</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">request.form</span><span class="o">[</span><span class="s1">'title'</span><span class="o">]</span> <span class="cp">}}</span><span class="s">"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"body"</span><span class="p">></span>Body<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">textarea</span> <span class="na">name</span><span class="o">=</span><span class="s">"body"</span> <span class="na">id</span><span class="o">=</span><span class="s">"body"</span><span class="p">></span><span class="cp">{{</span> <span class="nv">request.form</span><span class="o">[</span><span class="s1">'body'</span><span class="o">]</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">textarea</span><span class="p">></span>
|
||||
<span class="p"><</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">"Save"</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="update">
|
||||
<h2>Update<a class="headerlink" href="#update" title="Link to this heading">¶</a></h2>
|
||||
<p>Both the <code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code> views will need to fetch a <code class="docutils literal notranslate"><span class="pre">post</span></code>
|
||||
by <code class="docutils literal notranslate"><span class="pre">id</span></code> and check if the author matches the logged in user. To avoid
|
||||
duplicating code, you can write a function to get the <code class="docutils literal notranslate"><span class="pre">post</span></code> and call
|
||||
it from each view.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id7">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/blog.py</span></code></span><a class="headerlink" href="#id7" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">get_post</span><span class="p">(</span><span class="nb">id</span><span class="p">,</span> <span class="n">check_author</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s1">'SELECT p.id, title, body, created, author_id, username'</span>
|
||||
<span class="s1">' FROM post p JOIN user u ON p.author_id = u.id'</span>
|
||||
<span class="s1">' WHERE p.id = ?'</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="nb">id</span><span class="p">,)</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">post</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">abort</span><span class="p">(</span><span class="mi">404</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"Post id </span><span class="si">{</span><span class="nb">id</span><span class="si">}</span><span class="s2"> doesn't exist."</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">check_author</span> <span class="ow">and</span> <span class="n">post</span><span class="p">[</span><span class="s1">'author_id'</span><span class="p">]</span> <span class="o">!=</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span><span class="p">[</span><span class="s1">'id'</span><span class="p">]:</span>
|
||||
<span class="n">abort</span><span class="p">(</span><span class="mi">403</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">post</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference internal" href="../api.html#flask.abort" title="flask.abort"><code class="xref py py-func docutils literal notranslate"><span class="pre">abort()</span></code></a> will raise a special exception that returns an HTTP status
|
||||
code. It takes an optional message to show with the error, otherwise a
|
||||
default message is used. <code class="docutils literal notranslate"><span class="pre">404</span></code> means “Not Found”, and <code class="docutils literal notranslate"><span class="pre">403</span></code> means
|
||||
“Forbidden”. (<code class="docutils literal notranslate"><span class="pre">401</span></code> means “Unauthorized”, but you redirect to the
|
||||
login page instead of returning that status.)</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">check_author</span></code> argument is defined so that the function can be
|
||||
used to get a <code class="docutils literal notranslate"><span class="pre">post</span></code> without checking the author. This would be useful
|
||||
if you wrote a view to show an individual post on a page, where the user
|
||||
doesn’t matter because they’re not modifying the post.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id8">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/blog.py</span></code></span><a class="headerlink" href="#id8" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/<int:id>/update'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'POST'</span><span class="p">))</span>
|
||||
<span class="nd">@login_required</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">update</span><span class="p">(</span><span class="nb">id</span><span class="p">):</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">get_post</span><span class="p">(</span><span class="nb">id</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">'POST'</span><span class="p">:</span>
|
||||
<span class="n">title</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span>
|
||||
<span class="n">body</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'body'</span><span class="p">]</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">title</span><span class="p">:</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="s1">'Title is required.'</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">error</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">flash</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s1">'UPDATE post SET title = ?, body = ?'</span>
|
||||
<span class="s1">' WHERE id = ?'</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span> <span class="nb">id</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</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">'blog.index'</span><span class="p">))</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">'blog/update.html'</span><span class="p">,</span> <span class="n">post</span><span class="o">=</span><span class="n">post</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Unlike the views you’ve written so far, the <code class="docutils literal notranslate"><span class="pre">update</span></code> function takes
|
||||
an argument, <code class="docutils literal notranslate"><span class="pre">id</span></code>. That corresponds to the <code class="docutils literal notranslate"><span class="pre"><int:id></span></code> in the route.
|
||||
A real URL will look like <code class="docutils literal notranslate"><span class="pre">/1/update</span></code>. Flask will capture the <code class="docutils literal notranslate"><span class="pre">1</span></code>,
|
||||
ensure it’s an <a class="reference external" href="https://docs.python.org/3/library/functions.html#int" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">int</span></code></a>, and pass it as the <code class="docutils literal notranslate"><span class="pre">id</span></code> argument. If you
|
||||
don’t specify <code class="docutils literal notranslate"><span class="pre">int:</span></code> and instead do <code class="docutils literal notranslate"><span class="pre"><id></span></code>, it will be a string.
|
||||
To generate a URL to the update page, <a class="reference internal" href="../api.html#flask.url_for" title="flask.url_for"><code class="xref py py-func docutils literal notranslate"><span class="pre">url_for()</span></code></a> needs to be passed
|
||||
the <code class="docutils literal notranslate"><span class="pre">id</span></code> so it knows what to fill in:
|
||||
<code class="docutils literal notranslate"><span class="pre">url_for('blog.update',</span> <span class="pre">id=post['id'])</span></code>. This is also in the
|
||||
<code class="docutils literal notranslate"><span class="pre">index.html</span></code> file above.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">create</span></code> and <code class="docutils literal notranslate"><span class="pre">update</span></code> views look very similar. The main
|
||||
difference is that the <code class="docutils literal notranslate"><span class="pre">update</span></code> view uses a <code class="docutils literal notranslate"><span class="pre">post</span></code> object and an
|
||||
<code class="docutils literal notranslate"><span class="pre">UPDATE</span></code> query instead of an <code class="docutils literal notranslate"><span class="pre">INSERT</span></code>. With some clever refactoring,
|
||||
you could use one view and template for both actions, but for the
|
||||
tutorial it’s clearer to keep them separate.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id9">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/blog/update.html</span></code></span><a class="headerlink" href="#id9" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>Edit "<span class="cp">{{</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'title'</span><span class="o">]</span> <span class="cp">}}</span>"<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"title"</span><span class="p">></span>Title<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">"title"</span> <span class="na">id</span><span class="o">=</span><span class="s">"title"</span>
|
||||
<span class="na">value</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">request.form</span><span class="o">[</span><span class="s1">'title'</span><span class="o">]</span> <span class="k">or</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'title'</span><span class="o">]</span> <span class="cp">}}</span><span class="s">"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"body"</span><span class="p">></span>Body<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">textarea</span> <span class="na">name</span><span class="o">=</span><span class="s">"body"</span> <span class="na">id</span><span class="o">=</span><span class="s">"body"</span><span class="p">></span><span class="cp">{{</span> <span class="nv">request.form</span><span class="o">[</span><span class="s1">'body'</span><span class="o">]</span> <span class="k">or</span> <span class="nv">post</span><span class="o">[</span><span class="s1">'body'</span><span class="o">]</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">textarea</span><span class="p">></span>
|
||||
<span class="p"><</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">"Save"</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">hr</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'blog.delete'</span><span class="o">,</span> <span class="nv">id</span><span class="o">=</span><span class="nv">post</span><span class="o">[</span><span class="s1">'id'</span><span class="o">])</span> <span class="cp">}}</span><span class="s">"</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">class</span><span class="o">=</span><span class="s">"danger"</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">"Delete"</span> <span class="na">onclick</span><span class="o">=</span><span class="s">"return confirm('Are you sure?');"</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>This template has two forms. The first posts the edited data to the
|
||||
current page (<code class="docutils literal notranslate"><span class="pre">/<id>/update</span></code>). The other form contains only a button
|
||||
and specifies an <code class="docutils literal notranslate"><span class="pre">action</span></code> attribute that posts to the delete view
|
||||
instead. The button uses some JavaScript to show a confirmation dialog
|
||||
before submitting.</p>
|
||||
<p>The pattern <code class="docutils literal notranslate"><span class="pre">{{</span> <span class="pre">request.form['title']</span> <span class="pre">or</span> <span class="pre">post['title']</span> <span class="pre">}}</span></code> is used to
|
||||
choose what data appears in the form. When the form hasn’t been
|
||||
submitted, the original <code class="docutils literal notranslate"><span class="pre">post</span></code> data appears, but if invalid form data
|
||||
was posted you want to display that so the user can fix the error, so
|
||||
<code class="docutils literal notranslate"><span class="pre">request.form</span></code> is used instead. <a class="reference internal" href="../api.html#flask.request" title="flask.request"><code class="xref py py-data docutils literal notranslate"><span class="pre">request</span></code></a> is another variable
|
||||
that’s automatically available in templates.</p>
|
||||
</section>
|
||||
<section id="delete">
|
||||
<h2>Delete<a class="headerlink" href="#delete" title="Link to this heading">¶</a></h2>
|
||||
<p>The delete view doesn’t have its own template, the delete button is part
|
||||
of <code class="docutils literal notranslate"><span class="pre">update.html</span></code> and posts to the <code class="docutils literal notranslate"><span class="pre">/<id>/delete</span></code> URL. Since there
|
||||
is no template, it will only handle the <code class="docutils literal notranslate"><span class="pre">POST</span></code> method and then redirect
|
||||
to the <code class="docutils literal notranslate"><span class="pre">index</span></code> view.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id10">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/blog.py</span></code></span><a class="headerlink" href="#id10" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/<int:id>/delete'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">(</span><span class="s1">'POST'</span><span class="p">,))</span>
|
||||
<span class="nd">@login_required</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">delete</span><span class="p">(</span><span class="nb">id</span><span class="p">):</span>
|
||||
<span class="n">get_post</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'DELETE FROM post WHERE id = ?'</span><span class="p">,</span> <span class="p">(</span><span class="nb">id</span><span class="p">,))</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</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">'blog.index'</span><span class="p">))</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Congratulations, you’ve now finished writing your application! Take some
|
||||
time to try out everything in the browser. However, there’s still more
|
||||
to do before the project is complete.</p>
|
||||
<p>Continue to <a class="reference internal" href="install.html"><span class="doc">Make the Project Installable</span></a>.</p>
|
||||
<p>Visit <a class="reference external" href="http://127.0.0.1:5000/hello">http://127.0.0.1:5000/hello</a> in a browser and you should see the
|
||||
“Hello, World!” message. Congratulations, you’re now running your Flask
|
||||
web application!</p>
|
||||
<p>If another program is already using port 5000, you’ll see
|
||||
<code class="docutils literal notranslate"><span class="pre">OSError:</span> <span class="pre">[Errno</span> <span class="pre">98]</span></code> or <code class="docutils literal notranslate"><span class="pre">OSError:</span> <span class="pre">[WinError</span> <span class="pre">10013]</span></code> when the
|
||||
server tries to start. See <a class="reference internal" href="../server.html#address-already-in-use"><span class="std std-ref">Address already in use</span></a> for how to
|
||||
handle that.</p>
|
||||
<p>Continue to <a class="reference internal" href="database.html"><span class="doc">Define and Access the Database</span></a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
@ -370,21 +200,18 @@ to do before the project is complete.</p>
|
|||
<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="#">Blog Blueprint</a><ul>
|
||||
<li><a class="reference internal" href="#the-blueprint">The Blueprint</a></li>
|
||||
<li><a class="reference internal" href="#index">Index</a></li>
|
||||
<li><a class="reference internal" href="#create">Create</a></li>
|
||||
<li><a class="reference internal" href="#update">Update</a></li>
|
||||
<li><a class="reference internal" href="#delete">Delete</a></li>
|
||||
<li><a class="reference internal" href="#">Application Setup</a><ul>
|
||||
<li><a class="reference internal" href="#the-application-factory">The Application Factory</a></li>
|
||||
<li><a class="reference internal" href="#run-the-application">Run The Application</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -394,8 +221,8 @@ to do before the project is complete.</p>
|
|||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="static.html" title="previous chapter">Static Files</a>
|
||||
<li>Next: <a href="install.html" title="next chapter">Make the Project Installable</a></ul>
|
||||
<li>Previous: <a href="layout.html" title="previous chapter">Project Layout</a>
|
||||
<li>Next: <a href="database.html" title="next chapter">Define and Access the Database</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -419,4 +246,4 @@ to do before the project is complete.</p>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Define and Access the Database — Flask Documentation (3.2.x)</title>
|
||||
<title>Tutorial — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Blueprints and Views" href="views.html" />
|
||||
<link rel="prev" title="Application Setup" href="factory.html" />
|
||||
<link rel="next" title="Project Layout" href="layout.html" />
|
||||
<link rel="prev" title="Quickstart" href="../quickstart.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,213 +28,65 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="views.html" title="Blueprints and Views"
|
||||
<a href="layout.html" title="Project Layout"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="factory.html" title="Application Setup"
|
||||
<a href="../quickstart.html" title="Quickstart"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Define and Access the Database</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Tutorial</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="define-and-access-the-database">
|
||||
<h1>Define and Access the Database<a class="headerlink" href="#define-and-access-the-database" title="Link to this heading">¶</a></h1>
|
||||
<p>The application will use a <a class="reference external" href="https://sqlite.org/about.html">SQLite</a> database to store users and posts.
|
||||
Python comes with built-in support for SQLite in the <a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#module-sqlite3" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">sqlite3</span></code></a>
|
||||
module.</p>
|
||||
<p>SQLite is convenient because it doesn’t require setting up a separate
|
||||
database server and is built-in to Python. However, if concurrent
|
||||
requests try to write to the database at the same time, they will slow
|
||||
down as each write happens sequentially. Small applications won’t notice
|
||||
this. Once you become big, you may want to switch to a different
|
||||
database.</p>
|
||||
<p>The tutorial doesn’t go into detail about SQL. If you are not familiar
|
||||
with it, the SQLite docs describe the <a class="reference external" href="https://sqlite.org/lang.html">language</a>.</p>
|
||||
<section id="connect-to-the-database">
|
||||
<h2>Connect to the Database<a class="headerlink" href="#connect-to-the-database" title="Link to this heading">¶</a></h2>
|
||||
<p>The first thing to do when working with a SQLite database (and most
|
||||
other Python database libraries) is to create a connection to it. Any
|
||||
queries and operations are performed using the connection, which is
|
||||
closed after the work is finished.</p>
|
||||
<p>In web applications this connection is typically tied to the request. It
|
||||
is created at some point when handling a request, and closed before the
|
||||
response is sent.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/db.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">sqlite3</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">datetime</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">click</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">current_app</span><span class="p">,</span> <span class="n">g</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get_db</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="s1">'db'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">g</span><span class="p">:</span>
|
||||
<span class="n">g</span><span class="o">.</span><span class="n">db</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span>
|
||||
<span class="n">current_app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">'DATABASE'</span><span class="p">],</span>
|
||||
<span class="n">detect_types</span><span class="o">=</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">PARSE_DECLTYPES</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">g</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">row_factory</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">Row</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">g</span><span class="o">.</span><span class="n">db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">close_db</span><span class="p">(</span><span class="n">e</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">g</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">'db'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">db</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
<section id="tutorial">
|
||||
<h1>Tutorial<a class="headerlink" href="#tutorial" title="Link to this heading">¶</a></h1>
|
||||
<div class="toctree-wrapper compound">
|
||||
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="layout.html">Project Layout</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="factory.html">Application Setup</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="database.html">Define and Access the Database</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="views.html">Blueprints and Views</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="templates.html">Templates</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="static.html">Static Files</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="blog.html">Blog Blueprint</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="install.html">Make the Project Installable</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tests.html">Test Coverage</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="deploy.html">Deploy to Production</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="next.html">Keep Developing!</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference internal" href="../api.html#flask.g" title="flask.g"><code class="xref py py-data docutils literal notranslate"><span class="pre">g</span></code></a> is a special object that is unique for each request. It is
|
||||
used to store data that might be accessed by multiple functions during
|
||||
the request. The connection is stored and reused instead of creating a
|
||||
new connection if <code class="docutils literal notranslate"><span class="pre">get_db</span></code> is called a second time in the same
|
||||
request.</p>
|
||||
<p><a class="reference internal" href="../api.html#flask.current_app" title="flask.current_app"><code class="xref py py-data docutils literal notranslate"><span class="pre">current_app</span></code></a> is another special object that points to the Flask
|
||||
application handling the request. Since you used an application factory,
|
||||
there is no application object when writing the rest of your code.
|
||||
<code class="docutils literal notranslate"><span class="pre">get_db</span></code> will be called when the application has been created and is
|
||||
handling a request, so <a class="reference internal" href="../api.html#flask.current_app" title="flask.current_app"><code class="xref py py-data docutils literal notranslate"><span class="pre">current_app</span></code></a> can be used.</p>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.connect" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">sqlite3.connect()</span></code></a> establishes a connection to the file pointed at
|
||||
by the <code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> configuration key. This file doesn’t have to exist
|
||||
yet, and won’t until you initialize the database later.</p>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.Row" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">sqlite3.Row</span></code></a> tells the connection to return rows that behave
|
||||
like dicts. This allows accessing the columns by name.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">close_db</span></code> checks if a connection was created by checking if <code class="docutils literal notranslate"><span class="pre">g.db</span></code>
|
||||
was set. If the connection exists, it is closed. Further down you will
|
||||
tell your application about the <code class="docutils literal notranslate"><span class="pre">close_db</span></code> function in the application
|
||||
factory so that it is called after each request.</p>
|
||||
</section>
|
||||
<section id="create-the-tables">
|
||||
<h2>Create the Tables<a class="headerlink" href="#create-the-tables" title="Link to this heading">¶</a></h2>
|
||||
<p>In SQLite, data is stored in <em>tables</em> and <em>columns</em>. These need to be
|
||||
created before you can store and retrieve data. Flaskr will store users
|
||||
in the <code class="docutils literal notranslate"><span class="pre">user</span></code> table, and posts in the <code class="docutils literal notranslate"><span class="pre">post</span></code> table. Create a file
|
||||
with the SQL commands needed to create empty tables:</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/schema.sql</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-sql notranslate"><div class="highlight"><pre><span></span><span class="k">DROP</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">EXISTS</span><span class="w"> </span><span class="k">user</span><span class="p">;</span>
|
||||
<span class="k">DROP</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">EXISTS</span><span class="w"> </span><span class="n">post</span><span class="p">;</span>
|
||||
|
||||
<span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="n">AUTOINCREMENT</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">username</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">UNIQUE</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span>
|
||||
<span class="p">);</span>
|
||||
|
||||
<span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="n">AUTOINCREMENT</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">author_id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">created</span><span class="w"> </span><span class="k">TIMESTAMP</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="k">CURRENT_TIMESTAMP</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">title</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">body</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="k">FOREIGN</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="p">(</span><span class="n">author_id</span><span class="p">)</span><span class="w"> </span><span class="k">REFERENCES</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
|
||||
<span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Add the Python functions that will run these SQL commands to the
|
||||
<code class="docutils literal notranslate"><span class="pre">db.py</span></code> file:</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/db.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">init_db</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">current_app</span><span class="o">.</span><span class="n">open_resource</span><span class="p">(</span><span class="s1">'schema.sql'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">executescript</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf8'</span><span class="p">))</span>
|
||||
|
||||
|
||||
<span class="nd">@click</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s1">'init-db'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">init_db_command</span><span class="p">():</span>
|
||||
<span class="w"> </span><span class="sd">"""Clear the existing data and create new tables."""</span>
|
||||
<span class="n">init_db</span><span class="p">()</span>
|
||||
<span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s1">'Initialized the database.'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="n">sqlite3</span><span class="o">.</span><span class="n">register_converter</span><span class="p">(</span>
|
||||
<span class="s2">"timestamp"</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">fromisoformat</span><span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="n">decode</span><span class="p">())</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference internal" href="../api.html#flask.Flask.open_resource" title="flask.Flask.open_resource"><code class="xref py py-meth docutils literal notranslate"><span class="pre">open_resource()</span></code></a> opens a file relative to
|
||||
the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package, which is useful since you won’t necessarily know
|
||||
where that location is when deploying the application later. <code class="docutils literal notranslate"><span class="pre">get_db</span></code>
|
||||
returns a database connection, which is used to execute the commands
|
||||
read from the file.</p>
|
||||
<p><a class="reference external" href="https://click.palletsprojects.com/en/stable/api/#click.command" title="(in Click v8.1.x)"><code class="xref py py-func docutils literal notranslate"><span class="pre">click.command()</span></code></a> defines a command line command called <code class="docutils literal notranslate"><span class="pre">init-db</span></code>
|
||||
that calls the <code class="docutils literal notranslate"><span class="pre">init_db</span></code> function and shows a success message to the
|
||||
user. You can read <a class="reference internal" href="../cli.html"><span class="doc">Command Line Interface</span></a> to learn more about writing commands.</p>
|
||||
<p>The call to <a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.register_converter" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">sqlite3.register_converter()</span></code></a> tells Python how to
|
||||
interpret timestamp values in the database. We convert the value to a
|
||||
<a class="reference external" href="https://docs.python.org/3/library/datetime.html#datetime.datetime" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">datetime.datetime</span></code></a>.</p>
|
||||
</section>
|
||||
<section id="register-with-the-application">
|
||||
<h2>Register with the Application<a class="headerlink" href="#register-with-the-application" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">close_db</span></code> and <code class="docutils literal notranslate"><span class="pre">init_db_command</span></code> functions need to be registered
|
||||
with the application instance; otherwise, they won’t be used by the
|
||||
application. However, since you’re using a factory function, that
|
||||
instance isn’t available when writing the functions. Instead, write a
|
||||
function that takes an application and does the registration.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/db.py</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">teardown_appcontext</span><span class="p">(</span><span class="n">close_db</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">add_command</span><span class="p">(</span><span class="n">init_db_command</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference internal" href="../api.html#flask.Flask.teardown_appcontext" title="flask.Flask.teardown_appcontext"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.teardown_appcontext()</span></code></a> tells
|
||||
Flask to call that function when cleaning up after returning the
|
||||
response.</p>
|
||||
<p><a class="reference external" href="https://click.palletsprojects.com/en/stable/api/#click.Group.add_command" title="(in Click v8.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.cli.add_command()</span></code></a> adds a new
|
||||
command that can be called with the <code class="docutils literal notranslate"><span class="pre">flask</span></code> command.</p>
|
||||
<p>Import and call this function from the factory. Place the new code at
|
||||
the end of the factory function before returning the app.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/__init__.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">create_app</span><span class="p">():</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="o">...</span>
|
||||
<span class="c1"># existing code omitted</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.</span><span class="w"> </span><span class="kn">import</span> <span class="n">db</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">app</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="initialize-the-database-file">
|
||||
<h2>Initialize the Database File<a class="headerlink" href="#initialize-the-database-file" title="Link to this heading">¶</a></h2>
|
||||
<p>Now that <code class="docutils literal notranslate"><span class="pre">init-db</span></code> has been registered with the app, it can be called
|
||||
using the <code class="docutils literal notranslate"><span class="pre">flask</span></code> command, similar to the <code class="docutils literal notranslate"><span class="pre">run</span></code> command from the
|
||||
previous page.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>If you’re still running the server from the previous page, you can
|
||||
either stop the server, or run this command in a new terminal. If
|
||||
you use a new terminal, remember to change to your project directory
|
||||
and activate the env as described in <a class="reference internal" href="../installation.html"><span class="doc">Installation</span></a>.</p>
|
||||
</div>
|
||||
<p>Run the <code class="docutils literal notranslate"><span class="pre">init-db</span></code> command:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ flask --app flaskr init-db
|
||||
Initialized the database.
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>There will now be a <code class="docutils literal notranslate"><span class="pre">flaskr.sqlite</span></code> file in the <code class="docutils literal notranslate"><span class="pre">instance</span></code> folder in
|
||||
your project.</p>
|
||||
<p>Continue to <a class="reference internal" href="views.html"><span class="doc">Blueprints and Views</span></a>.</p>
|
||||
</section>
|
||||
<p>This tutorial will walk you through creating a basic blog application
|
||||
called Flaskr. Users will be able to register, log in, create posts,
|
||||
and edit or delete their own posts. You will be able to package and
|
||||
install the application on other computers.</p>
|
||||
<img alt="screenshot of index page" class="screenshot align-center" src="../_images/flaskr_index.png" />
|
||||
<p>It’s assumed that you’re already familiar with Python. The <a class="reference external" href="https://docs.python.org/3/tutorial/">official
|
||||
tutorial</a> in the Python docs is a great way to learn or review first.</p>
|
||||
<p>While it’s designed to give a good starting point, the tutorial doesn’t
|
||||
cover all of Flask’s features. Check out the <a class="reference internal" href="../quickstart.html"><span class="doc">Quickstart</span></a> for an
|
||||
overview of what Flask can do, then dive into the docs to find out more.
|
||||
The tutorial only uses what’s provided by Flask and Python. In another
|
||||
project, you might decide to use <a class="reference internal" href="../extensions.html"><span class="doc">Extensions</span></a> or other libraries
|
||||
to make some tasks simpler.</p>
|
||||
<img alt="screenshot of login page" class="screenshot align-center" src="../_images/flaskr_login.png" />
|
||||
<p>Flask is flexible. It doesn’t require you to use any particular project
|
||||
or code layout. However, when first starting, it’s helpful to use a more
|
||||
structured approach. This means that the tutorial will require a bit of
|
||||
boilerplate up front, but it’s done to avoid many common pitfalls that
|
||||
new developers encounter, and it creates a project that’s easy to expand
|
||||
on. Once you become more comfortable with Flask, you can step out of
|
||||
this structure and take full advantage of Flask’s flexibility.</p>
|
||||
<img alt="screenshot of edit page" class="screenshot align-center" src="../_images/flaskr_edit.png" />
|
||||
<p><a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial">The tutorial project is available as an example in the Flask
|
||||
repository</a>, if you want to compare your project
|
||||
with the final product as you follow the tutorial.</p>
|
||||
<p>Continue to <a class="reference internal" href="layout.html"><span class="doc">Project Layout</span></a>.</p>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -245,32 +97,18 @@ your project.</p>
|
|||
<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="#">Define and Access the Database</a><ul>
|
||||
<li><a class="reference internal" href="#connect-to-the-database">Connect to the Database</a></li>
|
||||
<li><a class="reference internal" href="#create-the-tables">Create the Tables</a></li>
|
||||
<li><a class="reference internal" href="#register-with-the-application">Register with the Application</a></li>
|
||||
<li><a class="reference internal" href="#initialize-the-database-file">Initialize the Database File</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="factory.html" title="previous chapter">Application Setup</a>
|
||||
<li>Next: <a href="views.html" title="next chapter">Blueprints and Views</a></ul>
|
||||
</li>
|
||||
<li>Previous: <a href="../quickstart.html" title="previous chapter">Quickstart</a>
|
||||
<li>Next: <a href="layout.html" title="next chapter">Project Layout</a>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -293,4 +131,4 @@ your project.</p>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Deploy to Production — Flask Documentation (3.2.x)</title>
|
||||
<title>Make the Project Installable — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Keep Developing!" href="next.html" />
|
||||
<link rel="prev" title="Test Coverage" href="tests.html" />
|
||||
<link rel="next" title="Test Coverage" href="tests.html" />
|
||||
<link rel="prev" title="Blog Blueprint" href="blog.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,108 +28,96 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="next.html" title="Keep Developing!"
|
||||
<a href="tests.html" title="Test Coverage"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="tests.html" title="Test Coverage"
|
||||
<a href="blog.html" title="Blog Blueprint"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Deploy to Production</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Make the Project Installable</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="deploy-to-production">
|
||||
<h1>Deploy to Production<a class="headerlink" href="#deploy-to-production" title="Link to this heading">¶</a></h1>
|
||||
<p>This part of the tutorial assumes you have a server that you want to
|
||||
deploy your application to. It gives an overview of how to create the
|
||||
distribution file and install it, but won’t go into specifics about
|
||||
what server or software to use. You can set up a new environment on your
|
||||
development computer to try out the instructions below, but probably
|
||||
shouldn’t use it for hosting a real public application. See
|
||||
<a class="reference internal" href="../deploying/index.html"><span class="doc">Deploying to Production</span></a> for a list of many different ways to host your
|
||||
application.</p>
|
||||
<section id="build-and-install">
|
||||
<h2>Build and Install<a class="headerlink" href="#build-and-install" title="Link to this heading">¶</a></h2>
|
||||
<p>When you want to deploy your application elsewhere, you build a <em>wheel</em>
|
||||
(<code class="docutils literal notranslate"><span class="pre">.whl</span></code>) file. Install and use the <code class="docutils literal notranslate"><span class="pre">build</span></code> tool to do this.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install build
|
||||
$ python -m build --wheel
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can find the file in <code class="docutils literal notranslate"><span class="pre">dist/flaskr-1.0.0-py3-none-any.whl</span></code>. The
|
||||
file name is in the format of {project name}-{version}-{python tag}
|
||||
-{abi tag}-{platform tag}.</p>
|
||||
<p>Copy this file to another machine,
|
||||
<a class="reference internal" href="../installation.html#install-create-env"><span class="std std-ref">set up a new virtualenv</span></a>, then install the
|
||||
file with <code class="docutils literal notranslate"><span class="pre">pip</span></code>.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install flaskr-1.0.0-py3-none-any.whl
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Pip will install your project along with its dependencies.</p>
|
||||
<p>Since this is a different machine, you need to run <code class="docutils literal notranslate"><span class="pre">init-db</span></code> again to
|
||||
create the database in the instance folder.</p>
|
||||
<blockquote>
|
||||
<div><div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ flask --app flaskr init-db
|
||||
</pre></div>
|
||||
</div>
|
||||
</div></blockquote>
|
||||
<p>When Flask detects that it’s installed (not in editable mode), it uses
|
||||
a different directory for the instance folder. You can find it at
|
||||
<code class="docutils literal notranslate"><span class="pre">.venv/var/flaskr-instance</span></code> instead.</p>
|
||||
</section>
|
||||
<section id="configure-the-secret-key">
|
||||
<h2>Configure the Secret Key<a class="headerlink" href="#configure-the-secret-key" title="Link to this heading">¶</a></h2>
|
||||
<p>In the beginning of the tutorial that you gave a default value for
|
||||
<a class="reference internal" href="../config.html#SECRET_KEY" title="SECRET_KEY"><code class="xref py py-data docutils literal notranslate"><span class="pre">SECRET_KEY</span></code></a>. This should be changed to some random bytes in
|
||||
production. Otherwise, attackers could use the public <code class="docutils literal notranslate"><span class="pre">'dev'</span></code> key to
|
||||
modify the session cookie, or anything else that uses the secret key.</p>
|
||||
<p>You can use the following command to output a random secret key:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ python -c 'import secrets; print(secrets.token_hex())'
|
||||
|
||||
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
</pre></div>
|
||||
<section id="make-the-project-installable">
|
||||
<h1>Make the Project Installable<a class="headerlink" href="#make-the-project-installable" title="Link to this heading">¶</a></h1>
|
||||
<p>Making your project installable means that you can build a <em>wheel</em> file and install that
|
||||
in another environment, just like you installed Flask in your project’s environment.
|
||||
This makes deploying your project the same as installing any other library, so you’re
|
||||
using all the standard Python tools to manage everything.</p>
|
||||
<p>Installing also comes with other benefits that might not be obvious from
|
||||
the tutorial or as a new Python user, including:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Currently, Python and Flask understand how to use the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code>
|
||||
package only because you’re running from your project’s directory.
|
||||
Installing means you can import it no matter where you run from.</p></li>
|
||||
<li><p>You can manage your project’s dependencies just like other packages
|
||||
do, so <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">yourproject.whl</span></code> installs them.</p></li>
|
||||
<li><p>Test tools can isolate your test environment from your development
|
||||
environment.</p></li>
|
||||
</ul>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This is being introduced late in the tutorial, but in your future
|
||||
projects you should always start with this.</p>
|
||||
</div>
|
||||
<p>Create the <code class="docutils literal notranslate"><span class="pre">config.py</span></code> file in the instance folder, which the factory
|
||||
will read from if it exists. Copy the generated value into it.</p>
|
||||
<section id="describe-the-project">
|
||||
<h2>Describe the Project<a class="headerlink" href="#describe-the-project" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file describes your project and how to build it.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">.venv/var/flaskr-instance/config.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">SECRET_KEY</span> <span class="o">=</span> <span class="s1">'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>You can also set any other necessary configuration here, although
|
||||
<code class="docutils literal notranslate"><span class="pre">SECRET_KEY</span></code> is the only one needed for Flaskr.</p>
|
||||
</section>
|
||||
<section id="run-with-a-production-server">
|
||||
<h2>Run with a Production Server<a class="headerlink" href="#run-with-a-production-server" title="Link to this heading">¶</a></h2>
|
||||
<p>When running publicly rather than in development, you should not use the
|
||||
built-in development server (<code class="docutils literal notranslate"><span class="pre">flask</span> <span class="pre">run</span></code>). The development server is
|
||||
provided by Werkzeug for convenience, but is not designed to be
|
||||
particularly efficient, stable, or secure.</p>
|
||||
<p>Instead, use a production WSGI server. For example, to use <a class="reference external" href="https://docs.pylonsproject.org/projects/waitress/en/stable/">Waitress</a>,
|
||||
first install it in the virtual environment:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install waitress
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You need to tell Waitress about your application, but it doesn’t use
|
||||
<code class="docutils literal notranslate"><span class="pre">--app</span></code> like <code class="docutils literal notranslate"><span class="pre">flask</span> <span class="pre">run</span></code> does. You need to tell it to import and
|
||||
call the application factory to get an application object.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ waitress-serve --call 'flaskr:create_app'
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="k">[project]</span>
|
||||
<span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"flaskr"</span>
|
||||
<span class="n">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.0.0"</span>
|
||||
<span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The basic blog app built in the Flask tutorial."</span>
|
||||
<span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="s2">"flask"</span><span class="p">,</span>
|
||||
<span class="p">]</span>
|
||||
|
||||
Serving on http://0.0.0.0:8080
|
||||
<span class="k">[build-system]</span>
|
||||
<span class="n">requires</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"flit_core<4"</span><span class="p">]</span>
|
||||
<span class="n">build-backend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"flit_core.buildapi"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>See <a class="reference internal" href="../deploying/index.html"><span class="doc">Deploying to Production</span></a> for a list of many different ways to host
|
||||
your application. Waitress is just an example, chosen for the tutorial
|
||||
because it supports both Windows and Linux. There are many more WSGI
|
||||
servers and deployment options that you may choose for your project.</p>
|
||||
<p>Continue to <a class="reference internal" href="next.html"><span class="doc">Keep Developing!</span></a>.</p>
|
||||
</div>
|
||||
<p>See the official <a class="reference external" href="https://packaging.python.org/tutorials/packaging-projects/">Packaging tutorial</a> for more
|
||||
explanation of the files and options used.</p>
|
||||
</section>
|
||||
<section id="install-the-project">
|
||||
<h2>Install the Project<a class="headerlink" href="#install-the-project" title="Link to this heading">¶</a></h2>
|
||||
<p>Use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install your project in the virtual environment.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install -e .
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This tells pip to find <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> in the current directory and install the
|
||||
project in <em>editable</em> or <em>development</em> mode. Editable mode means that as you make
|
||||
changes to your local code, you’ll only need to re-install if you change the metadata
|
||||
about the project, such as its dependencies.</p>
|
||||
<p>You can observe that the project is now installed with <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">list</span></code>.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip list
|
||||
|
||||
Package Version Location
|
||||
-------------- --------- ----------------------------------
|
||||
click 6.7
|
||||
Flask 1.0
|
||||
flaskr 1.0.0 /home/user/Projects/flask-tutorial
|
||||
itsdangerous 0.24
|
||||
Jinja2 2.10
|
||||
MarkupSafe 1.0
|
||||
pip 9.0.3
|
||||
Werkzeug 0.14.1
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Nothing changes from how you’ve been running your project so far.
|
||||
<code class="docutils literal notranslate"><span class="pre">--app</span></code> is still set to <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> and <code class="docutils literal notranslate"><span class="pre">flask</span> <span class="pre">run</span></code> still runs
|
||||
the application, but you can call it from anywhere, not just the
|
||||
<code class="docutils literal notranslate"><span class="pre">flask-tutorial</span></code> directory.</p>
|
||||
<p>Continue to <a class="reference internal" href="tests.html"><span class="doc">Test Coverage</span></a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
@ -141,19 +129,18 @@ servers and deployment options that you may choose for your project.</p>
|
|||
<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="#">Deploy to Production</a><ul>
|
||||
<li><a class="reference internal" href="#build-and-install">Build and Install</a></li>
|
||||
<li><a class="reference internal" href="#configure-the-secret-key">Configure the Secret Key</a></li>
|
||||
<li><a class="reference internal" href="#run-with-a-production-server">Run with a Production Server</a></li>
|
||||
<li><a class="reference internal" href="#">Make the Project Installable</a><ul>
|
||||
<li><a class="reference internal" href="#describe-the-project">Describe the Project</a></li>
|
||||
<li><a class="reference internal" href="#install-the-project">Install the Project</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -163,8 +150,8 @@ servers and deployment options that you may choose for your project.</p>
|
|||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="tests.html" title="previous chapter">Test Coverage</a>
|
||||
<li>Next: <a href="next.html" title="next chapter">Keep Developing!</a></ul>
|
||||
<li>Previous: <a href="blog.html" title="previous chapter">Blog Blueprint</a>
|
||||
<li>Next: <a href="tests.html" title="next chapter">Test Coverage</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -188,4 +175,4 @@ servers and deployment options that you may choose for your project.</p>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Application Setup — Flask Documentation (3.2.x)</title>
|
||||
<title>Project Layout — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Define and Access the Database" href="database.html" />
|
||||
<link rel="prev" title="Project Layout" href="layout.html" />
|
||||
<link rel="next" title="Application Setup" href="factory.html" />
|
||||
<link rel="prev" title="Tutorial" href="index.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,168 +28,121 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="database.html" title="Define and Access the Database"
|
||||
<a href="factory.html" title="Application Setup"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="layout.html" title="Project Layout"
|
||||
<a href="index.html" title="Tutorial"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Application Setup</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Project Layout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="application-setup">
|
||||
<h1>Application Setup<a class="headerlink" href="#application-setup" title="Link to this heading">¶</a></h1>
|
||||
<p>A Flask application is an instance of the <a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> class.
|
||||
Everything about the application, such as configuration and URLs, will
|
||||
be registered with this class.</p>
|
||||
<p>The most straightforward way to create a Flask application is to create
|
||||
a global <a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> instance directly at the top of your code, like
|
||||
how the “Hello, World!” example did on the previous page. While this is
|
||||
simple and useful in some cases, it can cause some tricky issues as the
|
||||
project grows.</p>
|
||||
<p>Instead of creating a <a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> instance globally, you will create
|
||||
it inside a function. This function is known as the <em>application
|
||||
factory</em>. Any configuration, registration, and other setup the
|
||||
application needs will happen inside the function, then the application
|
||||
will be returned.</p>
|
||||
<section id="the-application-factory">
|
||||
<h2>The Application Factory<a class="headerlink" href="#the-application-factory" title="Link to this heading">¶</a></h2>
|
||||
<p>It’s time to start coding! Create the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> directory and add the
|
||||
<code class="docutils literal notranslate"><span class="pre">__init__.py</span></code> file. The <code class="docutils literal notranslate"><span class="pre">__init__.py</span></code> serves double duty: it will
|
||||
contain the application factory, and it tells Python that the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code>
|
||||
directory should be treated as a package.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ mkdir flaskr
|
||||
|
||||
<section id="project-layout">
|
||||
<h1>Project Layout<a class="headerlink" href="#project-layout" title="Link to this heading">¶</a></h1>
|
||||
<p>Create a project directory and enter it:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ mkdir flask-tutorial
|
||||
$ cd flask-tutorial
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Then follow the <a class="reference internal" href="../installation.html"><span class="doc">installation instructions</span></a> to set
|
||||
up a Python virtual environment and install Flask for your project.</p>
|
||||
<p>The tutorial will assume you’re working from the <code class="docutils literal notranslate"><span class="pre">flask-tutorial</span></code>
|
||||
directory from now on. The file names at the top of each code block are
|
||||
relative to this directory.</p>
|
||||
<hr class="docutils" />
|
||||
<p>A Flask application can be as simple as a single file.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/__init__.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">hello.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">create_app</span><span class="p">(</span><span class="n">test_config</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="c1"># create and configure the app</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">instance_relative_config</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_mapping</span><span class="p">(</span>
|
||||
<span class="n">SECRET_KEY</span><span class="o">=</span><span class="s1">'dev'</span><span class="p">,</span>
|
||||
<span class="n">DATABASE</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">instance_path</span><span class="p">,</span> <span class="s1">'flaskr.sqlite'</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">test_config</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="c1"># load the instance config, if it exists, when not testing</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_pyfile</span><span class="p">(</span><span class="s1">'config.py'</span><span class="p">,</span> <span class="n">silent</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># load the test config if passed in</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_mapping</span><span class="p">(</span><span class="n">test_config</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># ensure the instance folder exists</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">instance_path</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
|
||||
<span class="k">pass</span>
|
||||
|
||||
<span class="c1"># a simple page that says hello</span>
|
||||
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
||||
<span class="k">return</span> <span class="s1">'Hello, World!'</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">app</span>
|
||||
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
||||
<span class="k">return</span> <span class="s1">'Hello, World!'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">create_app</span></code> is the application factory function. You’ll add to it
|
||||
later in the tutorial, but it already does a lot.</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">app</span> <span class="pre">=</span> <span class="pre">Flask(__name__,</span> <span class="pre">instance_relative_config=True)</span></code> creates the
|
||||
<a class="reference internal" href="../api.html#flask.Flask" title="flask.Flask"><code class="xref py py-class docutils literal notranslate"><span class="pre">Flask</span></code></a> instance.</p>
|
||||
<p>However, as a project gets bigger, it becomes overwhelming to keep all
|
||||
the code in one file. Python projects use <em>packages</em> to organize code
|
||||
into multiple modules that can be imported where needed, and the
|
||||
tutorial will do this as well.</p>
|
||||
<p>The project directory will contain:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">__name__</span></code> is the name of the current Python module. The app
|
||||
needs to know where it’s located to set up some paths, and
|
||||
<code class="docutils literal notranslate"><span class="pre">__name__</span></code> is a convenient way to tell it that.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">instance_relative_config=True</span></code> tells the app that
|
||||
configuration files are relative to the
|
||||
<a class="reference internal" href="../config.html#instance-folders"><span class="std std-ref">instance folder</span></a>. The instance folder
|
||||
is located outside the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package and can hold local
|
||||
data that shouldn’t be committed to version control, such as
|
||||
configuration secrets and the database file.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">flaskr/</span></code>, a Python package containing your application code and
|
||||
files.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">tests/</span></code>, a directory containing test modules.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">.venv/</span></code>, a Python virtual environment where Flask and other
|
||||
dependencies are installed.</p></li>
|
||||
<li><p>Installation files telling Python how to install your project.</p></li>
|
||||
<li><p>Version control config, such as <a class="reference external" href="https://git-scm.com/">git</a>. You should make a habit of
|
||||
using some type of version control for all your projects, no matter
|
||||
the size.</p></li>
|
||||
<li><p>Any other project files you might add in the future.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Config.from_mapping" title="flask.Config.from_mapping"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.config.from_mapping()</span></code></a> sets
|
||||
some default configuration that the app will use:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference internal" href="../config.html#SECRET_KEY" title="SECRET_KEY"><code class="xref py py-data docutils literal notranslate"><span class="pre">SECRET_KEY</span></code></a> is used by Flask and extensions to keep data
|
||||
safe. It’s set to <code class="docutils literal notranslate"><span class="pre">'dev'</span></code> to provide a convenient value
|
||||
during development, but it should be overridden with a random
|
||||
value when deploying.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> is the path where the SQLite database file will be
|
||||
saved. It’s under
|
||||
<a class="reference internal" href="../api.html#flask.Flask.instance_path" title="flask.Flask.instance_path"><code class="xref py py-attr docutils literal notranslate"><span class="pre">app.instance_path</span></code></a>, which is the
|
||||
path that Flask has chosen for the instance folder. You’ll learn
|
||||
more about the database in the next section.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Config.from_pyfile" title="flask.Config.from_pyfile"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.config.from_pyfile()</span></code></a> overrides
|
||||
the default configuration with values taken from the <code class="docutils literal notranslate"><span class="pre">config.py</span></code>
|
||||
file in the instance folder if it exists. For example, when
|
||||
deploying, this can be used to set a real <code class="docutils literal notranslate"><span class="pre">SECRET_KEY</span></code>.</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">test_config</span></code> can also be passed to the factory, and will be
|
||||
used instead of the instance configuration. This is so the tests
|
||||
you’ll write later in the tutorial can be configured
|
||||
independently of any development values you have configured.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><a class="reference external" href="https://docs.python.org/3/library/os.html#os.makedirs" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">os.makedirs()</span></code></a> ensures that
|
||||
<a class="reference internal" href="../api.html#flask.Flask.instance_path" title="flask.Flask.instance_path"><code class="xref py py-attr docutils literal notranslate"><span class="pre">app.instance_path</span></code></a> exists. Flask
|
||||
doesn’t create the instance folder automatically, but it needs to be
|
||||
created because your project will create the SQLite database file
|
||||
there.</p></li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Flask.route" title="flask.Flask.route"><code class="xref py py-meth docutils literal notranslate"><span class="pre">@app.route()</span></code></a> creates a simple route so you can
|
||||
see the application working before getting into the rest of the
|
||||
tutorial. It creates a connection between the URL <code class="docutils literal notranslate"><span class="pre">/hello</span></code> and a
|
||||
function that returns a response, the string <code class="docutils literal notranslate"><span class="pre">'Hello,</span> <span class="pre">World!'</span></code> in
|
||||
this case.</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
<section id="run-the-application">
|
||||
<h2>Run The Application<a class="headerlink" href="#run-the-application" title="Link to this heading">¶</a></h2>
|
||||
<p>Now you can run your application using the <code class="docutils literal notranslate"><span class="pre">flask</span></code> command. From the
|
||||
terminal, tell Flask where to find your application, then run it in
|
||||
debug mode. Remember, you should still be in the top-level
|
||||
<code class="docutils literal notranslate"><span class="pre">flask-tutorial</span></code> directory, not the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package.</p>
|
||||
<p>Debug mode shows an interactive debugger whenever a page raises an
|
||||
exception, and restarts the server whenever you make changes to the
|
||||
code. You can leave it running and just reload the browser page as you
|
||||
follow the tutorial.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ flask --app flaskr run --debug
|
||||
<p>By the end, your project layout will look like this:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>/home/user/Projects/flask-tutorial
|
||||
├── flaskr/
|
||||
│ ├── __init__.py
|
||||
│ ├── db.py
|
||||
│ ├── schema.sql
|
||||
│ ├── auth.py
|
||||
│ ├── blog.py
|
||||
│ ├── templates/
|
||||
│ │ ├── base.html
|
||||
│ │ ├── auth/
|
||||
│ │ │ ├── login.html
|
||||
│ │ │ └── register.html
|
||||
│ │ └── blog/
|
||||
│ │ ├── create.html
|
||||
│ │ ├── index.html
|
||||
│ │ └── update.html
|
||||
│ └── static/
|
||||
│ └── style.css
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── data.sql
|
||||
│ ├── test_factory.py
|
||||
│ ├── test_db.py
|
||||
│ ├── test_auth.py
|
||||
│ └── test_blog.py
|
||||
├── .venv/
|
||||
├── pyproject.toml
|
||||
└── MANIFEST.in
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You’ll see output similar to this:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>* Serving Flask app "flaskr"
|
||||
* Debug mode: on
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger PIN: nnn-nnn-nnn
|
||||
<p>If you’re using version control, the following files that are generated
|
||||
while running your project should be ignored. There may be other files
|
||||
based on the editor you use. In general, ignore files that you didn’t
|
||||
write. For example, with git:</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">.gitignore</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>.venv/
|
||||
|
||||
*.pyc
|
||||
__pycache__/
|
||||
|
||||
instance/
|
||||
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Visit <a class="reference external" href="http://127.0.0.1:5000/hello">http://127.0.0.1:5000/hello</a> in a browser and you should see the
|
||||
“Hello, World!” message. Congratulations, you’re now running your Flask
|
||||
web application!</p>
|
||||
<p>If another program is already using port 5000, you’ll see
|
||||
<code class="docutils literal notranslate"><span class="pre">OSError:</span> <span class="pre">[Errno</span> <span class="pre">98]</span></code> or <code class="docutils literal notranslate"><span class="pre">OSError:</span> <span class="pre">[WinError</span> <span class="pre">10013]</span></code> when the
|
||||
server tries to start. See <a class="reference internal" href="../server.html#address-already-in-use"><span class="std std-ref">Address already in use</span></a> for how to
|
||||
handle that.</p>
|
||||
<p>Continue to <a class="reference internal" href="database.html"><span class="doc">Define and Access the Database</span></a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
<p>Continue to <a class="reference internal" href="factory.html"><span class="doc">Application Setup</span></a>.</p>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -200,29 +153,20 @@ handle that.</p>
|
|||
<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="#">Application Setup</a><ul>
|
||||
<li><a class="reference internal" href="#the-application-factory">The Application Factory</a></li>
|
||||
<li><a class="reference internal" href="#run-the-application">Run The Application</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="layout.html" title="previous chapter">Project Layout</a>
|
||||
<li>Next: <a href="database.html" title="next chapter">Define and Access the Database</a></ul>
|
||||
<li>Previous: <a href="index.html" title="previous chapter">Tutorial</a>
|
||||
<li>Next: <a href="factory.html" title="next chapter">Application Setup</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -246,4 +190,4 @@ handle that.</p>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Tutorial — Flask Documentation (3.2.x)</title>
|
||||
<title>Keep Developing! — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Project Layout" href="layout.html" />
|
||||
<link rel="prev" title="Quickstart" href="../quickstart.html" />
|
||||
<link rel="next" title="Templates" href="../templating.html" />
|
||||
<link rel="prev" title="Deploy to Production" href="deploy.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,65 +28,52 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="layout.html" title="Project Layout"
|
||||
<a href="../templating.html" title="Templates"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="../quickstart.html" title="Quickstart"
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Tutorial</a></li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Keep Developing!</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="tutorial">
|
||||
<h1>Tutorial<a class="headerlink" href="#tutorial" title="Link to this heading">¶</a></h1>
|
||||
<div class="toctree-wrapper compound">
|
||||
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="layout.html">Project Layout</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="factory.html">Application Setup</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="database.html">Define and Access the Database</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="views.html">Blueprints and Views</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="templates.html">Templates</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="static.html">Static Files</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="blog.html">Blog Blueprint</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="install.html">Make the Project Installable</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tests.html">Test Coverage</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="deploy.html">Deploy to Production</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="next.html">Keep Developing!</a></li>
|
||||
|
||||
<section id="keep-developing">
|
||||
<h1>Keep Developing!<a class="headerlink" href="#keep-developing" title="Link to this heading">¶</a></h1>
|
||||
<p>You’ve learned about quite a few Flask and Python concepts throughout
|
||||
the tutorial. Go back and review the tutorial and compare your code with
|
||||
the steps you took to get there. Compare your project to the
|
||||
<a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial">example project</a>, which might look a bit
|
||||
different due to the step-by-step nature of the tutorial.</p>
|
||||
<p>There’s a lot more to Flask than what you’ve seen so far. Even so,
|
||||
you’re now equipped to start developing your own web applications. Check
|
||||
out the <a class="reference internal" href="../quickstart.html"><span class="doc">Quickstart</span></a> for an overview of what Flask can do, then
|
||||
dive into the docs to keep learning. Flask uses <a class="reference external" href="https://palletsprojects.com/p/jinja/">Jinja</a>, <a class="reference external" href="https://palletsprojects.com/p/click/">Click</a>,
|
||||
<a class="reference external" href="https://palletsprojects.com/p/werkzeug/">Werkzeug</a>, and <a class="reference external" href="https://palletsprojects.com/p/itsdangerous/">ItsDangerous</a> behind the scenes, and they all have
|
||||
their own documentation too. You’ll also be interested in
|
||||
<a class="reference internal" href="../extensions.html"><span class="doc">Extensions</span></a> which make tasks like working with the database or
|
||||
validating form data easier and more powerful.</p>
|
||||
<p>If you want to keep developing your Flaskr project, here are some ideas
|
||||
for what to try next:</p>
|
||||
<ul class="simple">
|
||||
<li><p>A detail view to show a single post. Click a post’s title to go to
|
||||
its page.</p></li>
|
||||
<li><p>Like / unlike a post.</p></li>
|
||||
<li><p>Comments.</p></li>
|
||||
<li><p>Tags. Clicking a tag shows all the posts with that tag.</p></li>
|
||||
<li><p>A search box that filters the index page by name.</p></li>
|
||||
<li><p>Paged display. Only show 5 posts per page.</p></li>
|
||||
<li><p>Upload an image to go along with a post.</p></li>
|
||||
<li><p>Format posts using Markdown.</p></li>
|
||||
<li><p>An RSS feed of new posts.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>This tutorial will walk you through creating a basic blog application
|
||||
called Flaskr. Users will be able to register, log in, create posts,
|
||||
and edit or delete their own posts. You will be able to package and
|
||||
install the application on other computers.</p>
|
||||
<img alt="screenshot of index page" class="screenshot align-center" src="../_images/flaskr_index.png" />
|
||||
<p>It’s assumed that you’re already familiar with Python. The <a class="reference external" href="https://docs.python.org/3/tutorial/">official
|
||||
tutorial</a> in the Python docs is a great way to learn or review first.</p>
|
||||
<p>While it’s designed to give a good starting point, the tutorial doesn’t
|
||||
cover all of Flask’s features. Check out the <a class="reference internal" href="../quickstart.html"><span class="doc">Quickstart</span></a> for an
|
||||
overview of what Flask can do, then dive into the docs to find out more.
|
||||
The tutorial only uses what’s provided by Flask and Python. In another
|
||||
project, you might decide to use <a class="reference internal" href="../extensions.html"><span class="doc">Extensions</span></a> or other libraries
|
||||
to make some tasks simpler.</p>
|
||||
<img alt="screenshot of login page" class="screenshot align-center" src="../_images/flaskr_login.png" />
|
||||
<p>Flask is flexible. It doesn’t require you to use any particular project
|
||||
or code layout. However, when first starting, it’s helpful to use a more
|
||||
structured approach. This means that the tutorial will require a bit of
|
||||
boilerplate up front, but it’s done to avoid many common pitfalls that
|
||||
new developers encounter, and it creates a project that’s easy to expand
|
||||
on. Once you become more comfortable with Flask, you can step out of
|
||||
this structure and take full advantage of Flask’s flexibility.</p>
|
||||
<img alt="screenshot of edit page" class="screenshot align-center" src="../_images/flaskr_edit.png" />
|
||||
<p><a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial">The tutorial project is available as an example in the Flask
|
||||
repository</a>, if you want to compare your project
|
||||
with the final product as you follow the tutorial.</p>
|
||||
<p>Continue to <a class="reference internal" href="layout.html"><span class="doc">Project Layout</span></a>.</p>
|
||||
<p>Have fun and make awesome applications!</p>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -97,18 +84,21 @@ with the final product as you follow the tutorial.</p>
|
|||
<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>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="../quickstart.html" title="previous chapter">Quickstart</a>
|
||||
<li>Next: <a href="layout.html" title="next chapter">Project Layout</a>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="deploy.html" title="previous chapter">Deploy to Production</a>
|
||||
<li>Next: <a href="../templating.html" title="next chapter">Templates</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -131,4 +121,4 @@ with the final product as you follow the tutorial.</p>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Make the Project Installable — Flask Documentation (3.2.x)</title>
|
||||
<title>Static Files — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Test Coverage" href="tests.html" />
|
||||
<link rel="prev" title="Blog Blueprint" href="blog.html" />
|
||||
<link rel="next" title="Blog Blueprint" href="blog.html" />
|
||||
<link rel="prev" title="Templates" href="templates.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,97 +28,80 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="tests.html" title="Test Coverage"
|
||||
<a href="blog.html" title="Blog Blueprint"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="blog.html" title="Blog Blueprint"
|
||||
<a href="templates.html" title="Templates"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Make the Project Installable</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Static Files</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="make-the-project-installable">
|
||||
<h1>Make the Project Installable<a class="headerlink" href="#make-the-project-installable" title="Link to this heading">¶</a></h1>
|
||||
<p>Making your project installable means that you can build a <em>wheel</em> file and install that
|
||||
in another environment, just like you installed Flask in your project’s environment.
|
||||
This makes deploying your project the same as installing any other library, so you’re
|
||||
using all the standard Python tools to manage everything.</p>
|
||||
<p>Installing also comes with other benefits that might not be obvious from
|
||||
the tutorial or as a new Python user, including:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Currently, Python and Flask understand how to use the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code>
|
||||
package only because you’re running from your project’s directory.
|
||||
Installing means you can import it no matter where you run from.</p></li>
|
||||
<li><p>You can manage your project’s dependencies just like other packages
|
||||
do, so <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">yourproject.whl</span></code> installs them.</p></li>
|
||||
<li><p>Test tools can isolate your test environment from your development
|
||||
environment.</p></li>
|
||||
</ul>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This is being introduced late in the tutorial, but in your future
|
||||
projects you should always start with this.</p>
|
||||
|
||||
<section id="static-files">
|
||||
<h1>Static Files<a class="headerlink" href="#static-files" title="Link to this heading">¶</a></h1>
|
||||
<p>The authentication views and templates work, but they look very plain
|
||||
right now. Some <a class="reference external" href="https://developer.mozilla.org/docs/Web/CSS">CSS</a> can be added to add style to the HTML layout you
|
||||
constructed. The style won’t change, so it’s a <em>static</em> file rather than
|
||||
a template.</p>
|
||||
<p>Flask automatically adds a <code class="docutils literal notranslate"><span class="pre">static</span></code> view that takes a path relative
|
||||
to the <code class="docutils literal notranslate"><span class="pre">flaskr/static</span></code> directory and serves it. The <code class="docutils literal notranslate"><span class="pre">base.html</span></code>
|
||||
template already has a link to the <code class="docutils literal notranslate"><span class="pre">style.css</span></code> file:</p>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'static'</span><span class="o">,</span> <span class="nv">filename</span><span class="o">=</span><span class="s1">'style.css'</span><span class="o">)</span> <span class="cp">}}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<section id="describe-the-project">
|
||||
<h2>Describe the Project<a class="headerlink" href="#describe-the-project" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file describes your project and how to build it.</p>
|
||||
<p>Besides CSS, other types of static files might be files with JavaScript
|
||||
functions, or a logo image. They are all placed under the
|
||||
<code class="docutils literal notranslate"><span class="pre">flaskr/static</span></code> directory and referenced with
|
||||
<code class="docutils literal notranslate"><span class="pre">url_for('static',</span> <span class="pre">filename='...')</span></code>.</p>
|
||||
<p>This tutorial isn’t focused on how to write CSS, so you can just copy
|
||||
the following into the <code class="docutils literal notranslate"><span class="pre">flaskr/static/style.css</span></code> file:</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="k">[project]</span>
|
||||
<span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"flaskr"</span>
|
||||
<span class="n">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.0.0"</span>
|
||||
<span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The basic blog app built in the Flask tutorial."</span>
|
||||
<span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="s2">"flask"</span><span class="p">,</span>
|
||||
<span class="p">]</span>
|
||||
|
||||
<span class="k">[build-system]</span>
|
||||
<span class="n">requires</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"flit_core<4"</span><span class="p">]</span>
|
||||
<span class="n">build-backend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"flit_core.buildapi"</span>
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/static/style.css</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-css notranslate"><div class="highlight"><pre><span></span><span class="nt">html</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-family</span><span class="p">:</span><span class="w"> </span><span class="kc">sans-serif</span><span class="p">;</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#eee</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">body</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">max-width</span><span class="p">:</span><span class="w"> </span><span class="mi">960</span><span class="kt">px</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="kc">white</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-family</span><span class="p">:</span><span class="w"> </span><span class="kc">serif</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#377ba8</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">a</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#377ba8</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">hr</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"> </span><span class="k">border-top</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="kc">lightgray</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="kc">lightgray</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="nt">a</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">text-decoration</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mf">0.25</span><span class="kt">rem</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">list-style</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="nt">li</span><span class="w"> </span><span class="nt">a</span><span class="o">,</span><span class="w"> </span><span class="nt">nav</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="nt">li</span><span class="w"> </span><span class="nt">span</span><span class="o">,</span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="p">.</span><span class="nc">action</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">block</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="kc">lightgray</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">flex-end</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">0.25</span><span class="kt">rem</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">flash</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#cae6f6</span><span class="p">;</span><span class="w"> </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="mh">#377ba8</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">flex-end</span><span class="p">;</span><span class="w"> </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mf">0.85</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">div</span><span class="p">:</span><span class="nd">first-of-type</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mf">1.5</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="p">.</span><span class="nc">about</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="kc">slategray</span><span class="p">;</span><span class="w"> </span><span class="k">font-style</span><span class="p">:</span><span class="w"> </span><span class="kc">italic</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="p">.</span><span class="nc">body</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre-line</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="p">:</span><span class="nd">last-child</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">form</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">flex-direction</span><span class="p">:</span><span class="w"> </span><span class="kc">column</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">label</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-weight</span><span class="p">:</span><span class="w"> </span><span class="kc">bold</span><span class="p">;</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">input</span><span class="o">,</span><span class="w"> </span><span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">textarea</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">textarea</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">min-height</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="k">resize</span><span class="p">:</span><span class="w"> </span><span class="kc">vertical</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">input</span><span class="p">.</span><span class="nc">danger</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#cc2f2e</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="nt">submit</span><span class="o">]</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">align-self</span><span class="p">:</span><span class="w"> </span><span class="kc">start</span><span class="p">;</span><span class="w"> </span><span class="k">min-width</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>See the official <a class="reference external" href="https://packaging.python.org/tutorials/packaging-projects/">Packaging tutorial</a> for more
|
||||
explanation of the files and options used.</p>
|
||||
</section>
|
||||
<section id="install-the-project">
|
||||
<h2>Install the Project<a class="headerlink" href="#install-the-project" title="Link to this heading">¶</a></h2>
|
||||
<p>Use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install your project in the virtual environment.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install -e .
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This tells pip to find <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> in the current directory and install the
|
||||
project in <em>editable</em> or <em>development</em> mode. Editable mode means that as you make
|
||||
changes to your local code, you’ll only need to re-install if you change the metadata
|
||||
about the project, such as its dependencies.</p>
|
||||
<p>You can observe that the project is now installed with <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">list</span></code>.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip list
|
||||
|
||||
Package Version Location
|
||||
-------------- --------- ----------------------------------
|
||||
click 6.7
|
||||
Flask 1.0
|
||||
flaskr 1.0.0 /home/user/Projects/flask-tutorial
|
||||
itsdangerous 0.24
|
||||
Jinja2 2.10
|
||||
MarkupSafe 1.0
|
||||
pip 9.0.3
|
||||
Werkzeug 0.14.1
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Nothing changes from how you’ve been running your project so far.
|
||||
<code class="docutils literal notranslate"><span class="pre">--app</span></code> is still set to <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> and <code class="docutils literal notranslate"><span class="pre">flask</span> <span class="pre">run</span></code> still runs
|
||||
the application, but you can call it from anywhere, not just the
|
||||
<code class="docutils literal notranslate"><span class="pre">flask-tutorial</span></code> directory.</p>
|
||||
<p>Continue to <a class="reference internal" href="tests.html"><span class="doc">Test Coverage</span></a>.</p>
|
||||
</section>
|
||||
<p>You can find a less compact version of <code class="docutils literal notranslate"><span class="pre">style.css</span></code> in the
|
||||
<a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial/flaskr/static/style.css">example code</a>.</p>
|
||||
<p>Go to <a class="reference external" href="http://127.0.0.1:5000/auth/login">http://127.0.0.1:5000/auth/login</a> and the page should look like the
|
||||
screenshot below.</p>
|
||||
<img alt="screenshot of login page" class="screenshot align-center" src="../_images/flaskr_login.png" />
|
||||
<p>You can read more about CSS from <a class="reference external" href="https://developer.mozilla.org/docs/Web/CSS">Mozilla’s documentation</a>. If
|
||||
you change a static file, refresh the browser page. If the change
|
||||
doesn’t show up, try clearing your browser’s cache.</p>
|
||||
<p>Continue to <a class="reference internal" href="blog.html"><span class="doc">Blog Blueprint</span></a>.</p>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -129,29 +112,20 @@ the application, but you can call it from anywhere, not just the
|
|||
<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="#">Make the Project Installable</a><ul>
|
||||
<li><a class="reference internal" href="#describe-the-project">Describe the Project</a></li>
|
||||
<li><a class="reference internal" href="#install-the-project">Install the Project</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="blog.html" title="previous chapter">Blog Blueprint</a>
|
||||
<li>Next: <a href="tests.html" title="next chapter">Test Coverage</a></ul>
|
||||
<li>Previous: <a href="templates.html" title="previous chapter">Templates</a>
|
||||
<li>Next: <a href="blog.html" title="next chapter">Blog Blueprint</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -175,4 +149,4 @@ the application, but you can call it from anywhere, not just the
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Project Layout — Flask Documentation (3.2.x)</title>
|
||||
<title>Templates — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Application Setup" href="factory.html" />
|
||||
<link rel="prev" title="Tutorial" href="index.html" />
|
||||
<link rel="next" title="Static Files" href="static.html" />
|
||||
<link rel="prev" title="Blueprints and Views" href="views.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,121 +28,185 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="factory.html" title="Application Setup"
|
||||
<a href="static.html" title="Static Files"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="index.html" title="Tutorial"
|
||||
<a href="views.html" title="Blueprints and Views"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Project Layout</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="project-layout">
|
||||
<h1>Project Layout<a class="headerlink" href="#project-layout" title="Link to this heading">¶</a></h1>
|
||||
<p>Create a project directory and enter it:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ mkdir flask-tutorial
|
||||
$ cd flask-tutorial
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Then follow the <a class="reference internal" href="../installation.html"><span class="doc">installation instructions</span></a> to set
|
||||
up a Python virtual environment and install Flask for your project.</p>
|
||||
<p>The tutorial will assume you’re working from the <code class="docutils literal notranslate"><span class="pre">flask-tutorial</span></code>
|
||||
directory from now on. The file names at the top of each code block are
|
||||
relative to this directory.</p>
|
||||
<hr class="docutils" />
|
||||
<p>A Flask application can be as simple as a single file.</p>
|
||||
|
||||
<section id="templates">
|
||||
<h1>Templates<a class="headerlink" href="#templates" title="Link to this heading">¶</a></h1>
|
||||
<p>You’ve written the authentication views for your application, but if
|
||||
you’re running the server and try to go to any of the URLs, you’ll see a
|
||||
<code class="docutils literal notranslate"><span class="pre">TemplateNotFound</span></code> error. That’s because the views are calling
|
||||
<a class="reference internal" href="../api.html#flask.render_template" title="flask.render_template"><code class="xref py py-func docutils literal notranslate"><span class="pre">render_template()</span></code></a>, but you haven’t written the templates yet.
|
||||
The template files will be stored in the <code class="docutils literal notranslate"><span class="pre">templates</span></code> directory inside
|
||||
the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package.</p>
|
||||
<p>Templates are files that contain static data as well as placeholders
|
||||
for dynamic data. A template is rendered with specific data to produce a
|
||||
final document. Flask uses the <a class="reference external" href="https://jinja.palletsprojects.com/templates/">Jinja</a> template library to render
|
||||
templates.</p>
|
||||
<p>In your application, you will use templates to render <a class="reference external" href="https://developer.mozilla.org/docs/Web/HTML">HTML</a> which
|
||||
will display in the user’s browser. In Flask, Jinja is configured to
|
||||
<em>autoescape</em> any data that is rendered in HTML templates. This means
|
||||
that it’s safe to render user input; any characters they’ve entered that
|
||||
could mess with the HTML, such as <code class="docutils literal notranslate"><span class="pre"><</span></code> and <code class="docutils literal notranslate"><span class="pre">></span></code> will be <em>escaped</em> with
|
||||
<em>safe</em> values that look the same in the browser but don’t cause unwanted
|
||||
effects.</p>
|
||||
<p>Jinja looks and behaves mostly like Python. Special delimiters are used
|
||||
to distinguish Jinja syntax from the static data in the template.
|
||||
Anything between <code class="docutils literal notranslate"><span class="pre">{{</span></code> and <code class="docutils literal notranslate"><span class="pre">}}</span></code> is an expression that will be output
|
||||
to the final document. <code class="docutils literal notranslate"><span class="pre">{%</span></code> and <code class="docutils literal notranslate"><span class="pre">%}</span></code> denotes a control flow
|
||||
statement like <code class="docutils literal notranslate"><span class="pre">if</span></code> and <code class="docutils literal notranslate"><span class="pre">for</span></code>. Unlike Python, blocks are denoted
|
||||
by start and end tags rather than indentation since static text within
|
||||
a block could change indentation.</p>
|
||||
<section id="the-base-layout">
|
||||
<h2>The Base Layout<a class="headerlink" href="#the-base-layout" title="Link to this heading">¶</a></h2>
|
||||
<p>Each page in the application will have the same basic layout around a
|
||||
different body. Instead of writing the entire HTML structure in each
|
||||
template, each template will <em>extend</em> a base template and override
|
||||
specific sections.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">hello.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||||
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
||||
<span class="k">return</span> <span class="s1">'Hello, World!'</span>
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/base.html</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp"><!doctype html></span>
|
||||
<span class="p"><</span><span class="nt">title</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span> - Flaskr<span class="p"></</span><span class="nt">title</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'static'</span><span class="o">,</span> <span class="nv">filename</span><span class="o">=</span><span class="s1">'style.css'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">nav</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Flaskr<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">ul</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">g.user</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">span</span><span class="p">></span><span class="cp">{{</span> <span class="nv">g.user</span><span class="o">[</span><span class="s1">'username'</span><span class="o">]</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">span</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'auth.logout'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Log Out<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'auth.register'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Register<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'auth.login'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Log In<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">nav</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">section</span> <span class="na">class</span><span class="o">=</span><span class="s">"content"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">message</span> <span class="k">in</span> <span class="nv">get_flashed_messages</span><span class="o">()</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"flash"</span><span class="p">></span><span class="cp">{{</span> <span class="nv">message</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">div</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>However, as a project gets bigger, it becomes overwhelming to keep all
|
||||
the code in one file. Python projects use <em>packages</em> to organize code
|
||||
into multiple modules that can be imported where needed, and the
|
||||
tutorial will do this as well.</p>
|
||||
<p>The project directory will contain:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">flaskr/</span></code>, a Python package containing your application code and
|
||||
files.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">tests/</span></code>, a directory containing test modules.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">.venv/</span></code>, a Python virtual environment where Flask and other
|
||||
dependencies are installed.</p></li>
|
||||
<li><p>Installation files telling Python how to install your project.</p></li>
|
||||
<li><p>Version control config, such as <a class="reference external" href="https://git-scm.com/">git</a>. You should make a habit of
|
||||
using some type of version control for all your projects, no matter
|
||||
the size.</p></li>
|
||||
<li><p>Any other project files you might add in the future.</p></li>
|
||||
</ul>
|
||||
<p>By the end, your project layout will look like this:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>/home/user/Projects/flask-tutorial
|
||||
├── flaskr/
|
||||
│ ├── __init__.py
|
||||
│ ├── db.py
|
||||
│ ├── schema.sql
|
||||
│ ├── auth.py
|
||||
│ ├── blog.py
|
||||
│ ├── templates/
|
||||
│ │ ├── base.html
|
||||
│ │ ├── auth/
|
||||
│ │ │ ├── login.html
|
||||
│ │ │ └── register.html
|
||||
│ │ └── blog/
|
||||
│ │ ├── create.html
|
||||
│ │ ├── index.html
|
||||
│ │ └── update.html
|
||||
│ └── static/
|
||||
│ └── style.css
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── data.sql
|
||||
│ ├── test_factory.py
|
||||
│ ├── test_db.py
|
||||
│ ├── test_auth.py
|
||||
│ └── test_blog.py
|
||||
├── .venv/
|
||||
├── pyproject.toml
|
||||
└── MANIFEST.in
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you’re using version control, the following files that are generated
|
||||
while running your project should be ignored. There may be other files
|
||||
based on the editor you use. In general, ignore files that you didn’t
|
||||
write. For example, with git:</p>
|
||||
<p><a class="reference internal" href="../api.html#flask.g" title="flask.g"><code class="xref py py-data docutils literal notranslate"><span class="pre">g</span></code></a> is automatically available in templates. Based on if
|
||||
<code class="docutils literal notranslate"><span class="pre">g.user</span></code> is set (from <code class="docutils literal notranslate"><span class="pre">load_logged_in_user</span></code>), either the username
|
||||
and a log out link are displayed, or links to register and log in
|
||||
are displayed. <a class="reference internal" href="../api.html#flask.url_for" title="flask.url_for"><code class="xref py py-func docutils literal notranslate"><span class="pre">url_for()</span></code></a> is also automatically available, and is
|
||||
used to generate URLs to views instead of writing them out manually.</p>
|
||||
<p>After the page title, and before the content, the template loops over
|
||||
each message returned by <a class="reference internal" href="../api.html#flask.get_flashed_messages" title="flask.get_flashed_messages"><code class="xref py py-func docutils literal notranslate"><span class="pre">get_flashed_messages()</span></code></a>. You used
|
||||
<a class="reference internal" href="../api.html#flask.flash" title="flask.flash"><code class="xref py py-func docutils literal notranslate"><span class="pre">flash()</span></code></a> in the views to show error messages, and this is the code
|
||||
that will display them.</p>
|
||||
<p>There are three blocks defined here that will be overridden in the other
|
||||
templates:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">title</span> <span class="pre">%}</span></code> will change the title displayed in the
|
||||
browser’s tab and window title.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">header</span> <span class="pre">%}</span></code> is similar to <code class="docutils literal notranslate"><span class="pre">title</span></code> but will change the
|
||||
title displayed on the page.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">content</span> <span class="pre">%}</span></code> is where the content of each page goes, such
|
||||
as the login form or a blog post.</p></li>
|
||||
</ol>
|
||||
<p>The base template is directly in the <code class="docutils literal notranslate"><span class="pre">templates</span></code> directory. To keep
|
||||
the others organized, the templates for a blueprint will be placed in a
|
||||
directory with the same name as the blueprint.</p>
|
||||
</section>
|
||||
<section id="register">
|
||||
<h2>Register<a class="headerlink" href="#register" title="Link to this heading">¶</a></h2>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">.gitignore</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>.venv/
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/auth/register.html</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
|
||||
*.pyc
|
||||
__pycache__/
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>Register<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
|
||||
instance/
|
||||
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"username"</span><span class="p">></span>Username<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">"username"</span> <span class="na">id</span><span class="o">=</span><span class="s">"username"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"password"</span><span class="p">></span>Password<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"password"</span> <span class="na">name</span><span class="o">=</span><span class="s">"password"</span> <span class="na">id</span><span class="o">=</span><span class="s">"password"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</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">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Continue to <a class="reference internal" href="factory.html"><span class="doc">Application Setup</span></a>.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">extends</span> <span class="pre">'base.html'</span> <span class="pre">%}</span></code> tells Jinja that this template should
|
||||
replace the blocks from the base template. All the rendered content must
|
||||
appear inside <code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">%}</span></code> tags that override blocks from the base
|
||||
template.</p>
|
||||
<p>A useful pattern used here is to place <code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">title</span> <span class="pre">%}</span></code> inside
|
||||
<code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">header</span> <span class="pre">%}</span></code>. This will set the title block and then output
|
||||
the value of it into the header block, so that both the window and page
|
||||
share the same title without writing it twice.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">input</span></code> tags are using the <code class="docutils literal notranslate"><span class="pre">required</span></code> attribute here. This tells
|
||||
the browser not to submit the form until those fields are filled in. If
|
||||
the user is using an older browser that doesn’t support that attribute,
|
||||
or if they are using something besides a browser to make requests, you
|
||||
still want to validate the data in the Flask view. It’s important to
|
||||
always fully validate the data on the server, even if the client does
|
||||
some validation as well.</p>
|
||||
</section>
|
||||
<section id="log-in">
|
||||
<h2>Log In<a class="headerlink" href="#log-in" title="Link to this heading">¶</a></h2>
|
||||
<p>This is identical to the register template except for the title and
|
||||
submit button.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/auth/login.html</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>Log In<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"username"</span><span class="p">></span>Username<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">"username"</span> <span class="na">id</span><span class="o">=</span><span class="s">"username"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"password"</span><span class="p">></span>Password<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"password"</span> <span class="na">name</span><span class="o">=</span><span class="s">"password"</span> <span class="na">id</span><span class="o">=</span><span class="s">"password"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</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">"Log In"</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="register-a-user">
|
||||
<h2>Register A User<a class="headerlink" href="#register-a-user" title="Link to this heading">¶</a></h2>
|
||||
<p>Now that the authentication templates are written, you can register a
|
||||
user. Make sure the server is still running (<code class="docutils literal notranslate"><span class="pre">flask</span> <span class="pre">run</span></code> if it’s not),
|
||||
then go to <a class="reference external" href="http://127.0.0.1:5000/auth/register">http://127.0.0.1:5000/auth/register</a>.</p>
|
||||
<p>Try clicking the “Register” button without filling out the form and see
|
||||
that the browser shows an error message. Try removing the <code class="docutils literal notranslate"><span class="pre">required</span></code>
|
||||
attributes from the <code class="docutils literal notranslate"><span class="pre">register.html</span></code> template and click “Register”
|
||||
again. Instead of the browser showing an error, the page will reload and
|
||||
the error from <a class="reference internal" href="../api.html#flask.flash" title="flask.flash"><code class="xref py py-func docutils literal notranslate"><span class="pre">flash()</span></code></a> in the view will be shown.</p>
|
||||
<p>Fill out a username and password and you’ll be redirected to the login
|
||||
page. Try entering an incorrect username, or the correct username and
|
||||
incorrect password. If you log in you’ll get an error because there’s
|
||||
no <code class="docutils literal notranslate"><span class="pre">index</span></code> view to redirect to yet.</p>
|
||||
<p>Continue to <a class="reference internal" href="static.html"><span class="doc">Static Files</span></a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -153,20 +217,31 @@ build/
|
|||
<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="#">Templates</a><ul>
|
||||
<li><a class="reference internal" href="#the-base-layout">The Base Layout</a></li>
|
||||
<li><a class="reference internal" href="#register">Register</a></li>
|
||||
<li><a class="reference internal" href="#log-in">Log In</a></li>
|
||||
<li><a class="reference internal" href="#register-a-user">Register A User</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="index.html" title="previous chapter">Tutorial</a>
|
||||
<li>Next: <a href="factory.html" title="next chapter">Application Setup</a></ul>
|
||||
<li>Previous: <a href="views.html" title="previous chapter">Blueprints and Views</a>
|
||||
<li>Next: <a href="static.html" title="next chapter">Static Files</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -190,4 +265,4 @@ build/
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Keep Developing! — Flask Documentation (3.2.x)</title>
|
||||
<title>Test Coverage — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Templates" href="../templating.html" />
|
||||
<link rel="prev" title="Deploy to Production" href="deploy.html" />
|
||||
<link rel="next" title="Deploy to Production" href="deploy.html" />
|
||||
<link rel="prev" title="Make the Project Installable" href="install.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,52 +28,537 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="../templating.html" title="Templates"
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
<a href="install.html" title="Make the Project Installable"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Keep Developing!</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Test Coverage</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="keep-developing">
|
||||
<h1>Keep Developing!<a class="headerlink" href="#keep-developing" title="Link to this heading">¶</a></h1>
|
||||
<p>You’ve learned about quite a few Flask and Python concepts throughout
|
||||
the tutorial. Go back and review the tutorial and compare your code with
|
||||
the steps you took to get there. Compare your project to the
|
||||
<a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial">example project</a>, which might look a bit
|
||||
different due to the step-by-step nature of the tutorial.</p>
|
||||
<p>There’s a lot more to Flask than what you’ve seen so far. Even so,
|
||||
you’re now equipped to start developing your own web applications. Check
|
||||
out the <a class="reference internal" href="../quickstart.html"><span class="doc">Quickstart</span></a> for an overview of what Flask can do, then
|
||||
dive into the docs to keep learning. Flask uses <a class="reference external" href="https://palletsprojects.com/p/jinja/">Jinja</a>, <a class="reference external" href="https://palletsprojects.com/p/click/">Click</a>,
|
||||
<a class="reference external" href="https://palletsprojects.com/p/werkzeug/">Werkzeug</a>, and <a class="reference external" href="https://palletsprojects.com/p/itsdangerous/">ItsDangerous</a> behind the scenes, and they all have
|
||||
their own documentation too. You’ll also be interested in
|
||||
<a class="reference internal" href="../extensions.html"><span class="doc">Extensions</span></a> which make tasks like working with the database or
|
||||
validating form data easier and more powerful.</p>
|
||||
<p>If you want to keep developing your Flaskr project, here are some ideas
|
||||
for what to try next:</p>
|
||||
<ul class="simple">
|
||||
<li><p>A detail view to show a single post. Click a post’s title to go to
|
||||
its page.</p></li>
|
||||
<li><p>Like / unlike a post.</p></li>
|
||||
<li><p>Comments.</p></li>
|
||||
<li><p>Tags. Clicking a tag shows all the posts with that tag.</p></li>
|
||||
<li><p>A search box that filters the index page by name.</p></li>
|
||||
<li><p>Paged display. Only show 5 posts per page.</p></li>
|
||||
<li><p>Upload an image to go along with a post.</p></li>
|
||||
<li><p>Format posts using Markdown.</p></li>
|
||||
<li><p>An RSS feed of new posts.</p></li>
|
||||
</ul>
|
||||
<p>Have fun and make awesome applications!</p>
|
||||
|
||||
<section id="test-coverage">
|
||||
<h1>Test Coverage<a class="headerlink" href="#test-coverage" title="Link to this heading">¶</a></h1>
|
||||
<p>Writing unit tests for your application lets you check that the code
|
||||
you wrote works the way you expect. Flask provides a test client that
|
||||
simulates requests to the application and returns the response data.</p>
|
||||
<p>You should test as much of your code as possible. Code in functions only
|
||||
runs when the function is called, and code in branches, such as <code class="docutils literal notranslate"><span class="pre">if</span></code>
|
||||
blocks, only runs when the condition is met. You want to make sure that
|
||||
each function is tested with data that covers each branch.</p>
|
||||
<p>The closer you get to 100% coverage, the more comfortable you can be
|
||||
that making a change won’t unexpectedly change other behavior. However,
|
||||
100% coverage doesn’t guarantee that your application doesn’t have bugs.
|
||||
In particular, it doesn’t test how the user interacts with the
|
||||
application in the browser. Despite this, test coverage is an important
|
||||
tool to use during development.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This is being introduced late in the tutorial, but in your future
|
||||
projects you should test as you develop.</p>
|
||||
</div>
|
||||
<p>You’ll use <a class="reference external" href="https://pytest.readthedocs.io/">pytest</a> and <a class="reference external" href="https://coverage.readthedocs.io/">coverage</a> to test and measure your code.
|
||||
Install them both:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install pytest coverage
|
||||
</pre></div>
|
||||
</div>
|
||||
<section id="setup-and-fixtures">
|
||||
<h2>Setup and Fixtures<a class="headerlink" href="#setup-and-fixtures" title="Link to this heading">¶</a></h2>
|
||||
<p>The test code is located in the <code class="docutils literal notranslate"><span class="pre">tests</span></code> directory. This directory is
|
||||
<em>next to</em> the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package, not inside it. The
|
||||
<code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code> file contains setup functions called <em>fixtures</em>
|
||||
that each test will use. Tests are in Python modules that start with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>, and each test function in those modules also starts with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>.</p>
|
||||
<p>Each test will create a new temporary database file and populate some
|
||||
data that will be used in the tests. Write a SQL file to insert that
|
||||
data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/data.sql</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-sql notranslate"><div class="highlight"><pre><span></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'</span><span class="p">),</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'other'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'</span><span class="p">);</span>
|
||||
|
||||
<span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">body</span><span class="p">,</span><span class="w"> </span><span class="n">author_id</span><span class="p">,</span><span class="w"> </span><span class="n">created</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test title'</span><span class="p">,</span><span class="w"> </span><span class="s1">'test'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">x</span><span class="s1">'0a'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">'body'</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">'2018-01-01 00:00:00'</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture will call the factory and pass <code class="docutils literal notranslate"><span class="pre">test_config</span></code> to
|
||||
configure the application and database for testing instead of using your
|
||||
local development configuration.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">tempfile</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span><span class="p">,</span> <span class="n">init_db</span>
|
||||
|
||||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span> <span class="s1">'data.sql'</span><span class="p">),</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||||
<span class="n">_data_sql</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf8'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">app</span><span class="p">():</span>
|
||||
<span class="n">db_fd</span><span class="p">,</span> <span class="n">db_path</span> <span class="o">=</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">mkstemp</span><span class="p">()</span>
|
||||
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">({</span>
|
||||
<span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="s1">'DATABASE'</span><span class="p">:</span> <span class="n">db_path</span><span class="p">,</span>
|
||||
<span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">init_db</span><span class="p">()</span>
|
||||
<span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">executescript</span><span class="p">(</span><span class="n">_data_sql</span><span class="p">)</span>
|
||||
|
||||
<span class="k">yield</span> <span class="n">app</span>
|
||||
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">db_fd</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">unlink</span><span class="p">(</span><span class="n">db_path</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">client</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_client</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">runner</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_cli_runner</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">tempfile.mkstemp()</span></code></a> creates and opens a temporary file, returning
|
||||
the file descriptor and the path to it. The <code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> path is
|
||||
overridden so it points to this temporary path instead of the instance
|
||||
folder. After setting the path, the database tables are created and the
|
||||
test data is inserted. After the test is over, the temporary file is
|
||||
closed and removed.</p>
|
||||
<p><a class="reference internal" href="../config.html#TESTING" title="TESTING"><code class="xref py py-data docutils literal notranslate"><span class="pre">TESTING</span></code></a> tells Flask that the app is in test mode. Flask changes
|
||||
some internal behavior so it’s easier to test, and other extensions can
|
||||
also use the flag to make testing them easier.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture calls
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_client" title="flask.Flask.test_client"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_client()</span></code></a> with the application
|
||||
object created by the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture. Tests will use the client to make
|
||||
requests to the application without running the server.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture is similar to <code class="docutils literal notranslate"><span class="pre">client</span></code>.
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_cli_runner" title="flask.Flask.test_cli_runner"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_cli_runner()</span></code></a> creates a runner
|
||||
that can call the Click commands registered with the application.</p>
|
||||
<p>Pytest uses fixtures by matching their function names with the names
|
||||
of arguments in the test functions. For example, the <code class="docutils literal notranslate"><span class="pre">test_hello</span></code>
|
||||
function you’ll write next takes a <code class="docutils literal notranslate"><span class="pre">client</span></code> argument. Pytest matches
|
||||
that with the <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture function, calls it, and passes the
|
||||
returned value to the test function.</p>
|
||||
</section>
|
||||
<section id="factory">
|
||||
<h2>Factory<a class="headerlink" href="#factory" title="Link to this heading">¶</a></h2>
|
||||
<p>There’s not much to test about the factory itself. Most of the code will
|
||||
be executed for each test already, so if something fails the other tests
|
||||
will notice.</p>
|
||||
<p>The only behavior that can change is passing test config. If config is
|
||||
not passed, there should be some default configuration, otherwise the
|
||||
configuration should be overridden.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_factory.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_config</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="ow">not</span> <span class="n">create_app</span><span class="p">()</span><span class="o">.</span><span class="n">testing</span>
|
||||
<span class="k">assert</span> <span class="n">create_app</span><span class="p">({</span><span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span><span class="o">.</span><span class="n">testing</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_hello</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span> <span class="o">==</span> <span class="sa">b</span><span class="s1">'Hello, World!'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>You added the <code class="docutils literal notranslate"><span class="pre">hello</span></code> route as an example when writing the factory at
|
||||
the beginning of the tutorial. It returns “Hello, World!”, so the test
|
||||
checks that the response data matches.</p>
|
||||
</section>
|
||||
<section id="database">
|
||||
<h2>Database<a class="headerlink" href="#database" title="Link to this heading">¶</a></h2>
|
||||
<p>Within an application context, <code class="docutils literal notranslate"><span class="pre">get_db</span></code> should return the same
|
||||
connection each time it’s called. After the context, the connection
|
||||
should be closed.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">sqlite3</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_get_close_db</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">db</span> <span class="ow">is</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">ProgrammingError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT 1'</span><span class="p">)</span>
|
||||
|
||||
<span class="k">assert</span> <span class="s1">'closed'</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">init-db</span></code> command should call the <code class="docutils literal notranslate"><span class="pre">init_db</span></code> function and output
|
||||
a message.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_init_db_command</span><span class="p">(</span><span class="n">runner</span><span class="p">,</span> <span class="n">monkeypatch</span><span class="p">):</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Recorder</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="n">called</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">fake_init_db</span><span class="p">():</span>
|
||||
<span class="n">Recorder</span><span class="o">.</span><span class="n">called</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="s1">'flaskr.db.init_db'</span><span class="p">,</span> <span class="n">fake_init_db</span><span class="p">)</span>
|
||||
<span class="n">result</span> <span class="o">=</span> <span class="n">runner</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="s1">'init-db'</span><span class="p">])</span>
|
||||
<span class="k">assert</span> <span class="s1">'Initialized'</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">output</span>
|
||||
<span class="k">assert</span> <span class="n">Recorder</span><span class="o">.</span><span class="n">called</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>This test uses Pytest’s <code class="docutils literal notranslate"><span class="pre">monkeypatch</span></code> fixture to replace the
|
||||
<code class="docutils literal notranslate"><span class="pre">init_db</span></code> function with one that records that it’s been called. The
|
||||
<code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture you wrote above is used to call the <code class="docutils literal notranslate"><span class="pre">init-db</span></code>
|
||||
command by name.</p>
|
||||
</section>
|
||||
<section id="authentication">
|
||||
<h2>Authentication<a class="headerlink" href="#authentication" title="Link to this heading">¶</a></h2>
|
||||
<p>For most of the views, a user needs to be logged in. The easiest way to
|
||||
do this in tests is to make a <code class="docutils literal notranslate"><span class="pre">POST</span></code> request to the <code class="docutils literal notranslate"><span class="pre">login</span></code> view
|
||||
with the client. Rather than writing that out every time, you can write
|
||||
a class with methods to do that, and use a fixture to pass it the client
|
||||
for each test.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id6">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id6" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">AuthActions</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">client</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_client</span> <span class="o">=</span> <span class="n">client</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">username</span><span class="o">=</span><span class="s1">'test'</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s1">'test'</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/login'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">logout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/logout'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">auth</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">AuthActions</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture, you can call <code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> in a test to
|
||||
log in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user, which was inserted as part of the test
|
||||
data in the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">register</span></code> view should render successfully on <code class="docutils literal notranslate"><span class="pre">GET</span></code>. On <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
with valid form data, it should redirect to the login URL and the user’s
|
||||
data should be in the database. Invalid data should display error
|
||||
messages.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id7">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id7" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">g</span><span class="p">,</span> <span class="n">session</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/register'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT * FROM user WHERE username = 'a'"</span><span class="p">,</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Username is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Password is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'already registered'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register_validate_input</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.get" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.get()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request
|
||||
and returns the <a class="reference internal" href="../api.html#flask.Response" title="flask.Response"><code class="xref py py-class docutils literal notranslate"><span class="pre">Response</span></code></a> object returned by Flask. Similarly,
|
||||
<a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.post" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.post()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
request, converting the <code class="docutils literal notranslate"><span class="pre">data</span></code> dict into form data.</p>
|
||||
<p>To test that the page renders successfully, a simple request is made and
|
||||
checked for a <code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> <a class="reference internal" href="../api.html#flask.Response.status_code" title="flask.Response.status_code"><code class="xref py py-attr docutils literal notranslate"><span class="pre">status_code</span></code></a>. If
|
||||
rendering failed, Flask would return a <code class="docutils literal notranslate"><span class="pre">500</span> <span class="pre">Internal</span> <span class="pre">Server</span> <span class="pre">Error</span></code>
|
||||
code.</p>
|
||||
<p><code class="xref py py-attr docutils literal notranslate"><span class="pre">headers</span></code> will have a <code class="docutils literal notranslate"><span class="pre">Location</span></code> header with the login
|
||||
URL when the register view redirects to the login view.</p>
|
||||
<p><a class="reference internal" href="../api.html#flask.Response.data" title="flask.Response.data"><code class="xref py py-attr docutils literal notranslate"><span class="pre">data</span></code></a> contains the body of the response as bytes. If
|
||||
you expect a certain value to render on the page, check that it’s in
|
||||
<code class="docutils literal notranslate"><span class="pre">data</span></code>. Bytes must be compared to bytes. If you want to compare text,
|
||||
use <a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/wrappers/#werkzeug.wrappers.Response.get_data" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">get_data(as_text=True)</span></code></a>
|
||||
instead.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">pytest.mark.parametrize</span></code> tells Pytest to run the same test function
|
||||
with different arguments. You use it here to test different invalid
|
||||
input and error messages without writing the same code three times.</p>
|
||||
<p>The tests for the <code class="docutils literal notranslate"><span class="pre">login</span></code> view are very similar to those for
|
||||
<code class="docutils literal notranslate"><span class="pre">register</span></code>. Rather than testing the data in the database,
|
||||
<a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should have <code class="docutils literal notranslate"><span class="pre">user_id</span></code> set after logging in.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id8">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id8" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_login</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/login'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span>
|
||||
<span class="k">assert</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'test'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect username.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect password.'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_validate_input</span><span class="p">(</span><span class="n">auth</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Using <code class="docutils literal notranslate"><span class="pre">client</span></code> in a <code class="docutils literal notranslate"><span class="pre">with</span></code> block allows accessing context variables
|
||||
such as <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> after the response is returned. Normally,
|
||||
accessing <code class="docutils literal notranslate"><span class="pre">session</span></code> outside of a request would raise an error.</p>
|
||||
<p>Testing <code class="docutils literal notranslate"><span class="pre">logout</span></code> is the opposite of <code class="docutils literal notranslate"><span class="pre">login</span></code>. <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should
|
||||
not contain <code class="docutils literal notranslate"><span class="pre">user_id</span></code> after logging out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id9">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id9" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_logout</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">logout</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="s1">'user_id'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">session</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="blog">
|
||||
<h2>Blog<a class="headerlink" href="#blog" title="Link to this heading">¶</a></h2>
|
||||
<p>All the blog views use the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture you wrote earlier. Call
|
||||
<code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> and subsequent requests from the client will be logged
|
||||
in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">index</span></code> view should display information about the post that was
|
||||
added with the test data. When logged in as the author, there should be
|
||||
a link to edit the post.</p>
|
||||
<p>You can also test some more authentication behavior while testing the
|
||||
<code class="docutils literal notranslate"><span class="pre">index</span></code> view. When not logged in, each page shows links to log in or
|
||||
register. When logged in, there’s a link to log out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id10">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id10" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_index</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Log In"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Register"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Log Out'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test title'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'by test on 2018-01-01'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test</span><span class="se">\n</span><span class="s1">body'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>A user must be logged in to access the <code class="docutils literal notranslate"><span class="pre">create</span></code>, <code class="docutils literal notranslate"><span class="pre">update</span></code>, and
|
||||
<code class="docutils literal notranslate"><span class="pre">delete</span></code> views. The logged in user must be the author of the post to
|
||||
access <code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code>, otherwise a <code class="docutils literal notranslate"><span class="pre">403</span> <span class="pre">Forbidden</span></code> status
|
||||
is returned. If a <code class="docutils literal notranslate"><span class="pre">post</span></code> with the given <code class="docutils literal notranslate"><span class="pre">id</span></code> doesn’t exist,
|
||||
<code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code> should return <code class="docutils literal notranslate"><span class="pre">404</span> <span class="pre">Not</span> <span class="pre">Found</span></code>.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id11">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id11" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_author_required</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="c1"># change the post author to another user</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'UPDATE post SET author_id = 2 WHERE id = 1'</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="c1"># current user can't modify other user's post</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="c1"># current user doesn't see edit link</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/2/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/2/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_exists_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">404</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">create</span></code> and <code class="docutils literal notranslate"><span class="pre">update</span></code> views should render and return a
|
||||
<code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> status for a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request. When valid data is sent in a
|
||||
<code class="docutils literal notranslate"><span class="pre">POST</span></code> request, <code class="docutils literal notranslate"><span class="pre">create</span></code> should insert the new post data into the
|
||||
database, and <code class="docutils literal notranslate"><span class="pre">update</span></code> should modify the existing data. Both pages
|
||||
should show an error message on invalid data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id12">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id12" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_create</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'created'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">count</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT COUNT(id) FROM post'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">assert</span> <span class="n">count</span> <span class="o">==</span> <span class="mi">2</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_update</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'updated'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'updated'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_create_update_validate</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Title is required.'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">delete</span></code> view should redirect to the index URL and the post should
|
||||
no longer exist in the database.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id13">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id13" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_delete</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span> <span class="ow">is</span> <span class="kc">None</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="running-the-tests">
|
||||
<h2>Running the Tests<a class="headerlink" href="#running-the-tests" title="Link to this heading">¶</a></h2>
|
||||
<p>Some extra configuration, which is not required but makes running tests with coverage
|
||||
less verbose, can be added to the project’s <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id14">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code></span><a class="headerlink" href="#id14" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="k">[tool.pytest.ini_options]</span>
|
||||
<span class="n">testpaths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"tests"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">[tool.coverage.run]</span>
|
||||
<span class="n">branch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="n">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"flaskr"</span><span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>To run the tests, use the <code class="docutils literal notranslate"><span class="pre">pytest</span></code> command. It will find and run all
|
||||
the test functions you’ve written.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pytest
|
||||
|
||||
========================= test session starts ==========================
|
||||
platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
|
||||
rootdir: /home/user/Projects/flask-tutorial
|
||||
collected 23 items
|
||||
|
||||
tests/test_auth.py ........ [ 34%]
|
||||
tests/test_blog.py ............ [ 86%]
|
||||
tests/test_db.py .. [ 95%]
|
||||
tests/test_factory.py .. [100%]
|
||||
|
||||
====================== 24 passed in 0.64 seconds =======================
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If any tests fail, pytest will show the error that was raised. You can
|
||||
run <code class="docutils literal notranslate"><span class="pre">pytest</span> <span class="pre">-v</span></code> to get a list of each test function rather than dots.</p>
|
||||
<p>To measure the code coverage of your tests, use the <code class="docutils literal notranslate"><span class="pre">coverage</span></code> command
|
||||
to run pytest instead of running it directly.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage run -m pytest
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can either view a simple coverage report in the terminal:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage report
|
||||
|
||||
Name Stmts Miss Branch BrPart Cover
|
||||
------------------------------------------------------
|
||||
flaskr/__init__.py 21 0 2 0 100%
|
||||
flaskr/auth.py 54 0 22 0 100%
|
||||
flaskr/blog.py 54 0 16 0 100%
|
||||
flaskr/db.py 24 0 4 0 100%
|
||||
------------------------------------------------------
|
||||
TOTAL 153 0 44 0 100%
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>An HTML report allows you to see which lines were covered in each file:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage html
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This generates files in the <code class="docutils literal notranslate"><span class="pre">htmlcov</span></code> directory. Open
|
||||
<code class="docutils literal notranslate"><span class="pre">htmlcov/index.html</span></code> in your browser to see the report.</p>
|
||||
<p>Continue to <a class="reference internal" href="deploy.html"><span class="doc">Deploy to Production</span></a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -84,20 +569,33 @@ its page.</p></li>
|
|||
<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="#">Test Coverage</a><ul>
|
||||
<li><a class="reference internal" href="#setup-and-fixtures">Setup and Fixtures</a></li>
|
||||
<li><a class="reference internal" href="#factory">Factory</a></li>
|
||||
<li><a class="reference internal" href="#database">Database</a></li>
|
||||
<li><a class="reference internal" href="#authentication">Authentication</a></li>
|
||||
<li><a class="reference internal" href="#blog">Blog</a></li>
|
||||
<li><a class="reference internal" href="#running-the-tests">Running the Tests</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="deploy.html" title="previous chapter">Deploy to Production</a>
|
||||
<li>Next: <a href="../templating.html" title="next chapter">Templates</a></ul>
|
||||
<li>Previous: <a href="install.html" title="previous chapter">Make the Project Installable</a>
|
||||
<li>Next: <a href="deploy.html" title="next chapter">Deploy to Production</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -121,4 +619,4 @@ its page.</p></li>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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>Static Files — Flask Documentation (3.2.x)</title>
|
||||
<title>Blueprints and Views — 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>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<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="Blog Blueprint" href="blog.html" />
|
||||
<link rel="prev" title="Templates" href="templates.html" />
|
||||
<link rel="next" title="Templates" href="templates.html" />
|
||||
<link rel="prev" title="Define and Access the Database" href="database.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,80 +28,298 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="blog.html" title="Blog Blueprint"
|
||||
<a href="templates.html" title="Templates"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="templates.html" title="Templates"
|
||||
<a href="database.html" title="Define and Access the Database"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Static Files</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Blueprints and Views</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="static-files">
|
||||
<h1>Static Files<a class="headerlink" href="#static-files" title="Link to this heading">¶</a></h1>
|
||||
<p>The authentication views and templates work, but they look very plain
|
||||
right now. Some <a class="reference external" href="https://developer.mozilla.org/docs/Web/CSS">CSS</a> can be added to add style to the HTML layout you
|
||||
constructed. The style won’t change, so it’s a <em>static</em> file rather than
|
||||
a template.</p>
|
||||
<p>Flask automatically adds a <code class="docutils literal notranslate"><span class="pre">static</span></code> view that takes a path relative
|
||||
to the <code class="docutils literal notranslate"><span class="pre">flaskr/static</span></code> directory and serves it. The <code class="docutils literal notranslate"><span class="pre">base.html</span></code>
|
||||
template already has a link to the <code class="docutils literal notranslate"><span class="pre">style.css</span></code> file:</p>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'static'</span><span class="o">,</span> <span class="nv">filename</span><span class="o">=</span><span class="s1">'style.css'</span><span class="o">)</span> <span class="cp">}}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Besides CSS, other types of static files might be files with JavaScript
|
||||
functions, or a logo image. They are all placed under the
|
||||
<code class="docutils literal notranslate"><span class="pre">flaskr/static</span></code> directory and referenced with
|
||||
<code class="docutils literal notranslate"><span class="pre">url_for('static',</span> <span class="pre">filename='...')</span></code>.</p>
|
||||
<p>This tutorial isn’t focused on how to write CSS, so you can just copy
|
||||
the following into the <code class="docutils literal notranslate"><span class="pre">flaskr/static/style.css</span></code> file:</p>
|
||||
|
||||
<section id="blueprints-and-views">
|
||||
<h1>Blueprints and Views<a class="headerlink" href="#blueprints-and-views" title="Link to this heading">¶</a></h1>
|
||||
<p>A view function is the code you write to respond to requests to your
|
||||
application. Flask uses patterns to match the incoming request URL to
|
||||
the view that should handle it. The view returns data that Flask turns
|
||||
into an outgoing response. Flask can also go the other direction and
|
||||
generate a URL to a view based on its name and arguments.</p>
|
||||
<section id="create-a-blueprint">
|
||||
<h2>Create a Blueprint<a class="headerlink" href="#create-a-blueprint" title="Link to this heading">¶</a></h2>
|
||||
<p>A <a class="reference internal" href="../api.html#flask.Blueprint" title="flask.Blueprint"><code class="xref py py-class docutils literal notranslate"><span class="pre">Blueprint</span></code></a> is a way to organize a group of related views and
|
||||
other code. Rather than registering views and other code directly with
|
||||
an application, they are registered with a blueprint. Then the blueprint
|
||||
is registered with the application when it is available in the factory
|
||||
function.</p>
|
||||
<p>Flaskr will have two blueprints, one for authentication functions and
|
||||
one for the blog posts functions. The code for each blueprint will go
|
||||
in a separate module. Since the blog needs to know about authentication,
|
||||
you’ll write the authentication one first.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/static/style.css</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-css notranslate"><div class="highlight"><pre><span></span><span class="nt">html</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-family</span><span class="p">:</span><span class="w"> </span><span class="kc">sans-serif</span><span class="p">;</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#eee</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">body</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">max-width</span><span class="p">:</span><span class="w"> </span><span class="mi">960</span><span class="kt">px</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="kc">white</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-family</span><span class="p">:</span><span class="w"> </span><span class="kc">serif</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#377ba8</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">a</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#377ba8</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">hr</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"> </span><span class="k">border-top</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="kc">lightgray</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="kc">lightgray</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="nt">a</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">text-decoration</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mf">0.25</span><span class="kt">rem</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">list-style</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">nav</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="nt">li</span><span class="w"> </span><span class="nt">a</span><span class="o">,</span><span class="w"> </span><span class="nt">nav</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="nt">li</span><span class="w"> </span><span class="nt">span</span><span class="o">,</span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="p">.</span><span class="nc">action</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">block</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="kc">lightgray</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">flex-end</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">0.25</span><span class="kt">rem</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">flash</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#cae6f6</span><span class="p">;</span><span class="w"> </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="mh">#377ba8</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">flex-end</span><span class="p">;</span><span class="w"> </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mf">0.85</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">div</span><span class="p">:</span><span class="nd">first-of-type</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="nt">header</span><span class="w"> </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mf">1.5</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="p">.</span><span class="nc">about</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="kc">slategray</span><span class="p">;</span><span class="w"> </span><span class="k">font-style</span><span class="p">:</span><span class="w"> </span><span class="kc">italic</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">post</span><span class="w"> </span><span class="p">.</span><span class="nc">body</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre-line</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="p">:</span><span class="nd">last-child</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">form</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">flex-direction</span><span class="p">:</span><span class="w"> </span><span class="kc">column</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">label</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">font-weight</span><span class="p">:</span><span class="w"> </span><span class="kc">bold</span><span class="p">;</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">input</span><span class="o">,</span><span class="w"> </span><span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">textarea</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">.</span><span class="nc">content</span><span class="w"> </span><span class="nt">textarea</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">min-height</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="k">resize</span><span class="p">:</span><span class="w"> </span><span class="kc">vertical</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">input</span><span class="p">.</span><span class="nc">danger</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#cc2f2e</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="nt">submit</span><span class="o">]</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">align-self</span><span class="p">:</span><span class="w"> </span><span class="kc">start</span><span class="p">;</span><span class="w"> </span><span class="k">min-width</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="kt">em</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/auth.py</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">functools</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">Blueprint</span><span class="p">,</span> <span class="n">flash</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">redirect</span><span class="p">,</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">url_for</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">werkzeug.security</span><span class="w"> </span><span class="kn">import</span> <span class="n">check_password_hash</span><span class="p">,</span> <span class="n">generate_password_hash</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
<span class="n">bp</span> <span class="o">=</span> <span class="n">Blueprint</span><span class="p">(</span><span class="s1">'auth'</span><span class="p">,</span> <span class="vm">__name__</span><span class="p">,</span> <span class="n">url_prefix</span><span class="o">=</span><span class="s1">'/auth'</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>You can find a less compact version of <code class="docutils literal notranslate"><span class="pre">style.css</span></code> in the
|
||||
<a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial/flaskr/static/style.css">example code</a>.</p>
|
||||
<p>Go to <a class="reference external" href="http://127.0.0.1:5000/auth/login">http://127.0.0.1:5000/auth/login</a> and the page should look like the
|
||||
screenshot below.</p>
|
||||
<img alt="screenshot of login page" class="screenshot align-center" src="../_images/flaskr_login.png" />
|
||||
<p>You can read more about CSS from <a class="reference external" href="https://developer.mozilla.org/docs/Web/CSS">Mozilla’s documentation</a>. If
|
||||
you change a static file, refresh the browser page. If the change
|
||||
doesn’t show up, try clearing your browser’s cache.</p>
|
||||
<p>Continue to <a class="reference internal" href="blog.html"><span class="doc">Blog Blueprint</span></a>.</p>
|
||||
<p>This creates a <a class="reference internal" href="../api.html#flask.Blueprint" title="flask.Blueprint"><code class="xref py py-class docutils literal notranslate"><span class="pre">Blueprint</span></code></a> named <code class="docutils literal notranslate"><span class="pre">'auth'</span></code>. Like the application
|
||||
object, the blueprint needs to know where it’s defined, so <code class="docutils literal notranslate"><span class="pre">__name__</span></code>
|
||||
is passed as the second argument. The <code class="docutils literal notranslate"><span class="pre">url_prefix</span></code> will be prepended
|
||||
to all the URLs associated with the blueprint.</p>
|
||||
<p>Import and register the blueprint from the factory using
|
||||
<a class="reference internal" href="../api.html#flask.Flask.register_blueprint" title="flask.Flask.register_blueprint"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.register_blueprint()</span></code></a>. Place the
|
||||
new code at the end of the factory function before returning the app.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/__init__.py</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">create_app</span><span class="p">():</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="o">...</span>
|
||||
<span class="c1"># existing code omitted</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.</span><span class="w"> </span><span class="kn">import</span> <span class="n">auth</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">register_blueprint</span><span class="p">(</span><span class="n">auth</span><span class="o">.</span><span class="n">bp</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">app</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The authentication blueprint will have views to register new users and
|
||||
to log in and log out.</p>
|
||||
</section>
|
||||
<section id="the-first-view-register">
|
||||
<h2>The First View: Register<a class="headerlink" href="#the-first-view-register" title="Link to this heading">¶</a></h2>
|
||||
<p>When the user visits the <code class="docutils literal notranslate"><span class="pre">/auth/register</span></code> URL, the <code class="docutils literal notranslate"><span class="pre">register</span></code> view
|
||||
will return <a class="reference external" href="https://developer.mozilla.org/docs/Web/HTML">HTML</a> with a form for them to fill out. When they submit
|
||||
the form, it will validate their input and either show the form again
|
||||
with an error message or create the new user and go to the login page.</p>
|
||||
<p>For now you will just write the view code. On the next page, you’ll
|
||||
write templates to generate the HTML form.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/auth.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/register'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'POST'</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="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'POST'</span><span class="p">:</span>
|
||||
<span class="n">username</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span>
|
||||
<span class="n">password</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'password'</span><span class="p">]</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">username</span><span class="p">:</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="s1">'Username is required.'</span>
|
||||
<span class="k">elif</span> <span class="ow">not</span> <span class="n">password</span><span class="p">:</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="s1">'Password is required.'</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">error</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"INSERT INTO user (username, password) VALUES (?, ?)"</span><span class="p">,</span>
|
||||
<span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">generate_password_hash</span><span class="p">(</span><span class="n">password</span><span class="p">)),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="k">except</span> <span class="n">db</span><span class="o">.</span><span class="n">IntegrityError</span><span class="p">:</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"User </span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2"> is already registered."</span>
|
||||
<span class="k">else</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="s2">"auth.login"</span><span class="p">))</span>
|
||||
|
||||
<span class="n">flash</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">'auth/register.html'</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Here’s what the <code class="docutils literal notranslate"><span class="pre">register</span></code> view function is doing:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Blueprint.route" title="flask.Blueprint.route"><code class="xref py py-meth docutils literal notranslate"><span class="pre">@bp.route</span></code></a> associates the URL <code class="docutils literal notranslate"><span class="pre">/register</span></code>
|
||||
with the <code class="docutils literal notranslate"><span class="pre">register</span></code> view function. When Flask receives a request
|
||||
to <code class="docutils literal notranslate"><span class="pre">/auth/register</span></code>, it will call the <code class="docutils literal notranslate"><span class="pre">register</span></code> view and use
|
||||
the return value as the response.</p></li>
|
||||
<li><p>If the user submitted the form,
|
||||
<a class="reference internal" href="../api.html#flask.Request.method" title="flask.Request.method"><code class="xref py py-attr docutils literal notranslate"><span class="pre">request.method</span></code></a> will be <code class="docutils literal notranslate"><span class="pre">'POST'</span></code>. In this
|
||||
case, start validating the input.</p></li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.Request.form" title="flask.Request.form"><code class="xref py py-attr docutils literal notranslate"><span class="pre">request.form</span></code></a> is a special type of
|
||||
<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#dict" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">dict</span></code></a> mapping submitted form keys and values. The user will
|
||||
input their <code class="docutils literal notranslate"><span class="pre">username</span></code> and <code class="docutils literal notranslate"><span class="pre">password</span></code>.</p></li>
|
||||
<li><p>Validate that <code class="docutils literal notranslate"><span class="pre">username</span></code> and <code class="docutils literal notranslate"><span class="pre">password</span></code> are not empty.</p></li>
|
||||
<li><p>If validation succeeds, insert the new user data into the database.</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.execute" title="(in Python v3.13)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">db.execute</span></code></a> takes a SQL
|
||||
query with <code class="docutils literal notranslate"><span class="pre">?</span></code> placeholders for any user input, and a tuple of
|
||||
values to replace the placeholders with. The database library
|
||||
will take care of escaping the values so you are not vulnerable
|
||||
to a <em>SQL injection attack</em>.</p></li>
|
||||
<li><p>For security, passwords should never be stored in the database
|
||||
directly. Instead,
|
||||
<a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/utils/#werkzeug.security.generate_password_hash" title="(in Werkzeug v3.1.x)"><code class="xref py py-func docutils literal notranslate"><span class="pre">generate_password_hash()</span></code></a> is used to
|
||||
securely hash the password, and that hash is stored. Since this
|
||||
query modifies data,
|
||||
<a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.commit" title="(in Python v3.13)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">db.commit()</span></code></a> needs to be
|
||||
called afterwards to save the changes.</p></li>
|
||||
<li><p>An <a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.IntegrityError" title="(in Python v3.13)"><code class="xref py py-exc docutils literal notranslate"><span class="pre">sqlite3.IntegrityError</span></code></a> will occur if the username
|
||||
already exists, which should be shown to the user as another
|
||||
validation error.</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>After storing the user, they are redirected to the login page.
|
||||
<a class="reference internal" href="../api.html#flask.url_for" title="flask.url_for"><code class="xref py py-func docutils literal notranslate"><span class="pre">url_for()</span></code></a> generates the URL for the login view based on its
|
||||
name. This is preferable to writing the URL directly as it allows
|
||||
you to change the URL later without changing all code that links to
|
||||
it. <a class="reference internal" href="../api.html#flask.redirect" title="flask.redirect"><code class="xref py py-func docutils literal notranslate"><span class="pre">redirect()</span></code></a> generates a redirect response to the generated
|
||||
URL.</p></li>
|
||||
<li><p>If validation fails, the error is shown to the user. <a class="reference internal" href="../api.html#flask.flash" title="flask.flash"><code class="xref py py-func docutils literal notranslate"><span class="pre">flash()</span></code></a>
|
||||
stores messages that can be retrieved when rendering the template.</p></li>
|
||||
<li><p>When the user initially navigates to <code class="docutils literal notranslate"><span class="pre">auth/register</span></code>, or
|
||||
there was a validation error, an HTML page with the registration
|
||||
form should be shown. <a class="reference internal" href="../api.html#flask.render_template" title="flask.render_template"><code class="xref py py-func docutils literal notranslate"><span class="pre">render_template()</span></code></a> will render a template
|
||||
containing the HTML, which you’ll write in the next step of the
|
||||
tutorial.</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
<section id="login">
|
||||
<h2>Login<a class="headerlink" href="#login" title="Link to this heading">¶</a></h2>
|
||||
<p>This view follows the same pattern as the <code class="docutils literal notranslate"><span class="pre">register</span></code> view above.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/auth.py</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/login'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'POST'</span><span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</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">'POST'</span><span class="p">:</span>
|
||||
<span class="n">username</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span>
|
||||
<span class="n">password</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">'password'</span><span class="p">]</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">user</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s1">'SELECT * FROM user WHERE username = ?'</span><span class="p">,</span> <span class="p">(</span><span class="n">username</span><span class="p">,)</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">user</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="s1">'Incorrect username.'</span>
|
||||
<span class="k">elif</span> <span class="ow">not</span> <span class="n">check_password_hash</span><span class="p">(</span><span class="n">user</span><span class="p">[</span><span class="s1">'password'</span><span class="p">],</span> <span class="n">password</span><span class="p">):</span>
|
||||
<span class="n">error</span> <span class="o">=</span> <span class="s1">'Incorrect password.'</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">error</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">session</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
|
||||
<span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">=</span> <span class="n">user</span><span class="p">[</span><span class="s1">'id'</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">'index'</span><span class="p">))</span>
|
||||
|
||||
<span class="n">flash</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">'auth/login.html'</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>There are a few differences from the <code class="docutils literal notranslate"><span class="pre">register</span></code> view:</p>
|
||||
<ol class="arabic">
|
||||
<li><p>The user is queried first and stored in a variable for later use.</p>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.fetchone" title="(in Python v3.13)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">fetchone()</span></code></a> returns one row from the query.
|
||||
If the query returned no results, it returns <code class="docutils literal notranslate"><span class="pre">None</span></code>. Later,
|
||||
<a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.fetchall" title="(in Python v3.13)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">fetchall()</span></code></a> will be used, which returns a list
|
||||
of all results.</p>
|
||||
</li>
|
||||
<li><p><a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/utils/#werkzeug.security.check_password_hash" title="(in Werkzeug v3.1.x)"><code class="xref py py-func docutils literal notranslate"><span class="pre">check_password_hash()</span></code></a> hashes the submitted
|
||||
password in the same way as the stored hash and securely compares
|
||||
them. If they match, the password is valid.</p></li>
|
||||
<li><p><a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> is a <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#dict" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">dict</span></code></a> that stores data across requests.
|
||||
When validation succeeds, the user’s <code class="docutils literal notranslate"><span class="pre">id</span></code> is stored in a new
|
||||
session. The data is stored in a <em>cookie</em> that is sent to the
|
||||
browser, and the browser then sends it back with subsequent requests.
|
||||
Flask securely <em>signs</em> the data so that it can’t be tampered with.</p></li>
|
||||
</ol>
|
||||
<p>Now that the user’s <code class="docutils literal notranslate"><span class="pre">id</span></code> is stored in the <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a>, it will be
|
||||
available on subsequent requests. At the beginning of each request, if
|
||||
a user is logged in their information should be loaded and made
|
||||
available to other views.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/auth.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">before_app_request</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">load_logged_in_user</span><span class="p">():</span>
|
||||
<span class="n">user_id</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'user_id'</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">user_id</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">g</span><span class="o">.</span><span class="n">user</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">g</span><span class="o">.</span><span class="n">user</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s1">'SELECT * FROM user WHERE id = ?'</span><span class="p">,</span> <span class="p">(</span><span class="n">user_id</span><span class="p">,)</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference internal" href="../api.html#flask.Blueprint.before_app_request" title="flask.Blueprint.before_app_request"><code class="xref py py-meth docutils literal notranslate"><span class="pre">bp.before_app_request()</span></code></a> registers
|
||||
a function that runs before the view function, no matter what URL is
|
||||
requested. <code class="docutils literal notranslate"><span class="pre">load_logged_in_user</span></code> checks if a user id is stored in the
|
||||
<a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> and gets that user’s data from the database, storing it
|
||||
on <a class="reference internal" href="../api.html#flask.g" title="flask.g"><code class="xref py py-data docutils literal notranslate"><span class="pre">g.user</span></code></a>, which lasts for the length of the request. If
|
||||
there is no user id, or if the id doesn’t exist, <code class="docutils literal notranslate"><span class="pre">g.user</span></code> will be
|
||||
<code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
|
||||
</section>
|
||||
<section id="logout">
|
||||
<h2>Logout<a class="headerlink" href="#logout" title="Link to this heading">¶</a></h2>
|
||||
<p>To log out, you need to remove the user id from the <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a>.
|
||||
Then <code class="docutils literal notranslate"><span class="pre">load_logged_in_user</span></code> won’t load a user on subsequent requests.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id6">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/auth.py</span></code></span><a class="headerlink" href="#id6" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@bp</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/logout'</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">logout</span><span class="p">():</span>
|
||||
<span class="n">session</span><span class="o">.</span><span class="n">clear</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">'index'</span><span class="p">))</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="require-authentication-in-other-views">
|
||||
<h2>Require Authentication in Other Views<a class="headerlink" href="#require-authentication-in-other-views" title="Link to this heading">¶</a></h2>
|
||||
<p>Creating, editing, and deleting blog posts will require a user to be
|
||||
logged in. A <em>decorator</em> can be used to check this for each view it’s
|
||||
applied to.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id7">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/auth.py</span></code></span><a class="headerlink" href="#id7" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">login_required</span><span class="p">(</span><span class="n">view</span><span class="p">):</span>
|
||||
<span class="nd">@functools</span><span class="o">.</span><span class="n">wraps</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">wrapped_view</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span> <span class="ow">is</span> <span class="kc">None</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">'auth.login'</span><span class="p">))</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">view</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">wrapped_view</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>This decorator returns a new view function that wraps the original view
|
||||
it’s applied to. The new function checks if a user is loaded and
|
||||
redirects to the login page otherwise. If a user is loaded the original
|
||||
view is called and continues normally. You’ll use this decorator when
|
||||
writing the blog views.</p>
|
||||
</section>
|
||||
<section id="endpoints-and-urls">
|
||||
<h2>Endpoints and URLs<a class="headerlink" href="#endpoints-and-urls" title="Link to this heading">¶</a></h2>
|
||||
<p>The <a class="reference internal" href="../api.html#flask.url_for" title="flask.url_for"><code class="xref py py-func docutils literal notranslate"><span class="pre">url_for()</span></code></a> function generates the URL to a view based on a name
|
||||
and arguments. The name associated with a view is also called the
|
||||
<em>endpoint</em>, and by default it’s the same as the name of the view
|
||||
function.</p>
|
||||
<p>For example, the <code class="docutils literal notranslate"><span class="pre">hello()</span></code> view that was added to the app
|
||||
factory earlier in the tutorial has the name <code class="docutils literal notranslate"><span class="pre">'hello'</span></code> and can be
|
||||
linked to with <code class="docutils literal notranslate"><span class="pre">url_for('hello')</span></code>. If it took an argument, which
|
||||
you’ll see later, it would be linked to using
|
||||
<code class="docutils literal notranslate"><span class="pre">url_for('hello',</span> <span class="pre">who='World')</span></code>.</p>
|
||||
<p>When using a blueprint, the name of the blueprint is prepended to the
|
||||
name of the function, so the endpoint for the <code class="docutils literal notranslate"><span class="pre">login</span></code> function you
|
||||
wrote above is <code class="docutils literal notranslate"><span class="pre">'auth.login'</span></code> because you added it to the <code class="docutils literal notranslate"><span class="pre">'auth'</span></code>
|
||||
blueprint.</p>
|
||||
<p>Continue to <a class="reference internal" href="templates.html"><span class="doc">Templates</span></a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -112,20 +330,33 @@ doesn’t show up, try clearing your browser’s cache.</p>
|
|||
<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="#">Blueprints and Views</a><ul>
|
||||
<li><a class="reference internal" href="#create-a-blueprint">Create a Blueprint</a></li>
|
||||
<li><a class="reference internal" href="#the-first-view-register">The First View: Register</a></li>
|
||||
<li><a class="reference internal" href="#login">Login</a></li>
|
||||
<li><a class="reference internal" href="#logout">Logout</a></li>
|
||||
<li><a class="reference internal" href="#require-authentication-in-other-views">Require Authentication in Other Views</a></li>
|
||||
<li><a class="reference internal" href="#endpoints-and-urls">Endpoints and URLs</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="templates.html" title="previous chapter">Templates</a>
|
||||
<li>Next: <a href="blog.html" title="next chapter">Blog Blueprint</a></ul>
|
||||
<li>Previous: <a href="database.html" title="previous chapter">Define and Access the Database</a>
|
||||
<li>Next: <a href="templates.html" title="next chapter">Templates</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -149,4 +380,4 @@ doesn’t show up, try clearing your browser’s cache.</p>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,211 +1,360 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../">
|
||||
<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>Templates — 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="Static Files" href="static.html" />
|
||||
<link rel="prev" title="Blueprints and Views" href="views.html" />
|
||||
<title>Class-based Views — 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="Application Structure and Lifecycle" href="lifecycle.html" />
|
||||
<link rel="prev" title="Signals" href="signals.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"
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
<a href="py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="static.html" title="Static Files"
|
||||
<a href="lifecycle.html" title="Application Structure and Lifecycle"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="views.html" title="Blueprints and Views"
|
||||
<a href="signals.html" title="Signals"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Templates</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Class-based Views</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="templates">
|
||||
<h1>Templates<a class="headerlink" href="#templates" title="Link to this heading">¶</a></h1>
|
||||
<p>You’ve written the authentication views for your application, but if
|
||||
you’re running the server and try to go to any of the URLs, you’ll see a
|
||||
<code class="docutils literal notranslate"><span class="pre">TemplateNotFound</span></code> error. That’s because the views are calling
|
||||
<a class="reference internal" href="../api.html#flask.render_template" title="flask.render_template"><code class="xref py py-func docutils literal notranslate"><span class="pre">render_template()</span></code></a>, but you haven’t written the templates yet.
|
||||
The template files will be stored in the <code class="docutils literal notranslate"><span class="pre">templates</span></code> directory inside
|
||||
the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package.</p>
|
||||
<p>Templates are files that contain static data as well as placeholders
|
||||
for dynamic data. A template is rendered with specific data to produce a
|
||||
final document. Flask uses the <a class="reference external" href="https://jinja.palletsprojects.com/templates/">Jinja</a> template library to render
|
||||
templates.</p>
|
||||
<p>In your application, you will use templates to render <a class="reference external" href="https://developer.mozilla.org/docs/Web/HTML">HTML</a> which
|
||||
will display in the user’s browser. In Flask, Jinja is configured to
|
||||
<em>autoescape</em> any data that is rendered in HTML templates. This means
|
||||
that it’s safe to render user input; any characters they’ve entered that
|
||||
could mess with the HTML, such as <code class="docutils literal notranslate"><span class="pre"><</span></code> and <code class="docutils literal notranslate"><span class="pre">></span></code> will be <em>escaped</em> with
|
||||
<em>safe</em> values that look the same in the browser but don’t cause unwanted
|
||||
effects.</p>
|
||||
<p>Jinja looks and behaves mostly like Python. Special delimiters are used
|
||||
to distinguish Jinja syntax from the static data in the template.
|
||||
Anything between <code class="docutils literal notranslate"><span class="pre">{{</span></code> and <code class="docutils literal notranslate"><span class="pre">}}</span></code> is an expression that will be output
|
||||
to the final document. <code class="docutils literal notranslate"><span class="pre">{%</span></code> and <code class="docutils literal notranslate"><span class="pre">%}</span></code> denotes a control flow
|
||||
statement like <code class="docutils literal notranslate"><span class="pre">if</span></code> and <code class="docutils literal notranslate"><span class="pre">for</span></code>. Unlike Python, blocks are denoted
|
||||
by start and end tags rather than indentation since static text within
|
||||
a block could change indentation.</p>
|
||||
<section id="the-base-layout">
|
||||
<h2>The Base Layout<a class="headerlink" href="#the-base-layout" title="Link to this heading">¶</a></h2>
|
||||
<p>Each page in the application will have the same basic layout around a
|
||||
different body. Instead of writing the entire HTML structure in each
|
||||
template, each template will <em>extend</em> a base template and override
|
||||
specific sections.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/base.html</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp"><!doctype html></span>
|
||||
<span class="p"><</span><span class="nt">title</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span> - Flaskr<span class="p"></</span><span class="nt">title</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'static'</span><span class="o">,</span> <span class="nv">filename</span><span class="o">=</span><span class="s1">'style.css'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">nav</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Flaskr<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">ul</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">g.user</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">span</span><span class="p">></span><span class="cp">{{</span> <span class="nv">g.user</span><span class="o">[</span><span class="s1">'username'</span><span class="o">]</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">span</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'auth.logout'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Log Out<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'auth.register'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Register<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">'auth.login'</span><span class="o">)</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>Log In<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">nav</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">section</span> <span class="na">class</span><span class="o">=</span><span class="s">"content"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">message</span> <span class="k">in</span> <span class="nv">get_flashed_messages</span><span class="o">()</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"flash"</span><span class="p">></span><span class="cp">{{</span> <span class="nv">message</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">div</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
|
||||
|
||||
<section id="class-based-views">
|
||||
<h1>Class-based Views<a class="headerlink" href="#class-based-views" title="Link to this heading">¶</a></h1>
|
||||
<p>This page introduces using the <a class="reference internal" href="api.html#flask.views.View" title="flask.views.View"><code class="xref py py-class docutils literal notranslate"><span class="pre">View</span></code></a> and <a class="reference internal" href="api.html#flask.views.MethodView" title="flask.views.MethodView"><code class="xref py py-class docutils literal notranslate"><span class="pre">MethodView</span></code></a>
|
||||
classes to write class-based views.</p>
|
||||
<p>A class-based view is a class that acts as a view function. Because it
|
||||
is a class, different instances of the class can be created with
|
||||
different arguments, to change the behavior of the view. This is also
|
||||
known as generic, reusable, or pluggable views.</p>
|
||||
<p>An example of where this is useful is defining a class that creates an
|
||||
API based on the database model it is initialized with.</p>
|
||||
<p>For more complex API behavior and customization, look into the various
|
||||
API extensions for Flask.</p>
|
||||
<section id="basic-reusable-view">
|
||||
<h2>Basic Reusable View<a class="headerlink" href="#basic-reusable-view" title="Link to this heading">¶</a></h2>
|
||||
<p>Let’s walk through an example converting a view function to a view
|
||||
class. We start with a view function that queries a list of users then
|
||||
renders a template to show the list.</p>
|
||||
<div class="highlight-python 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="s2">"/users/"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">user_list</span><span class="p">():</span>
|
||||
<span class="n">users</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s2">"users.html"</span><span class="p">,</span> <span class="n">users</span><span class="o">=</span><span class="n">users</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference internal" href="../api.html#flask.g" title="flask.g"><code class="xref py py-data docutils literal notranslate"><span class="pre">g</span></code></a> is automatically available in templates. Based on if
|
||||
<code class="docutils literal notranslate"><span class="pre">g.user</span></code> is set (from <code class="docutils literal notranslate"><span class="pre">load_logged_in_user</span></code>), either the username
|
||||
and a log out link are displayed, or links to register and log in
|
||||
are displayed. <a class="reference internal" href="../api.html#flask.url_for" title="flask.url_for"><code class="xref py py-func docutils literal notranslate"><span class="pre">url_for()</span></code></a> is also automatically available, and is
|
||||
used to generate URLs to views instead of writing them out manually.</p>
|
||||
<p>After the page title, and before the content, the template loops over
|
||||
each message returned by <a class="reference internal" href="../api.html#flask.get_flashed_messages" title="flask.get_flashed_messages"><code class="xref py py-func docutils literal notranslate"><span class="pre">get_flashed_messages()</span></code></a>. You used
|
||||
<a class="reference internal" href="../api.html#flask.flash" title="flask.flash"><code class="xref py py-func docutils literal notranslate"><span class="pre">flash()</span></code></a> in the views to show error messages, and this is the code
|
||||
that will display them.</p>
|
||||
<p>There are three blocks defined here that will be overridden in the other
|
||||
templates:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">title</span> <span class="pre">%}</span></code> will change the title displayed in the
|
||||
browser’s tab and window title.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">header</span> <span class="pre">%}</span></code> is similar to <code class="docutils literal notranslate"><span class="pre">title</span></code> but will change the
|
||||
title displayed on the page.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">content</span> <span class="pre">%}</span></code> is where the content of each page goes, such
|
||||
as the login form or a blog post.</p></li>
|
||||
</ol>
|
||||
<p>The base template is directly in the <code class="docutils literal notranslate"><span class="pre">templates</span></code> directory. To keep
|
||||
the others organized, the templates for a blueprint will be placed in a
|
||||
directory with the same name as the blueprint.</p>
|
||||
</section>
|
||||
<section id="register">
|
||||
<h2>Register<a class="headerlink" href="#register" title="Link to this heading">¶</a></h2>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/auth/register.html</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
<p>This works for the user model, but let’s say you also had more models
|
||||
that needed list pages. You’d need to write another view function for
|
||||
each model, even though the only thing that would change is the model
|
||||
and template name.</p>
|
||||
<p>Instead, you can write a <a class="reference internal" href="api.html#flask.views.View" title="flask.views.View"><code class="xref py py-class docutils literal notranslate"><span class="pre">View</span></code></a> subclass that will query a model
|
||||
and render a template. As the first step, we’ll convert the view to a
|
||||
class without any customization.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask.views</span><span class="w"> </span><span class="kn">import</span> <span class="n">View</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>Register<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">UserList</span><span class="p">(</span><span class="n">View</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">dispatch_request</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="n">users</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s2">"users.html"</span><span class="p">,</span> <span class="n">objects</span><span class="o">=</span><span class="n">users</span><span class="p">)</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"username"</span><span class="p">></span>Username<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">"username"</span> <span class="na">id</span><span class="o">=</span><span class="s">"username"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"password"</span><span class="p">></span>Password<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"password"</span> <span class="na">name</span><span class="o">=</span><span class="s">"password"</span> <span class="na">id</span><span class="o">=</span><span class="s">"password"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</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">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="s2">"/users/"</span><span class="p">,</span> <span class="n">view_func</span><span class="o">=</span><span class="n">UserList</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s2">"user_list"</span><span class="p">))</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <a class="reference internal" href="api.html#flask.views.View.dispatch_request" title="flask.views.View.dispatch_request"><code class="xref py py-meth docutils literal notranslate"><span class="pre">View.dispatch_request()</span></code></a> method is the equivalent of the view
|
||||
function. Calling <a class="reference internal" href="api.html#flask.views.View.as_view" title="flask.views.View.as_view"><code class="xref py py-meth docutils literal notranslate"><span class="pre">View.as_view()</span></code></a> method will create a view
|
||||
function that can be registered on the app with its
|
||||
<a class="reference internal" href="api.html#flask.Flask.add_url_rule" title="flask.Flask.add_url_rule"><code class="xref py py-meth docutils literal notranslate"><span class="pre">add_url_rule()</span></code></a> method. The first argument to
|
||||
<code class="docutils literal notranslate"><span class="pre">as_view</span></code> is the name to use to refer to the view with
|
||||
<a class="reference internal" href="api.html#flask.url_for" title="flask.url_for"><code class="xref py py-func docutils literal notranslate"><span class="pre">url_for()</span></code></a>.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>You can’t decorate the class with <code class="docutils literal notranslate"><span class="pre">@app.route()</span></code> the way you’d
|
||||
do with a basic view function.</p>
|
||||
</div>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">extends</span> <span class="pre">'base.html'</span> <span class="pre">%}</span></code> tells Jinja that this template should
|
||||
replace the blocks from the base template. All the rendered content must
|
||||
appear inside <code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">%}</span></code> tags that override blocks from the base
|
||||
template.</p>
|
||||
<p>A useful pattern used here is to place <code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">title</span> <span class="pre">%}</span></code> inside
|
||||
<code class="docutils literal notranslate"><span class="pre">{%</span> <span class="pre">block</span> <span class="pre">header</span> <span class="pre">%}</span></code>. This will set the title block and then output
|
||||
the value of it into the header block, so that both the window and page
|
||||
share the same title without writing it twice.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">input</span></code> tags are using the <code class="docutils literal notranslate"><span class="pre">required</span></code> attribute here. This tells
|
||||
the browser not to submit the form until those fields are filled in. If
|
||||
the user is using an older browser that doesn’t support that attribute,
|
||||
or if they are using something besides a browser to make requests, you
|
||||
still want to validate the data in the Flask view. It’s important to
|
||||
always fully validate the data on the server, even if the client does
|
||||
some validation as well.</p>
|
||||
</section>
|
||||
<section id="log-in">
|
||||
<h2>Log In<a class="headerlink" href="#log-in" title="Link to this heading">¶</a></h2>
|
||||
<p>This is identical to the register template except for the title and
|
||||
submit button.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">flaskr/templates/auth/login.html</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">'base.html'</span> <span class="cp">%}</span>
|
||||
<p>Next, we need to be able to register the same view class for different
|
||||
models and templates, to make it more useful than the original function.
|
||||
The class will take two arguments, the model and template, and store
|
||||
them on <code class="docutils literal notranslate"><span class="pre">self</span></code>. Then <code class="docutils literal notranslate"><span class="pre">dispatch_request</span></code> can reference these instead
|
||||
of hard-coded values.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">ListView</span><span class="p">(</span><span class="n">View</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model</span><span class="p">,</span> <span class="n">template</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">model</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">template</span> <span class="o">=</span> <span class="n">template</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">header</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span>Log In<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
|
||||
<span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span>
|
||||
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"username"</span><span class="p">></span>Username<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">"username"</span> <span class="na">id</span><span class="o">=</span><span class="s">"username"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"password"</span><span class="p">></span>Password<span class="p"></</span><span class="nt">label</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"password"</span> <span class="na">name</span><span class="o">=</span><span class="s">"password"</span> <span class="na">id</span><span class="o">=</span><span class="s">"password"</span> <span class="na">required</span><span class="p">></span>
|
||||
<span class="p"><</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">"Log In"</span><span class="p">></span>
|
||||
<span class="p"></</span><span class="nt">form</span><span class="p">></span>
|
||||
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">dispatch_request</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="n">items</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">template</span><span class="p">,</span> <span class="n">items</span><span class="o">=</span><span class="n">items</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Remember, we create the view function with <code class="docutils literal notranslate"><span class="pre">View.as_view()</span></code> instead of
|
||||
creating the class directly. Any extra arguments passed to <code class="docutils literal notranslate"><span class="pre">as_view</span></code>
|
||||
are then passed when creating the class. Now we can register the same
|
||||
view to handle multiple models.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span>
|
||||
<span class="s2">"/users/"</span><span class="p">,</span>
|
||||
<span class="n">view_func</span><span class="o">=</span><span class="n">ListView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s2">"user_list"</span><span class="p">,</span> <span class="n">User</span><span class="p">,</span> <span class="s2">"users.html"</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span>
|
||||
<span class="s2">"/stories/"</span><span class="p">,</span>
|
||||
<span class="n">view_func</span><span class="o">=</span><span class="n">ListView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s2">"story_list"</span><span class="p">,</span> <span class="n">Story</span><span class="p">,</span> <span class="s2">"stories.html"</span><span class="p">),</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="register-a-user">
|
||||
<h2>Register A User<a class="headerlink" href="#register-a-user" title="Link to this heading">¶</a></h2>
|
||||
<p>Now that the authentication templates are written, you can register a
|
||||
user. Make sure the server is still running (<code class="docutils literal notranslate"><span class="pre">flask</span> <span class="pre">run</span></code> if it’s not),
|
||||
then go to <a class="reference external" href="http://127.0.0.1:5000/auth/register">http://127.0.0.1:5000/auth/register</a>.</p>
|
||||
<p>Try clicking the “Register” button without filling out the form and see
|
||||
that the browser shows an error message. Try removing the <code class="docutils literal notranslate"><span class="pre">required</span></code>
|
||||
attributes from the <code class="docutils literal notranslate"><span class="pre">register.html</span></code> template and click “Register”
|
||||
again. Instead of the browser showing an error, the page will reload and
|
||||
the error from <a class="reference internal" href="../api.html#flask.flash" title="flask.flash"><code class="xref py py-func docutils literal notranslate"><span class="pre">flash()</span></code></a> in the view will be shown.</p>
|
||||
<p>Fill out a username and password and you’ll be redirected to the login
|
||||
page. Try entering an incorrect username, or the correct username and
|
||||
incorrect password. If you log in you’ll get an error because there’s
|
||||
no <code class="docutils literal notranslate"><span class="pre">index</span></code> view to redirect to yet.</p>
|
||||
<p>Continue to <a class="reference internal" href="static.html"><span class="doc">Static Files</span></a>.</p>
|
||||
<section id="url-variables">
|
||||
<h2>URL Variables<a class="headerlink" href="#url-variables" title="Link to this heading">¶</a></h2>
|
||||
<p>Any variables captured by the URL are passed as keyword arguments to the
|
||||
<code class="docutils literal notranslate"><span class="pre">dispatch_request</span></code> method, as they would be for a regular view
|
||||
function.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">DetailView</span><span class="p">(</span><span class="n">View</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">model</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">template</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">model</span><span class="o">.</span><span class="vm">__name__</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="si">}</span><span class="s2">/detail.html"</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">dispatch_request</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">)</span>
|
||||
<span class="n">item</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get_or_404</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">template</span><span class="p">,</span> <span class="n">item</span><span class="o">=</span><span class="n">item</span><span class="p">)</span>
|
||||
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span>
|
||||
<span class="s2">"/users/<int:id>"</span><span class="p">,</span>
|
||||
<span class="n">view_func</span><span class="o">=</span><span class="n">DetailView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s2">"user_detail"</span><span class="p">,</span> <span class="n">User</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="view-lifetime-and-self">
|
||||
<h2>View Lifetime and <code class="docutils literal notranslate"><span class="pre">self</span></code><a class="headerlink" href="#view-lifetime-and-self" title="Link to this heading">¶</a></h2>
|
||||
<p>By default, a new instance of the view class is created every time a
|
||||
request is handled. This means that it is safe to write other data to
|
||||
<code class="docutils literal notranslate"><span class="pre">self</span></code> during the request, since the next request will not see it,
|
||||
unlike other forms of global state.</p>
|
||||
<p>However, if your view class needs to do a lot of complex initialization,
|
||||
doing it for every request is unnecessary and can be inefficient. To
|
||||
avoid this, set <a class="reference internal" href="api.html#flask.views.View.init_every_request" title="flask.views.View.init_every_request"><code class="xref py py-attr docutils literal notranslate"><span class="pre">View.init_every_request</span></code></a> to <code class="docutils literal notranslate"><span class="pre">False</span></code>, which will
|
||||
only create one instance of the class and use it for every request. In
|
||||
this case, writing to <code class="docutils literal notranslate"><span class="pre">self</span></code> is not safe. If you need to store data
|
||||
during the request, use <a class="reference internal" href="api.html#flask.g" title="flask.g"><code class="xref py py-data docutils literal notranslate"><span class="pre">g</span></code></a> instead.</p>
|
||||
<p>In the <code class="docutils literal notranslate"><span class="pre">ListView</span></code> example, nothing writes to <code class="docutils literal notranslate"><span class="pre">self</span></code> during the
|
||||
request, so it is more efficient to create a single instance.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">ListView</span><span class="p">(</span><span class="n">View</span><span class="p">):</span>
|
||||
<span class="n">init_every_request</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model</span><span class="p">,</span> <span class="n">template</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">model</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">template</span> <span class="o">=</span> <span class="n">template</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">dispatch_request</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="n">items</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">template</span><span class="p">,</span> <span class="n">items</span><span class="o">=</span><span class="n">items</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Different instances will still be created each for each <code class="docutils literal notranslate"><span class="pre">as_view</span></code>
|
||||
call, but not for each request to those views.</p>
|
||||
</section>
|
||||
<section id="view-decorators">
|
||||
<h2>View Decorators<a class="headerlink" href="#view-decorators" title="Link to this heading">¶</a></h2>
|
||||
<p>The view class itself is not the view function. View decorators need to
|
||||
be applied to the view function returned by <code class="docutils literal notranslate"><span class="pre">as_view</span></code>, not the class
|
||||
itself. Set <a class="reference internal" href="api.html#flask.views.View.decorators" title="flask.views.View.decorators"><code class="xref py py-attr docutils literal notranslate"><span class="pre">View.decorators</span></code></a> to a list of decorators to apply.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">UserList</span><span class="p">(</span><span class="n">View</span><span class="p">):</span>
|
||||
<span class="n">decorators</span> <span class="o">=</span> <span class="p">[</span><span class="n">cache</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">2</span><span class="p">),</span> <span class="n">login_required</span><span class="p">]</span>
|
||||
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="s1">'/users/'</span><span class="p">,</span> <span class="n">view_func</span><span class="o">=</span><span class="n">UserList</span><span class="o">.</span><span class="n">as_view</span><span class="p">())</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you didn’t set <code class="docutils literal notranslate"><span class="pre">decorators</span></code>, you could apply them manually instead.
|
||||
This is equivalent to:</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">view</span> <span class="o">=</span> <span class="n">UserList</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s2">"users_list"</span><span class="p">)</span>
|
||||
<span class="n">view</span> <span class="o">=</span> <span class="n">cache</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">2</span><span class="p">)(</span><span class="n">view</span><span class="p">)</span>
|
||||
<span class="n">view</span> <span class="o">=</span> <span class="n">login_required</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="s1">'/users/'</span><span class="p">,</span> <span class="n">view_func</span><span class="o">=</span><span class="n">view</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Keep in mind that order matters. If you’re used to <code class="docutils literal notranslate"><span class="pre">@decorator</span></code> style,
|
||||
this is equivalent to:</p>
|
||||
<div class="highlight-python 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="s2">"/users/"</span><span class="p">)</span>
|
||||
<span class="nd">@login_required</span>
|
||||
<span class="nd">@cache</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">user_list</span><span class="p">():</span>
|
||||
<span class="o">...</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="method-hints">
|
||||
<h2>Method Hints<a class="headerlink" href="#method-hints" title="Link to this heading">¶</a></h2>
|
||||
<p>A common pattern is to register a view with <code class="docutils literal notranslate"><span class="pre">methods=["GET",</span> <span class="pre">"POST"]</span></code>,
|
||||
then check <code class="docutils literal notranslate"><span class="pre">request.method</span> <span class="pre">==</span> <span class="pre">"POST"</span></code> to decide what to do. Setting
|
||||
<a class="reference internal" href="api.html#flask.views.View.methods" title="flask.views.View.methods"><code class="xref py py-attr docutils literal notranslate"><span class="pre">View.methods</span></code></a> is equivalent to passing the list of methods to
|
||||
<code class="docutils literal notranslate"><span class="pre">add_url_rule</span></code> or <code class="docutils literal notranslate"><span class="pre">route</span></code>.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">MyView</span><span class="p">(</span><span class="n">View</span><span class="p">):</span>
|
||||
<span class="n">methods</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"GET"</span><span class="p">,</span> <span class="s2">"POST"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">dispatch_request</span><span class="p">(</span><span class="bp">self</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="s2">"POST"</span><span class="p">:</span>
|
||||
<span class="o">...</span>
|
||||
<span class="o">...</span>
|
||||
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="s1">'/my-view'</span><span class="p">,</span> <span class="n">view_func</span><span class="o">=</span><span class="n">MyView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s1">'my-view'</span><span class="p">))</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This is equivalent to the following, except further subclasses can
|
||||
inherit or change the methods.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span>
|
||||
<span class="s2">"/my-view"</span><span class="p">,</span>
|
||||
<span class="n">view_func</span><span class="o">=</span><span class="n">MyView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="s2">"my-view"</span><span class="p">),</span>
|
||||
<span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"GET"</span><span class="p">,</span> <span class="s2">"POST"</span><span class="p">],</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="method-dispatching-and-apis">
|
||||
<h2>Method Dispatching and APIs<a class="headerlink" href="#method-dispatching-and-apis" title="Link to this heading">¶</a></h2>
|
||||
<p>For APIs it can be helpful to use a different function for each HTTP
|
||||
method. <a class="reference internal" href="api.html#flask.views.MethodView" title="flask.views.MethodView"><code class="xref py py-class docutils literal notranslate"><span class="pre">MethodView</span></code></a> extends the basic <a class="reference internal" href="api.html#flask.views.View" title="flask.views.View"><code class="xref py py-class docutils literal notranslate"><span class="pre">View</span></code></a> to dispatch
|
||||
to different methods of the class based on the request method. Each HTTP
|
||||
method maps to a method of the class with the same (lowercase) name.</p>
|
||||
<p><a class="reference internal" href="api.html#flask.views.MethodView" title="flask.views.MethodView"><code class="xref py py-class docutils literal notranslate"><span class="pre">MethodView</span></code></a> automatically sets <a class="reference internal" href="api.html#flask.views.View.methods" title="flask.views.View.methods"><code class="xref py py-attr docutils literal notranslate"><span class="pre">View.methods</span></code></a> based on the
|
||||
methods defined by the class. It even knows how to handle subclasses
|
||||
that override or define other methods.</p>
|
||||
<p>We can make a generic <code class="docutils literal notranslate"><span class="pre">ItemAPI</span></code> class that provides get (detail),
|
||||
patch (edit), and delete methods for a given model. A <code class="docutils literal notranslate"><span class="pre">GroupAPI</span></code> can
|
||||
provide get (list) and post (create) methods.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask.views</span><span class="w"> </span><span class="kn">import</span> <span class="n">MethodView</span>
|
||||
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">ItemAPI</span><span class="p">(</span><span class="n">MethodView</span><span class="p">):</span>
|
||||
<span class="n">init_every_request</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">model</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">generate_validator</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_get_item</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get_or_404</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||||
<span class="n">item</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_item</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">to_json</span><span class="p">())</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">patch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||||
<span class="n">item</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_item</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
<span class="n">errors</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">errors</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">errors</span><span class="p">),</span> <span class="mi">400</span>
|
||||
|
||||
<span class="n">item</span><span class="o">.</span><span class="n">update_from_json</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">to_json</span><span class="p">())</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||||
<span class="n">item</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_item</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="s2">""</span><span class="p">,</span> <span class="mi">204</span>
|
||||
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">GroupAPI</span><span class="p">(</span><span class="n">MethodView</span><span class="p">):</span>
|
||||
<span class="n">init_every_request</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">model</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">generate_validator</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">create</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="n">items</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">jsonify</span><span class="p">([</span><span class="n">item</span><span class="o">.</span><span class="n">to_json</span><span class="p">()</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">])</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="n">errors</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">errors</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">errors</span><span class="p">),</span> <span class="mi">400</span>
|
||||
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">from_json</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">))</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">to_json</span><span class="p">())</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">register_api</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">model</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
|
||||
<span class="n">item</span> <span class="o">=</span> <span class="n">ItemAPI</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">-item"</span><span class="p">,</span> <span class="n">model</span><span class="p">)</span>
|
||||
<span class="n">group</span> <span class="o">=</span> <span class="n">GroupAPI</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">-group"</span><span class="p">,</span> <span class="n">model</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="sa">f</span><span class="s2">"/</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">/<int:id>"</span><span class="p">,</span> <span class="n">view_func</span><span class="o">=</span><span class="n">item</span><span class="p">)</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_url_rule</span><span class="p">(</span><span class="sa">f</span><span class="s2">"/</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">/"</span><span class="p">,</span> <span class="n">view_func</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
|
||||
|
||||
<span class="n">register_api</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">User</span><span class="p">,</span> <span class="s2">"users"</span><span class="p">)</span>
|
||||
<span class="n">register_api</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">Story</span><span class="p">,</span> <span class="s2">"stories"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This produces the following views, a standard REST API!</p>
|
||||
<table class="docutils align-default">
|
||||
<tbody>
|
||||
<tr class="row-odd"><td><p>URL</p></td>
|
||||
<td><p>Method</p></td>
|
||||
<td><p>Description</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">/users/</span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">GET</span></code></p></td>
|
||||
<td><p>List all users</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">/users/</span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">POST</span></code></p></td>
|
||||
<td><p>Create a new user</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">/users/<id></span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">GET</span></code></p></td>
|
||||
<td><p>Show a single user</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">/users/<id></span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">PATCH</span></code></p></td>
|
||||
<td><p>Update a user</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">/users/<id></span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">DELETE</span></code></p></td>
|
||||
<td><p>Delete a user</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">/stories/</span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">GET</span></code></p></td>
|
||||
<td><p>List all stories</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">/stories/</span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">POST</span></code></p></td>
|
||||
<td><p>Create a new story</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">/stories/<id></span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">GET</span></code></p></td>
|
||||
<td><p>Show a single story</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">/stories/<id></span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">PATCH</span></code></p></td>
|
||||
<td><p>Update a story</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">/stories/<id></span></code></p></td>
|
||||
<td><p><code class="docutils literal notranslate"><span class="pre">DELETE</span></code></p></td>
|
||||
<td><p>Delete a story</p></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
@ -217,39 +366,38 @@ no <code class="docutils literal notranslate"><span class="pre">index</span></co
|
|||
<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"/>
|
||||
|
||||
|
||||
<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="#">Templates</a><ul>
|
||||
<li><a class="reference internal" href="#the-base-layout">The Base Layout</a></li>
|
||||
<li><a class="reference internal" href="#register">Register</a></li>
|
||||
<li><a class="reference internal" href="#log-in">Log In</a></li>
|
||||
<li><a class="reference internal" href="#register-a-user">Register A User</a></li>
|
||||
<li><a class="reference internal" href="#">Class-based Views</a><ul>
|
||||
<li><a class="reference internal" href="#basic-reusable-view">Basic Reusable View</a></li>
|
||||
<li><a class="reference internal" href="#url-variables">URL Variables</a></li>
|
||||
<li><a class="reference internal" href="#view-lifetime-and-self">View Lifetime and <code class="docutils literal notranslate"><span class="pre">self</span></code></a></li>
|
||||
<li><a class="reference internal" href="#view-decorators">View Decorators</a></li>
|
||||
<li><a class="reference internal" href="#method-hints">Method Hints</a></li>
|
||||
<li><a class="reference internal" href="#method-dispatching-and-apis">Method Dispatching and APIs</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<li><a href="index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="views.html" title="previous chapter">Blueprints and Views</a>
|
||||
<li>Next: <a href="static.html" title="next chapter">Static Files</a></ul>
|
||||
</li>
|
||||
<li>Previous: <a href="signals.html" title="previous chapter">Signals</a>
|
||||
<li>Next: <a href="lifecycle.html" title="next chapter">Application Structure and Lifecycle</a>
|
||||
</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">
|
||||
<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>
|
||||
|
|
@ -265,4 +413,4 @@ no <code class="docutils literal notranslate"><span class="pre">index</span></co
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,563 +1,304 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../">
|
||||
<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>Test Coverage — 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="Deploy to Production" href="deploy.html" />
|
||||
<link rel="prev" title="Make the Project Installable" href="install.html" />
|
||||
<title>Security Considerations — 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="Deploying to Production" href="deploying/index.html" />
|
||||
<link rel="prev" title="Single-Page Applications" href="patterns/singlepageapplications.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"
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
<a href="py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
<a href="deploying/index.html" title="Deploying to Production"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="install.html" title="Make the Project Installable"
|
||||
<a href="patterns/singlepageapplications.html" title="Single-Page Applications"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Test Coverage</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Security Considerations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="test-coverage">
|
||||
<h1>Test Coverage<a class="headerlink" href="#test-coverage" title="Link to this heading">¶</a></h1>
|
||||
<p>Writing unit tests for your application lets you check that the code
|
||||
you wrote works the way you expect. Flask provides a test client that
|
||||
simulates requests to the application and returns the response data.</p>
|
||||
<p>You should test as much of your code as possible. Code in functions only
|
||||
runs when the function is called, and code in branches, such as <code class="docutils literal notranslate"><span class="pre">if</span></code>
|
||||
blocks, only runs when the condition is met. You want to make sure that
|
||||
each function is tested with data that covers each branch.</p>
|
||||
<p>The closer you get to 100% coverage, the more comfortable you can be
|
||||
that making a change won’t unexpectedly change other behavior. However,
|
||||
100% coverage doesn’t guarantee that your application doesn’t have bugs.
|
||||
In particular, it doesn’t test how the user interacts with the
|
||||
application in the browser. Despite this, test coverage is an important
|
||||
tool to use during development.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This is being introduced late in the tutorial, but in your future
|
||||
projects you should test as you develop.</p>
|
||||
</div>
|
||||
<p>You’ll use <a class="reference external" href="https://pytest.readthedocs.io/">pytest</a> and <a class="reference external" href="https://coverage.readthedocs.io/">coverage</a> to test and measure your code.
|
||||
Install them both:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install pytest coverage
|
||||
</pre></div>
|
||||
</div>
|
||||
<section id="setup-and-fixtures">
|
||||
<h2>Setup and Fixtures<a class="headerlink" href="#setup-and-fixtures" title="Link to this heading">¶</a></h2>
|
||||
<p>The test code is located in the <code class="docutils literal notranslate"><span class="pre">tests</span></code> directory. This directory is
|
||||
<em>next to</em> the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package, not inside it. The
|
||||
<code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code> file contains setup functions called <em>fixtures</em>
|
||||
that each test will use. Tests are in Python modules that start with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>, and each test function in those modules also starts with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>.</p>
|
||||
<p>Each test will create a new temporary database file and populate some
|
||||
data that will be used in the tests. Write a SQL file to insert that
|
||||
data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/data.sql</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-sql notranslate"><div class="highlight"><pre><span></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'</span><span class="p">),</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'other'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'</span><span class="p">);</span>
|
||||
|
||||
<span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">body</span><span class="p">,</span><span class="w"> </span><span class="n">author_id</span><span class="p">,</span><span class="w"> </span><span class="n">created</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test title'</span><span class="p">,</span><span class="w"> </span><span class="s1">'test'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">x</span><span class="s1">'0a'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">'body'</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">'2018-01-01 00:00:00'</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture will call the factory and pass <code class="docutils literal notranslate"><span class="pre">test_config</span></code> to
|
||||
configure the application and database for testing instead of using your
|
||||
local development configuration.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">tempfile</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span><span class="p">,</span> <span class="n">init_db</span>
|
||||
|
||||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span> <span class="s1">'data.sql'</span><span class="p">),</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||||
<span class="n">_data_sql</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf8'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">app</span><span class="p">():</span>
|
||||
<span class="n">db_fd</span><span class="p">,</span> <span class="n">db_path</span> <span class="o">=</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">mkstemp</span><span class="p">()</span>
|
||||
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">({</span>
|
||||
<span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="s1">'DATABASE'</span><span class="p">:</span> <span class="n">db_path</span><span class="p">,</span>
|
||||
<span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">init_db</span><span class="p">()</span>
|
||||
<span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">executescript</span><span class="p">(</span><span class="n">_data_sql</span><span class="p">)</span>
|
||||
|
||||
<span class="k">yield</span> <span class="n">app</span>
|
||||
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">db_fd</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">unlink</span><span class="p">(</span><span class="n">db_path</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">client</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_client</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">runner</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_cli_runner</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">tempfile.mkstemp()</span></code></a> creates and opens a temporary file, returning
|
||||
the file descriptor and the path to it. The <code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> path is
|
||||
overridden so it points to this temporary path instead of the instance
|
||||
folder. After setting the path, the database tables are created and the
|
||||
test data is inserted. After the test is over, the temporary file is
|
||||
closed and removed.</p>
|
||||
<p><a class="reference internal" href="../config.html#TESTING" title="TESTING"><code class="xref py py-data docutils literal notranslate"><span class="pre">TESTING</span></code></a> tells Flask that the app is in test mode. Flask changes
|
||||
some internal behavior so it’s easier to test, and other extensions can
|
||||
also use the flag to make testing them easier.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture calls
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_client" title="flask.Flask.test_client"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_client()</span></code></a> with the application
|
||||
object created by the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture. Tests will use the client to make
|
||||
requests to the application without running the server.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture is similar to <code class="docutils literal notranslate"><span class="pre">client</span></code>.
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_cli_runner" title="flask.Flask.test_cli_runner"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_cli_runner()</span></code></a> creates a runner
|
||||
that can call the Click commands registered with the application.</p>
|
||||
<p>Pytest uses fixtures by matching their function names with the names
|
||||
of arguments in the test functions. For example, the <code class="docutils literal notranslate"><span class="pre">test_hello</span></code>
|
||||
function you’ll write next takes a <code class="docutils literal notranslate"><span class="pre">client</span></code> argument. Pytest matches
|
||||
that with the <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture function, calls it, and passes the
|
||||
returned value to the test function.</p>
|
||||
<section id="security-considerations">
|
||||
<h1>Security Considerations<a class="headerlink" href="#security-considerations" title="Link to this heading">¶</a></h1>
|
||||
<p>Web applications face many types of potential security problems, and it can be
|
||||
hard to get everything right, or even to know what “right” is in general. Flask
|
||||
tries to solve a few of these things by default, but there are other parts you
|
||||
may have to take care of yourself. Many of these solutions are tradeoffs, and
|
||||
will depend on each application’s specific needs and threat model. Many hosting
|
||||
platforms may take care of certain types of problems without the need for the
|
||||
Flask application to handle them.</p>
|
||||
<section id="resource-use">
|
||||
<h2>Resource Use<a class="headerlink" href="#resource-use" title="Link to this heading">¶</a></h2>
|
||||
<p>A common category of attacks is “Denial of Service” (DoS or DDoS). This is a
|
||||
very broad category, and different variants target different layers in a
|
||||
deployed application. In general, something is done to increase how much
|
||||
processing time or memory is used to handle each request, to the point where
|
||||
there are not enough resources to handle legitimate requests.</p>
|
||||
<p>Flask provides a few configuration options to handle resource use. They can
|
||||
also be set on individual requests to customize only that request. The
|
||||
documentation for each goes into more detail.</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference internal" href="config.html#MAX_CONTENT_LENGTH" title="MAX_CONTENT_LENGTH"><code class="xref py py-data docutils literal notranslate"><span class="pre">MAX_CONTENT_LENGTH</span></code></a> or <a class="reference internal" href="api.html#flask.Request.max_content_length" title="flask.Request.max_content_length"><code class="xref py py-attr docutils literal notranslate"><span class="pre">Request.max_content_length</span></code></a> controls
|
||||
how much data will be read from a request. It is not set by default,
|
||||
although it will still block truly unlimited streams unless the WSGI server
|
||||
indicates support.</p></li>
|
||||
<li><p><a class="reference internal" href="config.html#MAX_FORM_MEMORY_SIZE" title="MAX_FORM_MEMORY_SIZE"><code class="xref py py-data docutils literal notranslate"><span class="pre">MAX_FORM_MEMORY_SIZE</span></code></a> or <a class="reference internal" href="api.html#flask.Request.max_form_memory_size" title="flask.Request.max_form_memory_size"><code class="xref py py-attr docutils literal notranslate"><span class="pre">Request.max_form_memory_size</span></code></a>
|
||||
controls how large any non-file <code class="docutils literal notranslate"><span class="pre">multipart/form-data</span></code> field can be. It is
|
||||
set to 500kB by default.</p></li>
|
||||
<li><p><a class="reference internal" href="config.html#MAX_FORM_PARTS" title="MAX_FORM_PARTS"><code class="xref py py-data docutils literal notranslate"><span class="pre">MAX_FORM_PARTS</span></code></a> or <a class="reference internal" href="api.html#flask.Request.max_form_parts" title="flask.Request.max_form_parts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">Request.max_form_parts</span></code></a> controls how many
|
||||
<code class="docutils literal notranslate"><span class="pre">multipart/form-data</span></code> fields can be parsed. It is set to 1000 by default.
|
||||
Combined with the default <code class="code docutils literal notranslate"><span class="pre">max_form_memory_size</span></code>, this means that a form
|
||||
will occupy at most 500MB of memory.</p></li>
|
||||
</ul>
|
||||
<p>Regardless of these settings, you should also review what settings are available
|
||||
from your operating system, container deployment (Docker etc), WSGI server, HTTP
|
||||
server, and hosting platform. They typically have ways to set process resource
|
||||
limits, timeouts, and other checks regardless of how Flask is configured.</p>
|
||||
</section>
|
||||
<section id="factory">
|
||||
<h2>Factory<a class="headerlink" href="#factory" title="Link to this heading">¶</a></h2>
|
||||
<p>There’s not much to test about the factory itself. Most of the code will
|
||||
be executed for each test already, so if something fails the other tests
|
||||
will notice.</p>
|
||||
<p>The only behavior that can change is passing test config. If config is
|
||||
not passed, there should be some default configuration, otherwise the
|
||||
configuration should be overridden.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_factory.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_config</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="ow">not</span> <span class="n">create_app</span><span class="p">()</span><span class="o">.</span><span class="n">testing</span>
|
||||
<span class="k">assert</span> <span class="n">create_app</span><span class="p">({</span><span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span><span class="o">.</span><span class="n">testing</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_hello</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span> <span class="o">==</span> <span class="sa">b</span><span class="s1">'Hello, World!'</span>
|
||||
<section id="cross-site-scripting-xss">
|
||||
<span id="security-xss"></span><h2>Cross-Site Scripting (XSS)<a class="headerlink" href="#cross-site-scripting-xss" title="Link to this heading">¶</a></h2>
|
||||
<p>Cross site scripting is the concept of injecting arbitrary HTML (and with
|
||||
it JavaScript) into the context of a website. To remedy this, developers
|
||||
have to properly escape text so that it cannot include arbitrary HTML
|
||||
tags. For more information on that have a look at the Wikipedia article
|
||||
on <a class="reference external" href="https://en.wikipedia.org/wiki/Cross-site_scripting">Cross-Site Scripting</a>.</p>
|
||||
<p>Flask configures Jinja2 to automatically escape all values unless
|
||||
explicitly told otherwise. This should rule out all XSS problems caused
|
||||
in templates, but there are still other places where you have to be
|
||||
careful:</p>
|
||||
<ul class="simple">
|
||||
<li><p>generating HTML without the help of Jinja2</p></li>
|
||||
<li><p>calling <code class="xref py py-class docutils literal notranslate"><span class="pre">Markup</span></code> on data submitted by users</p></li>
|
||||
<li><p>sending out HTML from uploaded files, never do that, use the
|
||||
<code class="docutils literal notranslate"><span class="pre">Content-Disposition:</span> <span class="pre">attachment</span></code> header to prevent that problem.</p></li>
|
||||
<li><p>sending out textfiles from uploaded files. Some browsers are using
|
||||
content-type guessing based on the first few bytes so users could
|
||||
trick a browser to execute HTML.</p></li>
|
||||
</ul>
|
||||
<p>Another thing that is very important are unquoted attributes. While
|
||||
Jinja2 can protect you from XSS issues by escaping HTML, there is one
|
||||
thing it cannot protect you from: XSS by attribute injection. To counter
|
||||
this possible attack vector, be sure to always quote your attributes with
|
||||
either double or single quotes when using Jinja expressions in them:</p>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">input</span> <span class="na">value</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">value</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Why is this necessary? Because if you would not be doing that, an
|
||||
attacker could easily inject custom JavaScript handlers. For example an
|
||||
attacker could inject this piece of HTML+JavaScript:</p>
|
||||
<div class="highlight-html notranslate"><div class="highlight"><pre><span></span>onmouseover=alert(document.cookie)
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You added the <code class="docutils literal notranslate"><span class="pre">hello</span></code> route as an example when writing the factory at
|
||||
the beginning of the tutorial. It returns “Hello, World!”, so the test
|
||||
checks that the response data matches.</p>
|
||||
<p>When the user would then move with the mouse over the input, the cookie
|
||||
would be presented to the user in an alert window. But instead of showing
|
||||
the cookie to the user, a good attacker might also execute any other
|
||||
JavaScript code. In combination with CSS injections the attacker might
|
||||
even make the element fill out the entire page so that the user would
|
||||
just have to have the mouse anywhere on the page to trigger the attack.</p>
|
||||
<p>There is one class of XSS issues that Jinja’s escaping does not protect
|
||||
against. The <code class="docutils literal notranslate"><span class="pre">a</span></code> tag’s <code class="docutils literal notranslate"><span class="pre">href</span></code> attribute can contain a <code class="code docutils literal notranslate"><span class="pre">javascript:</span></code> URI,
|
||||
which the browser will execute when clicked if not secured properly.</p>
|
||||
<div class="highlight-html notranslate"><div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ value }}"</span><span class="p">></span>click here<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"javascript:alert('unsafe');"</span><span class="p">></span>click here<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>To prevent this, you’ll need to set the <a class="reference internal" href="#security-csp"><span class="std std-ref">Content Security Policy (CSP)</span></a> response header.</p>
|
||||
</section>
|
||||
<section id="database">
|
||||
<h2>Database<a class="headerlink" href="#database" title="Link to this heading">¶</a></h2>
|
||||
<p>Within an application context, <code class="docutils literal notranslate"><span class="pre">get_db</span></code> should return the same
|
||||
connection each time it’s called. After the context, the connection
|
||||
should be closed.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">sqlite3</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_get_close_db</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">db</span> <span class="ow">is</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">ProgrammingError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT 1'</span><span class="p">)</span>
|
||||
|
||||
<span class="k">assert</span> <span class="s1">'closed'</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">init-db</span></code> command should call the <code class="docutils literal notranslate"><span class="pre">init_db</span></code> function and output
|
||||
a message.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_init_db_command</span><span class="p">(</span><span class="n">runner</span><span class="p">,</span> <span class="n">monkeypatch</span><span class="p">):</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Recorder</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="n">called</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">fake_init_db</span><span class="p">():</span>
|
||||
<span class="n">Recorder</span><span class="o">.</span><span class="n">called</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="s1">'flaskr.db.init_db'</span><span class="p">,</span> <span class="n">fake_init_db</span><span class="p">)</span>
|
||||
<span class="n">result</span> <span class="o">=</span> <span class="n">runner</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="s1">'init-db'</span><span class="p">])</span>
|
||||
<span class="k">assert</span> <span class="s1">'Initialized'</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">output</span>
|
||||
<span class="k">assert</span> <span class="n">Recorder</span><span class="o">.</span><span class="n">called</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>This test uses Pytest’s <code class="docutils literal notranslate"><span class="pre">monkeypatch</span></code> fixture to replace the
|
||||
<code class="docutils literal notranslate"><span class="pre">init_db</span></code> function with one that records that it’s been called. The
|
||||
<code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture you wrote above is used to call the <code class="docutils literal notranslate"><span class="pre">init-db</span></code>
|
||||
command by name.</p>
|
||||
<section id="cross-site-request-forgery-csrf">
|
||||
<h2>Cross-Site Request Forgery (CSRF)<a class="headerlink" href="#cross-site-request-forgery-csrf" title="Link to this heading">¶</a></h2>
|
||||
<p>Another big problem is CSRF. This is a very complex topic and I won’t
|
||||
outline it here in detail just mention what it is and how to theoretically
|
||||
prevent it.</p>
|
||||
<p>If your authentication information is stored in cookies, you have implicit
|
||||
state management. The state of “being logged in” is controlled by a
|
||||
cookie, and that cookie is sent with each request to a page.
|
||||
Unfortunately that includes requests triggered by 3rd party sites. If you
|
||||
don’t keep that in mind, some people might be able to trick your
|
||||
application’s users with social engineering to do stupid things without
|
||||
them knowing.</p>
|
||||
<p>Say you have a specific URL that, when you sent <code class="docutils literal notranslate"><span class="pre">POST</span></code> requests to will
|
||||
delete a user’s profile (say <code class="docutils literal notranslate"><span class="pre">http://example.com/user/delete</span></code>). If an
|
||||
attacker now creates a page that sends a post request to that page with
|
||||
some JavaScript they just have to trick some users to load that page and
|
||||
their profiles will end up being deleted.</p>
|
||||
<p>Imagine you were to run Facebook with millions of concurrent users and
|
||||
someone would send out links to images of little kittens. When users
|
||||
would go to that page, their profiles would get deleted while they are
|
||||
looking at images of fluffy cats.</p>
|
||||
<p>How can you prevent that? Basically for each request that modifies
|
||||
content on the server you would have to either use a one-time token and
|
||||
store that in the cookie <strong>and</strong> also transmit it with the form data.
|
||||
After receiving the data on the server again, you would then have to
|
||||
compare the two tokens and ensure they are equal.</p>
|
||||
<p>Why does Flask not do that for you? The ideal place for this to happen is
|
||||
the form validation framework, which does not exist in Flask.</p>
|
||||
</section>
|
||||
<section id="authentication">
|
||||
<h2>Authentication<a class="headerlink" href="#authentication" title="Link to this heading">¶</a></h2>
|
||||
<p>For most of the views, a user needs to be logged in. The easiest way to
|
||||
do this in tests is to make a <code class="docutils literal notranslate"><span class="pre">POST</span></code> request to the <code class="docutils literal notranslate"><span class="pre">login</span></code> view
|
||||
with the client. Rather than writing that out every time, you can write
|
||||
a class with methods to do that, and use a fixture to pass it the client
|
||||
for each test.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id6">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id6" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">AuthActions</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">client</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_client</span> <span class="o">=</span> <span class="n">client</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">username</span><span class="o">=</span><span class="s1">'test'</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s1">'test'</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/login'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">logout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/logout'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">auth</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">AuthActions</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture, you can call <code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> in a test to
|
||||
log in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user, which was inserted as part of the test
|
||||
data in the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">register</span></code> view should render successfully on <code class="docutils literal notranslate"><span class="pre">GET</span></code>. On <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
with valid form data, it should redirect to the login URL and the user’s
|
||||
data should be in the database. Invalid data should display error
|
||||
messages.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id7">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id7" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">g</span><span class="p">,</span> <span class="n">session</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/register'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT * FROM user WHERE username = 'a'"</span><span class="p">,</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Username is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Password is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'already registered'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register_validate_input</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.get" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.get()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request
|
||||
and returns the <a class="reference internal" href="../api.html#flask.Response" title="flask.Response"><code class="xref py py-class docutils literal notranslate"><span class="pre">Response</span></code></a> object returned by Flask. Similarly,
|
||||
<a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.post" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.post()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
request, converting the <code class="docutils literal notranslate"><span class="pre">data</span></code> dict into form data.</p>
|
||||
<p>To test that the page renders successfully, a simple request is made and
|
||||
checked for a <code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> <a class="reference internal" href="../api.html#flask.Response.status_code" title="flask.Response.status_code"><code class="xref py py-attr docutils literal notranslate"><span class="pre">status_code</span></code></a>. If
|
||||
rendering failed, Flask would return a <code class="docutils literal notranslate"><span class="pre">500</span> <span class="pre">Internal</span> <span class="pre">Server</span> <span class="pre">Error</span></code>
|
||||
code.</p>
|
||||
<p><code class="xref py py-attr docutils literal notranslate"><span class="pre">headers</span></code> will have a <code class="docutils literal notranslate"><span class="pre">Location</span></code> header with the login
|
||||
URL when the register view redirects to the login view.</p>
|
||||
<p><a class="reference internal" href="../api.html#flask.Response.data" title="flask.Response.data"><code class="xref py py-attr docutils literal notranslate"><span class="pre">data</span></code></a> contains the body of the response as bytes. If
|
||||
you expect a certain value to render on the page, check that it’s in
|
||||
<code class="docutils literal notranslate"><span class="pre">data</span></code>. Bytes must be compared to bytes. If you want to compare text,
|
||||
use <a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/wrappers/#werkzeug.wrappers.Response.get_data" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">get_data(as_text=True)</span></code></a>
|
||||
instead.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">pytest.mark.parametrize</span></code> tells Pytest to run the same test function
|
||||
with different arguments. You use it here to test different invalid
|
||||
input and error messages without writing the same code three times.</p>
|
||||
<p>The tests for the <code class="docutils literal notranslate"><span class="pre">login</span></code> view are very similar to those for
|
||||
<code class="docutils literal notranslate"><span class="pre">register</span></code>. Rather than testing the data in the database,
|
||||
<a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should have <code class="docutils literal notranslate"><span class="pre">user_id</span></code> set after logging in.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id8">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id8" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_login</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/login'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span>
|
||||
<span class="k">assert</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'test'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect username.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect password.'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_validate_input</span><span class="p">(</span><span class="n">auth</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Using <code class="docutils literal notranslate"><span class="pre">client</span></code> in a <code class="docutils literal notranslate"><span class="pre">with</span></code> block allows accessing context variables
|
||||
such as <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> after the response is returned. Normally,
|
||||
accessing <code class="docutils literal notranslate"><span class="pre">session</span></code> outside of a request would raise an error.</p>
|
||||
<p>Testing <code class="docutils literal notranslate"><span class="pre">logout</span></code> is the opposite of <code class="docutils literal notranslate"><span class="pre">login</span></code>. <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should
|
||||
not contain <code class="docutils literal notranslate"><span class="pre">user_id</span></code> after logging out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id9">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id9" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_logout</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">logout</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="s1">'user_id'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">session</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<section id="json-security">
|
||||
<span id="security-json"></span><h2>JSON Security<a class="headerlink" href="#json-security" title="Link to this heading">¶</a></h2>
|
||||
<p>In Flask 0.10 and lower, <code class="xref py py-func docutils literal notranslate"><span class="pre">jsonify()</span></code> did not serialize top-level
|
||||
arrays to JSON. This was because of a security vulnerability in ECMAScript 4.</p>
|
||||
<p>ECMAScript 5 closed this vulnerability, so only extremely old browsers are
|
||||
still vulnerable. All of these browsers have <a class="reference external" href="https://github.com/pallets/flask/issues/248#issuecomment-59934857">other more serious
|
||||
vulnerabilities</a>, so
|
||||
this behavior was changed and <code class="xref py py-func docutils literal notranslate"><span class="pre">jsonify()</span></code> now supports serializing
|
||||
arrays.</p>
|
||||
</section>
|
||||
<section id="blog">
|
||||
<h2>Blog<a class="headerlink" href="#blog" title="Link to this heading">¶</a></h2>
|
||||
<p>All the blog views use the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture you wrote earlier. Call
|
||||
<code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> and subsequent requests from the client will be logged
|
||||
in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">index</span></code> view should display information about the post that was
|
||||
added with the test data. When logged in as the author, there should be
|
||||
a link to edit the post.</p>
|
||||
<p>You can also test some more authentication behavior while testing the
|
||||
<code class="docutils literal notranslate"><span class="pre">index</span></code> view. When not logged in, each page shows links to log in or
|
||||
register. When logged in, there’s a link to log out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id10">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id10" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_index</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Log In"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Register"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Log Out'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test title'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'by test on 2018-01-01'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test</span><span class="se">\n</span><span class="s1">body'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<section id="security-headers">
|
||||
<h2>Security Headers<a class="headerlink" href="#security-headers" title="Link to this heading">¶</a></h2>
|
||||
<p>Browsers recognize various response headers in order to control security. We
|
||||
recommend reviewing each of the headers below for use in your application.
|
||||
The <a class="reference external" href="https://github.com/GoogleCloudPlatform/flask-talisman">Flask-Talisman</a> extension can be used to manage HTTPS and the security
|
||||
headers for you.</p>
|
||||
<section id="http-strict-transport-security-hsts">
|
||||
<h3>HTTP Strict Transport Security (HSTS)<a class="headerlink" href="#http-strict-transport-security-hsts" title="Link to this heading">¶</a></h3>
|
||||
<p>Tells the browser to convert all HTTP requests to HTTPS, preventing
|
||||
man-in-the-middle (MITM) attacks.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Strict-Transport-Security'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'max-age=31536000; includeSubDomains'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>A user must be logged in to access the <code class="docutils literal notranslate"><span class="pre">create</span></code>, <code class="docutils literal notranslate"><span class="pre">update</span></code>, and
|
||||
<code class="docutils literal notranslate"><span class="pre">delete</span></code> views. The logged in user must be the author of the post to
|
||||
access <code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code>, otherwise a <code class="docutils literal notranslate"><span class="pre">403</span> <span class="pre">Forbidden</span></code> status
|
||||
is returned. If a <code class="docutils literal notranslate"><span class="pre">post</span></code> with the given <code class="docutils literal notranslate"><span class="pre">id</span></code> doesn’t exist,
|
||||
<code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code> should return <code class="docutils literal notranslate"><span class="pre">404</span> <span class="pre">Not</span> <span class="pre">Found</span></code>.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id11">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id11" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_author_required</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="c1"># change the post author to another user</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'UPDATE post SET author_id = 2 WHERE id = 1'</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="c1"># current user can't modify other user's post</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="c1"># current user doesn't see edit link</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/2/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/2/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_exists_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">404</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">create</span></code> and <code class="docutils literal notranslate"><span class="pre">update</span></code> views should render and return a
|
||||
<code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> status for a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request. When valid data is sent in a
|
||||
<code class="docutils literal notranslate"><span class="pre">POST</span></code> request, <code class="docutils literal notranslate"><span class="pre">create</span></code> should insert the new post data into the
|
||||
database, and <code class="docutils literal notranslate"><span class="pre">update</span></code> should modify the existing data. Both pages
|
||||
should show an error message on invalid data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id12">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id12" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_create</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'created'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">count</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT COUNT(id) FROM post'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">assert</span> <span class="n">count</span> <span class="o">==</span> <span class="mi">2</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_update</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'updated'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'updated'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_create_update_validate</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Title is required.'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">delete</span></code> view should redirect to the index URL and the post should
|
||||
no longer exist in the database.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id13">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id13" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_delete</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span> <span class="ow">is</span> <span class="kc">None</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="running-the-tests">
|
||||
<h2>Running the Tests<a class="headerlink" href="#running-the-tests" title="Link to this heading">¶</a></h2>
|
||||
<p>Some extra configuration, which is not required but makes running tests with coverage
|
||||
less verbose, can be added to the project’s <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id14">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code></span><a class="headerlink" href="#id14" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="k">[tool.pytest.ini_options]</span>
|
||||
<span class="n">testpaths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"tests"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">[tool.coverage.run]</span>
|
||||
<span class="n">branch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="n">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"flaskr"</span><span class="p">]</span>
|
||||
<section id="content-security-policy-csp">
|
||||
<span id="security-csp"></span><h3>Content Security Policy (CSP)<a class="headerlink" href="#content-security-policy-csp" title="Link to this heading">¶</a></h3>
|
||||
<p>Tell the browser where it can load various types of resource from. This header
|
||||
should be used whenever possible, but requires some work to define the correct
|
||||
policy for your site. A very strict policy would be:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Content-Security-Policy'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"default-src 'self'"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>To run the tests, use the <code class="docutils literal notranslate"><span class="pre">pytest</span></code> command. It will find and run all
|
||||
the test functions you’ve written.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pytest
|
||||
|
||||
========================= test session starts ==========================
|
||||
platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
|
||||
rootdir: /home/user/Projects/flask-tutorial
|
||||
collected 23 items
|
||||
|
||||
tests/test_auth.py ........ [ 34%]
|
||||
tests/test_blog.py ............ [ 86%]
|
||||
tests/test_db.py .. [ 95%]
|
||||
tests/test_factory.py .. [100%]
|
||||
|
||||
====================== 24 passed in 0.64 seconds =======================
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://csp.withgoogle.com/docs/index.html">https://csp.withgoogle.com/docs/index.html</a></p></li>
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="x-content-type-options">
|
||||
<h3>X-Content-Type-Options<a class="headerlink" href="#x-content-type-options" title="Link to this heading">¶</a></h3>
|
||||
<p>Forces the browser to honor the response content type instead of trying to
|
||||
detect it, which can be abused to generate a cross-site scripting (XSS)
|
||||
attack.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'X-Content-Type-Options'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'nosniff'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If any tests fail, pytest will show the error that was raised. You can
|
||||
run <code class="docutils literal notranslate"><span class="pre">pytest</span> <span class="pre">-v</span></code> to get a list of each test function rather than dots.</p>
|
||||
<p>To measure the code coverage of your tests, use the <code class="docutils literal notranslate"><span class="pre">coverage</span></code> command
|
||||
to run pytest instead of running it directly.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage run -m pytest
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="x-frame-options">
|
||||
<h3>X-Frame-Options<a class="headerlink" href="#x-frame-options" title="Link to this heading">¶</a></h3>
|
||||
<p>Prevents external sites from embedding your site in an <code class="docutils literal notranslate"><span class="pre">iframe</span></code>. This
|
||||
prevents a class of attacks where clicks in the outer frame can be translated
|
||||
invisibly to clicks on your page’s elements. This is also known as
|
||||
“clickjacking”.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'X-Frame-Options'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'SAMEORIGIN'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can either view a simple coverage report in the terminal:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage report
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="set-cookie-options">
|
||||
<span id="security-cookie"></span><h3>Set-Cookie options<a class="headerlink" href="#set-cookie-options" title="Link to this heading">¶</a></h3>
|
||||
<p>These options can be added to a <code class="docutils literal notranslate"><span class="pre">Set-Cookie</span></code> header to improve their
|
||||
security. Flask has configuration options to set these on the session cookie.
|
||||
They can be set on other cookies too.</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">Secure</span></code> limits cookies to HTTPS traffic only.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">HttpOnly</span></code> protects the contents of cookies from being read with
|
||||
JavaScript.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">SameSite</span></code> restricts how cookies are sent with requests from
|
||||
external sites. Can be set to <code class="docutils literal notranslate"><span class="pre">'Lax'</span></code> (recommended) or <code class="docutils literal notranslate"><span class="pre">'Strict'</span></code>.
|
||||
<code class="docutils literal notranslate"><span class="pre">Lax</span></code> prevents sending cookies with CSRF-prone requests from
|
||||
external sites, such as submitting a form. <code class="docutils literal notranslate"><span class="pre">Strict</span></code> prevents sending
|
||||
cookies with all external requests, including following regular links.</p></li>
|
||||
</ul>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
|
||||
<span class="n">SESSION_COOKIE_SECURE</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">SESSION_COOKIE_HTTPONLY</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">SESSION_COOKIE_SAMESITE</span><span class="o">=</span><span class="s1">'Lax'</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
Name Stmts Miss Branch BrPart Cover
|
||||
------------------------------------------------------
|
||||
flaskr/__init__.py 21 0 2 0 100%
|
||||
flaskr/auth.py 54 0 22 0 100%
|
||||
flaskr/blog.py 54 0 16 0 100%
|
||||
flaskr/db.py 24 0 4 0 100%
|
||||
------------------------------------------------------
|
||||
TOTAL 153 0 44 0 100%
|
||||
<span class="n">response</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'flask'</span><span class="p">,</span> <span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">samesite</span><span class="o">=</span><span class="s1">'Lax'</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>An HTML report allows you to see which lines were covered in each file:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage html
|
||||
<p>Specifying <code class="docutils literal notranslate"><span class="pre">Expires</span></code> or <code class="docutils literal notranslate"><span class="pre">Max-Age</span></code> options, will remove the cookie after
|
||||
the given time, or the current time plus the age, respectively. If neither
|
||||
option is set, the cookie will be removed when the browser is closed.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># cookie expires after 10 minutes</span>
|
||||
<span class="n">response</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span><span class="s1">'snakes'</span><span class="p">,</span> <span class="s1">'3'</span><span class="p">,</span> <span class="n">max_age</span><span class="o">=</span><span class="mi">600</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This generates files in the <code class="docutils literal notranslate"><span class="pre">htmlcov</span></code> directory. Open
|
||||
<code class="docutils literal notranslate"><span class="pre">htmlcov/index.html</span></code> in your browser to see the report.</p>
|
||||
<p>Continue to <a class="reference internal" href="deploy.html"><span class="doc">Deploy to Production</span></a>.</p>
|
||||
<p>For the session cookie, if <a class="reference internal" href="api.html#flask.session.permanent" title="flask.session.permanent"><code class="xref py py-attr docutils literal notranslate"><span class="pre">session.permanent</span></code></a>
|
||||
is set, then <a class="reference internal" href="config.html#PERMANENT_SESSION_LIFETIME" title="PERMANENT_SESSION_LIFETIME"><code class="xref py py-data docutils literal notranslate"><span class="pre">PERMANENT_SESSION_LIFETIME</span></code></a> is used to set the expiration.
|
||||
Flask’s default cookie implementation validates that the cryptographic
|
||||
signature is not older than this value. Lowering this value may help mitigate
|
||||
replay attacks, where intercepted cookies can be sent at a later time.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
|
||||
<span class="n">PERMANENT_SESSION_LIFETIME</span><span class="o">=</span><span class="mi">600</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/login'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">'POST'</span><span class="p">])</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">():</span>
|
||||
<span class="o">...</span>
|
||||
<span class="n">session</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
|
||||
<span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="n">id</span>
|
||||
<span class="n">session</span><span class="o">.</span><span class="n">permanent</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="o">...</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Use <code class="xref py py-class docutils literal notranslate"><span class="pre">itsdangerous.TimedSerializer</span></code> to sign and validate other cookie
|
||||
values (or any values that need secure signatures).</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies</a></p></li>
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
<section id="copy-paste-to-terminal">
|
||||
<h2>Copy/Paste to Terminal<a class="headerlink" href="#copy-paste-to-terminal" title="Link to this heading">¶</a></h2>
|
||||
<p>Hidden characters such as the backspace character (<code class="docutils literal notranslate"><span class="pre">\b</span></code>, <code class="docutils literal notranslate"><span class="pre">^H</span></code>) can
|
||||
cause text to render differently in HTML than how it is interpreted if
|
||||
<a class="reference external" href="https://security.stackexchange.com/q/39118">pasted into a terminal</a>.</p>
|
||||
<p>For example, <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">y\bose\bm\bi\bt\be\b</span></code> renders as
|
||||
<code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">yosemite</span></code> in HTML, but the backspaces are applied when pasted
|
||||
into a terminal, and it becomes <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">os</span></code>.</p>
|
||||
<p>If you expect users to copy and paste untrusted code from your site,
|
||||
such as from comments posted by users on a technical blog, consider
|
||||
applying extra filtering, such as replacing all <code class="docutils literal notranslate"><span class="pre">\b</span></code> characters.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">body</span> <span class="o">=</span> <span class="n">body</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\b</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Most modern terminals will warn about and remove hidden characters when
|
||||
pasting, so this isn’t strictly necessary. It’s also possible to craft
|
||||
dangerous commands in other ways that aren’t possible to filter.
|
||||
Depending on your site’s use case, it may be good to show a warning
|
||||
about copying code in general.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
@ -569,41 +310,45 @@ TOTAL 153 0 44 0 100%
|
|||
<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"/>
|
||||
|
||||
|
||||
<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="#">Test Coverage</a><ul>
|
||||
<li><a class="reference internal" href="#setup-and-fixtures">Setup and Fixtures</a></li>
|
||||
<li><a class="reference internal" href="#factory">Factory</a></li>
|
||||
<li><a class="reference internal" href="#database">Database</a></li>
|
||||
<li><a class="reference internal" href="#authentication">Authentication</a></li>
|
||||
<li><a class="reference internal" href="#blog">Blog</a></li>
|
||||
<li><a class="reference internal" href="#running-the-tests">Running the Tests</a></li>
|
||||
<li><a class="reference internal" href="#">Security Considerations</a><ul>
|
||||
<li><a class="reference internal" href="#resource-use">Resource Use</a></li>
|
||||
<li><a class="reference internal" href="#cross-site-scripting-xss">Cross-Site Scripting (XSS)</a></li>
|
||||
<li><a class="reference internal" href="#cross-site-request-forgery-csrf">Cross-Site Request Forgery (CSRF)</a></li>
|
||||
<li><a class="reference internal" href="#json-security">JSON Security</a></li>
|
||||
<li><a class="reference internal" href="#security-headers">Security Headers</a><ul>
|
||||
<li><a class="reference internal" href="#http-strict-transport-security-hsts">HTTP Strict Transport Security (HSTS)</a></li>
|
||||
<li><a class="reference internal" href="#content-security-policy-csp">Content Security Policy (CSP)</a></li>
|
||||
<li><a class="reference internal" href="#x-content-type-options">X-Content-Type-Options</a></li>
|
||||
<li><a class="reference internal" href="#x-frame-options">X-Frame-Options</a></li>
|
||||
<li><a class="reference internal" href="#set-cookie-options">Set-Cookie options</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#copy-paste-to-terminal">Copy/Paste to Terminal</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<li><a href="index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="install.html" title="previous chapter">Make the Project Installable</a>
|
||||
<li>Next: <a href="deploy.html" title="next chapter">Deploy to Production</a></ul>
|
||||
</li>
|
||||
<li>Previous: <a href="patterns/singlepageapplications.html" title="previous chapter">Single-Page Applications</a>
|
||||
<li>Next: <a href="deploying/index.html" title="next chapter">Deploying to Production</a>
|
||||
</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">
|
||||
<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>
|
||||
|
|
@ -619,4 +364,4 @@ TOTAL 153 0 44 0 100%
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue