rewrite tutorial docs and example

This commit is contained in:
David Lord 2018-02-09 14:39:05 -08:00
parent 16d83d6bb4
commit c3dd7b8e4c
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
103 changed files with 3327 additions and 2224 deletions

View file

@ -1,113 +1,187 @@
.. _tutorial-templates:
.. currentmodule:: flask
Step 7: The Templates
=====================
Templates
=========
Now it is time to start working on the templates. As you may have
noticed, if you make requests with the app running, you will get
an exception that Flask cannot find the templates. The templates
are using `Jinja2`_ syntax and have autoescaping enabled by
default. This means that unless you mark a value in the code with
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
escaped with their XML equivalents.
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
``TemplateNotFound`` error. That's because the views are calling
:func:`render_template`, but you haven't written the templates yet.
The template files will be stored in the ``templates`` directory inside
the ``flaskr`` package.
We are also using template inheritance which makes it possible to reuse
the layout of the website in all pages.
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 `Jinja`_ template library to render
templates.
Create the follwing three HTML files and place them in the
:file:`templates` folder:
In your application, you will use templates to render `HTML`_ which
will display in the user's browser. In Flask, Jinja is configured to
*autoescape* 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 ``<`` and ``>`` will be *escaped* with
*safe* values that look the same in the browser but don't cause unwanted
effects.
.. _Jinja2: http://jinja.pocoo.org/docs/templates
Jinja looks and behaves mostly like Python. Special delimiters are used
to distinguish Jinja syntax from the static data in the template.
Anything between ``{{`` and ``}}`` is an expression that will be output
to the final document. ``{%`` and ``%}`` denotes a control flow
statement like ``if`` and ``for``. Unlike Python, blocks are denoted
by start and end tags rather than indentation since static text within
a block could change indentation.
layout.html
-----------
.. _Jinja: http://jinja.pocoo.org/docs/templates/
.. _HTML: https://developer.mozilla.org/docs/Web/HTML
This template contains the HTML skeleton, the header and a link to log in
(or log out if the user was already logged in). It also displays the
flashed messages if there are any. The ``{% block body %}`` block can be
replaced by a block of the same name (``body``) in a child template.
The :class:`~flask.session` dict is available in the template as well and
you can use that to check if the user is logged in or not. Note that in
Jinja you can access missing attributes and items of objects / dicts which
makes the following code work, even if there is no ``'logged_in'`` key in
the session:
The Base Layout
---------------
.. sourcecode:: html+jinja
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 *extend* a base template and override
specific sections.
.. code-block:: html+jinja
:caption: ``flaskr/templates/base.html``
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1>Flaskr</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in</a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>
show_entries.html
-----------------
This template extends the :file:`layout.html` template from above to display the
messages. Note that the ``for`` loop iterates over the messages we passed
in with the :func:`~flask.render_template` function. Notice that the form is
configured to submit to the `add_entry` view function and use ``POST`` as
HTTP method:
.. sourcecode:: html+jinja
{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
<dl>
<dt>Title:
<dd><input type=text size=30 name=title>
<dt>Text:
<dd><textarea name=text rows=5 cols=40></textarea>
<dd><input type=submit value=Share>
</dl>
</form>
{% endif %}
<ul class=entries>
{% for entry in entries %}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
{% else %}
<li><em>Unbelievable. No entries here so far</em></li>
{% endfor %}
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</section>
:data:`g` is automatically available in templates. Based on if
``g.user`` is set (from ``load_logged_in_user``), either the username
and a log out link are displayed, otherwise links to register and log in
are displayed. :func:`url_for` is also automatically available, and is
used to generate URLs to views instead of writing them out manually.
After the page title, and before the content, the template loops over
each message returned by :func:`get_flashed_messages`. You used
:func:`flash` in the views to show error messages, and this is the code
that will display them.
There are three blocks defined here that will be overridden in the other
templates:
#. ``{% block title %}`` will change the title displayed in the
browser's tab and window title.
#. ``{% block header %}`` is similar to ``title`` but will change the
title displayed on the page.
#. ``{% block content %}`` is where the content of each page goes, such
as the login form or a blog post.
The base template is directly in the ``templates`` directory. To keep
the others organized, the templates for a blueprint will be placed in a
directory with the same name as the blueprint.
Register
--------
.. code-block:: html+jinja
:caption: ``flaskr/templates/auth/register.html``
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}
login.html
----------
This is the login template, which basically just displays a form to allow
the user to login:
.. sourcecode:: html+jinja
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method=post>
<dl>
<dt>Username:
<dd><input type=text name=username>
<dt>Password:
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Register">
</form>
{% endblock %}
Continue with :ref:`tutorial-css`.
``{% extends 'base.html' %}`` tells Jinja that this template should
replace the blocks from the base template. All the rendered content must
appear inside ``{% block %}`` tags that override blocks from the base
template.
A useful pattern used here is to place ``{% block title %}`` inside
``{% block header %}``. 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.
The ``input`` tags are using the ``required`` 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.
Log In
------
This is identical to the register template except for the title and
submit button.
.. code-block:: html+jinja
:caption: ``flaskr/templates/auth/login.html``
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}
Register A User
---------------
Now that the authentication templates are written, you can register a
user. Make sure the server is still running (``flask run`` if it's not),
then go to http://127.0.0.1:5000/auth/register.
Try clicking the "Register" button without filling out the form and see
that the browser shows an error message. Try removing the ``required``
attributes from the ``register.html`` template and click "Register"
again. Instead of the browser showing an error, the page will reload and
the error from :func:`flash` in the view will be shown.
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 ``index`` view to redirect to yet.
Continue to :doc:`static`.