Browse Source

rewrite tutorial docs and example

pull/2676/head
David Lord 7 years ago
parent
commit
c3dd7b8e4c
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
  1. 2
      CONTRIBUTING.rst
  2. BIN
      docs/_static/flaskr.png
  3. 39
      docs/conf.py
  4. 2
      docs/installation.rst
  5. 6
      docs/patterns/jquery.rst
  6. 6
      docs/patterns/packages.rst
  7. 5
      docs/testing.rst
  8. 336
      docs/tutorial/blog.rst
  9. 31
      docs/tutorial/css.rst
  10. 213
      docs/tutorial/database.rst
  11. 78
      docs/tutorial/dbcon.rst
  12. 80
      docs/tutorial/dbinit.rst
  13. 121
      docs/tutorial/deploy.rst
  14. 177
      docs/tutorial/factory.rst
  15. BIN
      docs/tutorial/flaskr_edit.png
  16. BIN
      docs/tutorial/flaskr_index.png
  17. BIN
      docs/tutorial/flaskr_login.png
  18. 31
      docs/tutorial/folders.rst
  19. 79
      docs/tutorial/index.rst
  20. 113
      docs/tutorial/install.rst
  21. 39
      docs/tutorial/introduction.rst
  22. 110
      docs/tutorial/layout.rst
  23. 38
      docs/tutorial/next.rst
  24. 108
      docs/tutorial/packaging.rst
  25. 25
      docs/tutorial/schema.rst
  26. 101
      docs/tutorial/setup.rst
  27. 72
      docs/tutorial/static.rst
  28. 268
      docs/tutorial/templates.rst
  29. 96
      docs/tutorial/testing.rst
  30. 561
      docs/tutorial/tests.rst
  31. 363
      docs/tutorial/views.rst
  32. 19
      examples/blueprintexample/blueprintexample.py
  33. 0
      examples/blueprintexample/simple_page/__init__.py
  34. 13
      examples/blueprintexample/simple_page/simple_page.py
  35. 5
      examples/blueprintexample/simple_page/templates/pages/hello.html
  36. 5
      examples/blueprintexample/simple_page/templates/pages/index.html
  37. 20
      examples/blueprintexample/simple_page/templates/pages/layout.html
  38. 4
      examples/blueprintexample/simple_page/templates/pages/world.html
  39. 35
      examples/blueprintexample/test_blueprintexample.py
  40. 2
      examples/flaskr/.gitignore
  41. 40
      examples/flaskr/README
  42. 0
      examples/flaskr/flaskr/__init__.py
  43. 0
      examples/flaskr/flaskr/blueprints/__init__.py
  44. 85
      examples/flaskr/flaskr/blueprints/flaskr.py
  45. 64
      examples/flaskr/flaskr/factory.py
  46. 6
      examples/flaskr/flaskr/schema.sql
  47. 18
      examples/flaskr/flaskr/static/style.css
  48. 17
      examples/flaskr/flaskr/templates/layout.html
  49. 14
      examples/flaskr/flaskr/templates/login.html
  50. 21
      examples/flaskr/flaskr/templates/show_entries.html
  51. 2
      examples/flaskr/setup.cfg
  52. 27
      examples/flaskr/setup.py
  53. 83
      examples/flaskr/tests/test_flaskr.py
  54. 29
      examples/jqueryexample/jqueryexample.py
  55. 33
      examples/jqueryexample/templates/index.html
  56. 8
      examples/jqueryexample/templates/layout.html
  57. 2
      examples/minitwit/.gitignore
  58. 3
      examples/minitwit/MANIFEST.in
  59. 39
      examples/minitwit/README
  60. 1
      examples/minitwit/minitwit/__init__.py
  61. 256
      examples/minitwit/minitwit/minitwit.py
  62. 21
      examples/minitwit/minitwit/schema.sql
  63. 178
      examples/minitwit/minitwit/static/style.css
  64. 32
      examples/minitwit/minitwit/templates/layout.html
  65. 16
      examples/minitwit/minitwit/templates/login.html
  66. 19
      examples/minitwit/minitwit/templates/register.html
  67. 49
      examples/minitwit/minitwit/templates/timeline.html
  68. 2
      examples/minitwit/setup.cfg
  69. 16
      examples/minitwit/setup.py
  70. 150
      examples/minitwit/tests/test_minitwit.py
  71. 10
      examples/patterns/largerapp/setup.py
  72. 21
      examples/patterns/largerapp/tests/test_largerapp.py
  73. 13
      examples/patterns/largerapp/yourapplication/__init__.py
  74. 0
      examples/patterns/largerapp/yourapplication/static/style.css
  75. 0
      examples/patterns/largerapp/yourapplication/templates/index.html
  76. 0
      examples/patterns/largerapp/yourapplication/templates/layout.html
  77. 0
      examples/patterns/largerapp/yourapplication/templates/login.html
  78. 14
      examples/patterns/largerapp/yourapplication/views.py
  79. 14
      examples/tutorial/.gitignore
  80. 31
      examples/tutorial/LICENSE
  81. 7
      examples/tutorial/MANIFEST.in
  82. 76
      examples/tutorial/README.rst
  83. 48
      examples/tutorial/flaskr/__init__.py
  84. 108
      examples/tutorial/flaskr/auth.py
  85. 119
      examples/tutorial/flaskr/blog.py
  86. 54
      examples/tutorial/flaskr/db.py
  87. 20
      examples/tutorial/flaskr/schema.sql
  88. 134
      examples/tutorial/flaskr/static/style.css
  89. 15
      examples/tutorial/flaskr/templates/auth/login.html
  90. 15
      examples/tutorial/flaskr/templates/auth/register.html
  91. 24
      examples/tutorial/flaskr/templates/base.html
  92. 15
      examples/tutorial/flaskr/templates/blog/create.html
  93. 28
      examples/tutorial/flaskr/templates/blog/index.html
  94. 19
      examples/tutorial/flaskr/templates/blog/update.html
  95. 13
      examples/tutorial/setup.cfg
  96. 23
      examples/tutorial/setup.py
  97. 64
      examples/tutorial/tests/conftest.py
  98. 8
      examples/tutorial/tests/data.sql
  99. 66
      examples/tutorial/tests/test_auth.py
  100. 92
      examples/tutorial/tests/test_blog.py
  101. Some files were not shown because too many files have changed in this diff Show More

2
CONTRIBUTING.rst

@ -75,7 +75,7 @@ First time setup
.. _latest version of git: https://git-scm.com/downloads .. _latest version of git: https://git-scm.com/downloads
.. _username: https://help.github.com/articles/setting-your-username-in-git/ .. _username: https://help.github.com/articles/setting-your-username-in-git/
.. _email: https://help.github.com/articles/setting-your-email-in-git/ .. _email: https://help.github.com/articles/setting-your-email-in-git/
.. _Fork: https://github.com/pallets/flask/pull/2305#fork-destination-box .. _Fork: https://github.com/pallets/flask/fork
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork .. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
Start coding Start coding

BIN
docs/_static/flaskr.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

39
docs/conf.py

@ -11,13 +11,13 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
from __future__ import print_function from __future__ import print_function
import datetime
import os import os
import sys import sys
import pkg_resources
import time import time
import datetime
from sphinx.application import Sphinx import pkg_resources
BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
@ -300,7 +300,7 @@ unwrap_decorators()
del unwrap_decorators del unwrap_decorators
def setup(app: Sphinx): def setup(app):
def cut_module_meta(app, what, name, obj, options, lines): def cut_module_meta(app, what, name, obj, options, lines):
"""Remove metadata from autodoc output.""" """Remove metadata from autodoc output."""
if what != 'module': if what != 'module':
@ -312,3 +312,34 @@ def setup(app: Sphinx):
] ]
app.connect('autodoc-process-docstring', cut_module_meta) app.connect('autodoc-process-docstring', cut_module_meta)
def github_link(
name, rawtext, text, lineno, inliner,
options=None, content=None
):
app = inliner.document.settings.env.app
release = app.config.release
base_url = 'https://github.com/pallets/flask/tree/'
if text.endswith('>'):
words, text = text[:-1].rsplit('<', 1)
words = words.strip()
else:
words = None
if release.endswith('dev'):
url = '{0}master/{1}'.format(base_url, text)
else:
url = '{0}{1}/{2}'.format(base_url, release, text)
if words is None:
words = url
from docutils.nodes import reference
from docutils.parsers.rst.roles import set_classes
options = options or {}
set_classes(options)
node = reference(rawtext, words, refuri=url, **options)
return [node], []
app.add_role('gh', github_link)

2
docs/installation.rst

@ -104,6 +104,8 @@ On Windows:
\Python27\Scripts\virtualenv.exe venv \Python27\Scripts\virtualenv.exe venv
.. _install-activate-env:
Activate the environment Activate the environment
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~

6
docs/patterns/jquery.rst

@ -162,7 +162,5 @@ explanation of the little bit of code above:
argument. Note that we can use the `$SCRIPT_ROOT` variable here that argument. Note that we can use the `$SCRIPT_ROOT` variable here that
we set earlier. we set earlier.
If you don't get the whole picture, download the `sourcecode If you don't get the whole picture, download the :gh:`sourcecode
for this example for this example <examples/jqueryexample>`.
<https://github.com/pallets/flask/tree/master/examples/jqueryexample>`_
from GitHub.

6
docs/patterns/packages.rst

@ -17,9 +17,8 @@ this::
login.html login.html
... ...
If you find yourself stuck on something, feel free The :ref:`tutorial <tutorial>` is structured this way, see the
to take a look at the source code for this example. :gh:`example code <examples/tutorial>`.
You'll find `the full src for this example here`_.
Simple Packages Simple Packages
--------------- ---------------
@ -134,7 +133,6 @@ You should then end up with something like that::
.. _working-with-modules: .. _working-with-modules:
.. _the full src for this example here: https://github.com/pallets/flask/tree/master/examples/patterns/largerapp
Working with Blueprints Working with Blueprints
----------------------- -----------------------

5
docs/testing.rst

@ -28,10 +28,7 @@ The Application
First, we need an application to test; we will use the application from First, we need an application to test; we will use the application from
the :ref:`tutorial`. If you don't have that application yet, get the the :ref:`tutorial`. If you don't have that application yet, get the
source code from `the examples`_. source code from :gh:`the examples <examples/tutorial>`.
.. _the examples:
https://github.com/pallets/flask/tree/master/examples/flaskr/
The Testing Skeleton The Testing Skeleton
-------------------- --------------------

336
docs/tutorial/blog.rst

@ -0,0 +1,336 @@
.. currentmodule:: flask
Blog Blueprint
==============
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.
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.
The Blueprint
-------------
Define the blueprint and register it in the application factory.
.. code-block:: python
:caption: ``flaskr/blog.py``
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
from werkzeug.exceptions import abort
from flaskr.auth import login_required
from flaskr.db import get_db
bp = Blueprint('blog', __name__)
Import and register the blueprint from the factory using
:meth:`app.register_blueprint() <Flask.register_blueprint>`. Place the
new code at the end of the factory function before returning the app.
.. code-block:: python
:caption: ``flaskr/__init__.py``
def create_app():
app = ...
# existing code omitted
from . import blog
app.register_blueprint(blog.bp)
app.add_url_rule('/', endpoint='index')
return app
Unlike the auth blueprint, the blog blueprint does not have a
``url_prefix``. So the ``index`` view will be at ``/``, the ``create``
view at ``/create``, and so on. The blog is the main feature of Flaskr,
so it makes sense that the blog index will be the main index.
However, the endpoint for the ``index`` view defined below will be
``blog.index``. Some of the authentication views referred to a plain
``index`` endpoint. :meth:`app.add_url_rule() <Flask.add_url_rule>`
associates the endpoint name ``'index'`` with the ``/`` url so that
``url_for('index')`` or ``url_for('blog.index')`` will both work,
generating the same ``/`` URL either way.
In another application you might give the blog blueprint a
``url_prefix`` and define a separate ``index`` view in the application
factory, similar to the ``hello`` view. Then the ``index`` and
``blog.index`` endpoints and URLs would be different.
Index
-----
The index will show all of the posts, most recent first. A ``JOIN`` is
used so that the author information from the ``user`` table is
available in the result.
.. code-block:: python
:caption: ``flaskr/blog.py``
@bp.route('/')
def index():
db = get_db()
posts = db.execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' ORDER BY created DESC'
).fetchall()
return render_template('blog/index.html', posts=posts)
.. code-block:: html+jinja
:caption: ``flaskr/templates/blog/index.html``
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Posts{% endblock %}</h1>
{% if g.user %}
<a class="action" href="{{ url_for('blog.create') }}">New</a>
{% endif %}
{% endblock %}
{% block content %}
{% for post in posts %}
<article class="post">
<header>
<div>
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
</div>
{% if g.user['id'] == post['author_id'] %}
<a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ post['body'] }}</p>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
When a user is logged in, the ``header`` block adds a link to the
``create`` view. When the user is the author of a post, they'll see an
"Edit" link to the ``update`` view for that post. ``loop.last`` is a
special variable available inside `Jinja for loops`_. It's used to
display a line after each post except the last one, to visually separate
them.
.. _Jinja for loops: http://jinja.pocoo.org/docs/templates/#for
Create
------
The ``create`` view works the same as the auth ``register`` 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.
The ``login_required`` 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.
.. code-block:: python
:caption: ``flaskr/blog.py``
@bp.route('/create', methods=('GET', 'POST'))
@login_required
def create():
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'INSERT INTO post (title, body, author_id)'
' VALUES (?, ?, ?)',
(title, body, g.user['id'])
)
db.commit()
return redirect(url_for('blog.index'))
return render_template('blog/create.html')
.. code-block:: html+jinja
:caption: ``flaskr/templates/blog/create.html``
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
<input type="submit" value="Save">
</form>
{% endblock %}
Update
------
Both the ``update`` and ``delete`` views will need to fetch a ``post``
by ``id`` and check if the author matches the logged in user. To avoid
duplicating code, you can write a function to get the ``post`` and call
it from each view.
.. code-block:: python
:caption: ``flaskr/blog.py``
def get_post(id, check_author=True):
post = get_db().execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' WHERE p.id = ?',
(id,)
).fetchone()
if post is None:
abort(404, "Post id {0} doesn't exist.".format(id))
if check_author and post['author_id'] != g.user['id']:
abort(403)
return post
:func:`abort` 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. ``404`` means "Not Found", and ``403`` means
"Forbidden". (``401`` means "Unauthorized", but you redirect to the
login page instead of returning that status.)
The ``check_author`` argument is defined so that the function can be
used to get a ``post`` 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.
.. code-block:: python
:caption: ``flaskr/blog.py``
@bp.route('/<int:id>/update', methods=('GET', 'POST'))
@login_required
def update(id):
post = get_post(id)
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'UPDATE post SET title = ?, body = ?'
' WHERE id = ?',
(title, body, id)
)
db.commit()
return redirect(url_for('blog.index'))
return render_template('blog/update.html', post=post)
Unlike the views you've written so far, the ``update`` function takes
an argument, ``id``. That corresponds to the ``<int:id>`` in the route.
A real URL will look like ``/1/update``. Flask will capture the ``1``,
ensure it's an :class:`int`, and pass it as the ``id`` argument. If you
don't specify ``int:`` and instead do ``<id>``, it will be a string.
To generate a URL to the update page, :func:`url_for` needs to be passed
the ``id`` so it knows what to fill in:
``url_for('blog.update', id=post['id'])``. This is also in the
``index.html`` file above.
The ``create`` and ``update`` views look very similar. The main
difference is that the ``update`` view uses a ``post`` object and an
``UPDATE`` query instead of an ``INSERT``. 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.
.. code-block:: html+jinja
:caption: ``flaskr/templates/blog/update.html``
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title"
value="{{ request.form['title'] or post['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
<input type="submit" value="Save">
</form>
<hr>
<form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
</form>
{% endblock %}
This template has two forms. The first posts the edited data to the
current page (``/<id>/update``). The other form contains only a button
and specifies an ``action`` attribute that posts to the delete view
instead. The button uses some JavaScript to show a confirmation dialog
before submitting.
The pattern ``{{ request.form['title'] or post['title'] }}`` is used to
choose what data appears in the form. When the form hasn't been
submitted, the original ``post`` data appears, but if invalid form data
was posted you want to display that so the user can fix the error, so
``request.form`` is used instead. :data:`request` is another variable
that's automatically available in templates.
Delete
------
The delete view doesn't have its own template, the delete button is part
of ``update.html`` and posts to the ``/<id>/delete`` URL. Since there
is no template, it will only handle the ``POST`` method then redirect
to the ``index`` view.
.. code-block:: python
:caption: ``flaskr/blog.py``
@bp.route('/<int:id>/delete', methods=('POST',))
@login_required
def delete(id):
get_post(id)
db = get_db()
db.execute('DELETE FROM post WHERE id = ?', (id,))
db.commit()
return redirect(url_for('blog.index'))
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.
Continue to :doc:`install`.

31
docs/tutorial/css.rst

@ -1,31 +0,0 @@
.. _tutorial-css:
Step 8: Adding Style
====================
Now that everything else works, it's time to add some style to the
application. Just create a stylesheet called :file:`style.css` in the
:file:`static` folder:
.. sourcecode:: css
body { font-family: sans-serif; background: #eee; }
a, h1, h2 { color: #377ba8; }
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl { font-weight: bold; }
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
margin-bottom: 1em; background: #fafafa; }
.flash { background: #cee5F5; padding: 0.5em;
border: 1px solid #aacbe2; }
.error { background: #f0d6d6; padding: 0.5em; }
Continue with :ref:`tutorial-testing`.

213
docs/tutorial/database.rst

@ -0,0 +1,213 @@
.. currentmodule:: flask
Define and Access the Database
==============================
The application will use a `SQLite`_ database to store users and posts.
Python comes with built-in support for SQLite in the :mod:`sqlite3`
module.
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.
The tutorial doesn't go into detail about SQL. If you are not familiar
with it, the SQLite docs describe the `language`_.
.. _SQLite: https://sqlite.org/about.html
.. _language: https://sqlite.org/lang.html
Connect to the Database
-----------------------
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.
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.
.. code-block:: python
:caption: ``flaskr/db.py``
import sqlite3
import click
from flask import current_app, g
from flask.cli import with_appcontext
def get_db():
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
:data:`g` 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 ``get_db`` is called a second time in the same
request.
:data:`current_app` 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.
``get_db`` will be called when the application has been created and is
handling a request, so :data:`current_app` can be used.
:func:`sqlite3.connect` establishes a connection to the file pointed at
by the ``DATABASE`` configuration key. This file doesn't have to exist
yet, and won't until you initialize the database later.
:class:`sqlite3.Row` tells the connection to return rows that behave
like dicts. This allows accessing the columns by name.
``close_db`` checks if a connection was created by checking if ``g.db``
was set. If the connection exists, it is closed. Further down you will
tell your application about the ``close_db`` function in the application
factory so that it is called after each request.
Create the Tables
-----------------
In SQLite, data is stored in *tables* and *columns*. These need to be
created before you can store and retrieve data. Flaskr will store users
in the ``user`` table, and posts in the ``post`` table. Create a file
with the SQL commands needed to create empty tables:
.. code-block:: sql
:caption: ``flaskr/schema.sql``
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS post;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES user (id)
);
Add the Python functions that will run these SQL commands to the
``db.py`` file:
.. code-block:: python
:caption: ``flaskr/db.py``
def init_db():
db = get_db()
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))
@click.command('init-db')
@with_appcontext
def init_db_command():
"""Clear the existing data and create new tables."""
init_db()
click.echo('Initialized the database.')
:meth:`open_resource() <Flask.open_resource>` opens a file relative to
the ``flaskr`` package, which is useful since you won't necessarily know
where that location is when deploying the application later. ``get_db``
returns a database connection, which is used to execute the commands
read from the file.
:func:`click.command` defines a command line command called ``init-db``
that calls the ``init_db`` function and shows a success message to the
user. You can read :ref:`cli` to learn more about writing commands.
Register with the Application
-----------------------------
The ``close_db`` and ``init_db_command`` 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.
.. code-block:: python
:caption: ``flaskr/db.py``
def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)
:meth:`app.teardown_appcontext() <Flask.teardown_appcontext>` tells
Flask to call that function when cleaning up after returning the
response.
:meth:`app.cli.add_command() <click.Group.add_command>` adds a new
command that can be called with the ``flask`` command.
Import and call this function from the factory. Place the new code at
the end of the factory function before returning the app.
.. code-block:: python
:caption: ``flaskr/__init__.py``
def create_app():
app = ...
# existing code omitted
from . import db
db.init_app(app)
return app
Initialize the Database File
----------------------------
Now that ``init-db`` has been registered with the app, it can be called
using the ``flask`` command, similar to the ``run`` command from the
previous page.
.. note::
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 :ref:`install-activate-env`.
You'll also need to set ``FLASK_APP`` and ``FLASK_ENV`` as shown on
the previous page.
Run the ``init-db`` command:
.. code-block:: none
flask init-db
Initialized the database.
There will now be a ``flaskr.sqlite`` file in the ``instance`` folder in
your project.
Continue to :doc:`views`.

78
docs/tutorial/dbcon.rst

@ -1,78 +0,0 @@
.. _tutorial-dbcon:
Step 4: Database Connections
----------------------------
Let's continue building our code in the ``flaskr.py`` file.
(Scroll to the end of the page for more about project layout.)
You currently have a function for establishing a database connection with
`connect_db`, but by itself, it is not particularly useful. Creating and
closing database connections all the time is very inefficient, so you will
need to keep it around for longer. Because database connections
encapsulate a transaction, you will need to make sure that only one
request at a time uses the connection. An elegant way to do this is by
utilizing the *application context*.
Flask provides two contexts: the *application context* and the
*request context*. For the time being, all you have to know is that there
are special variables that use these. For instance, the
:data:`~flask.request` variable is the request object associated with
the current request, whereas :data:`~flask.g` is a general purpose
variable associated with the current application context. The tutorial
will cover some more details of this later on.
For the time being, all you have to know is that you can store information
safely on the :data:`~flask.g` object.
So when do you put it on there? To do that you can make a helper
function. The first time the function is called, it will create a database
connection for the current context, and successive calls will return the
already established connection::
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db
Now you know how to connect, but how can you properly disconnect? For
that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
decorator. It's executed every time the application context tears down::
@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()
Functions marked with :meth:`~flask.Flask.teardown_appcontext` are called
every time the app context tears down. What does this mean?
Essentially, the app context is created before the request comes in and is
destroyed (torn down) whenever the request finishes. A teardown can
happen because of two reasons: either everything went well (the error
parameter will be ``None``) or an exception happened, in which case the error
is passed to the teardown function.
Curious about what these contexts mean? Have a look at the
:ref:`app-context` documentation to learn more.
Continue to :ref:`tutorial-dbinit`.
.. hint:: Where do I put this code?
If you've been following along in this tutorial, you might be wondering
where to put the code from this step and the next. A logical place is to
group these module-level functions together, and put your new
``get_db`` and ``close_db`` functions below your existing
``connect_db`` function (following the tutorial line-by-line).
If you need a moment to find your bearings, take a look at how the `example
source`_ is organized. In Flask, you can put all of your application code
into a single Python module. You don't have to, and if your app :ref:`grows
larger <larger-applications>`, it's a good idea not to.
.. _example source:
https://github.com/pallets/flask/tree/master/examples/flaskr/

80
docs/tutorial/dbinit.rst

@ -1,80 +0,0 @@
.. _tutorial-dbinit:
Step 5: Creating The Database
=============================
As outlined earlier, Flaskr is a database powered application, and more
precisely, it is an application powered by a relational database system. Such
systems need a schema that tells them how to store that information.
Before starting the server for the first time, it's important to create
that schema.
Such a schema could be created by piping the ``schema.sql`` file into the
``sqlite3`` command as follows::
sqlite3 /tmp/flaskr.db < schema.sql
However, the downside of this is that it requires the ``sqlite3`` command
to be installed, which is not necessarily the case on every system. This
also requires that you provide the path to the database, which can introduce
errors.
Instead of the ``sqlite3`` command above, it's a good idea to add a function
to our application that initializes the database for you. To do this, you
can create a function and hook it into a :command:`flask` command that
initializes the database.
Take a look at the code segment below. A good place to add this function,
and command, is just below the ``connect_db`` function in :file:`flaskr.py`::
def init_db():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
@app.cli.command('initdb')
def initdb_command():
"""Initializes the database."""
init_db()
print('Initialized the database.')
The ``app.cli.command()`` decorator registers a new command with the
:command:`flask` script. When the command executes, Flask will automatically
create an application context which is bound to the right application.
Within the function, you can then access :attr:`flask.g` and other things as
you might expect. When the script ends, the application context tears down
and the database connection is released.
You will want to keep an actual function around that initializes the database,
though, so that we can easily create databases in unit tests later on. (For
more information see :ref:`testing`.)
The :func:`~flask.Flask.open_resource` method of the application object
is a convenient helper function that will open a resource that the
application provides. This function opens a file from the resource
location (the :file:`flaskr/flaskr` folder) and allows you to read from it.
It is used in this example to execute a script on the database connection.
The connection object provided by SQLite can give you a cursor object.
On that cursor, there is a method to execute a complete script. Finally, you
only have to commit the changes. SQLite3 and other transactional
databases will not commit unless you explicitly tell it to.
Now, in a terminal, from the application root directory :file:`flaskr/` it is
possible to create a database with the :command:`flask` script::
flask initdb
Initialized the database.
.. admonition:: Troubleshooting
If you get an exception later on stating that a table cannot be found, check
that you did execute the ``initdb`` command and that your table names are
correct (singular vs. plural, for example).
Continue with :ref:`tutorial-views`

121
docs/tutorial/deploy.rst

@ -0,0 +1,121 @@
Deploy to Production
====================
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
:doc:`/deploying/index` for a list of many different ways to host your
application.
Build and Install
-----------------
When you want to deploy your application elsewhere, you build a
distribution file. The current standard for Python distribution is the
*wheel* format, with the ``.whl`` extension. Make sure the wheel library
is installed first:
.. code-block:: none
pip install wheel
Running ``setup.py`` with Python gives you a command line tool to issue
build-related commands. The ``bdist_wheel`` command will build a wheel
distribution file.
.. code-block:: none
python setup.py bdist_wheel
You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The
file name is the name of the project, the version, and some tags about
the file can install.
Copy this file to another machine,
:ref:`set up a new virtualenv <install-create-env>`, then install the
file with ``pip``.
.. code-block:: none
pip install flaskr-1.0.0-py3-none-any.whl
Pip will install your project along with its dependencies.
Since this is a different machine, you need to run ``init-db`` again to
create the database in the instance folder.
.. code-block:: none
export FLASK_APP=flaskr
flask init-db
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
``venv/var/flaskr-instance`` instead.
Configure the Secret Key
------------------------
In the beginning of the tutorial that you gave a default value for
:data:`SECRET_KEY`. This should be changed to some random bytes in
production. Otherwise, attackers could use the public ``'dev'`` key to
modify the session cookie, or anything else that uses the secret key.
You can use the following command to output a random secret key:
.. code-block:: none
python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
Create the ``config.py`` file in the instance folder, which the factory
will read from if it exists. Copy the generated value into it.
.. code-block:: python
:caption: ``venv/var/flaskr-instance/config.py``
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
You can also set any other necessary configuration here, although
``SECRET_KEY`` is the only one needed for Flaskr.
Run with a Production Server
----------------------------
When running publicly rather than in development, you should not use the
built-in development server (``flask run``). The development server is
provided by Werkzeug for convenience, but is not designed to be
particularly efficient, stable, or secure.
Instead, use a production WSGI server. For example, to use `Waitress`_,
first install it in the virtual environment:
.. code-block:: none
pip install waitress
You need to tell Waitress about your application, but it doesn't use
``FLASK_APP`` like ``flask run`` does. You need to tell it to import and
call the application factory to get an application object.
.. code-block:: none
waitress-serve --call 'flaskr:create_app'
Serving on http://0.0.0.0:8080
See :doc:`/deploying/index` 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.
.. _Waitress: https://docs.pylonsproject.org/projects/waitress/
Continue to :doc:`next`.

177
docs/tutorial/factory.rst

@ -0,0 +1,177 @@
.. currentmodule:: flask
Application Setup
=================
A Flask application is an instance of the :class:`Flask` class.
Everything about the application, such as configuration and URLs, will
be registered with this class.
The most straightforward way to create a Flask application is to create
a global :class:`Flask` 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.
Instead of creating a :class:`Flask` instance globally, you will create
it inside a function. This function is known as the *application
factory*. Any configuration, registration, and other setup the
application needs will happen inside the function, then the application
will be returned.
The Application Factory
-----------------------
It's time to start coding! Create the ``flaskr`` directory and add the
``__init__.py`` file. The ``__init__.py`` serves double duty: it will
contain the application factory, and it tells Python that the ``flaskr``
directory should be treated as a package.
.. code-block:: none
mkdir flaskr
.. code-block:: python
:caption: ``flaskr/__init__.py``
import os
from flask import Flask
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# a simple page that says hello
@app.route('/hello')
def hello():
return 'Hello, World!'
return app
``create_app`` is the application factory function. You'll add to it
later in the tutorial, but it already does a lot.
#. ``app = Flask(__name__, instance_relative_config=True)`` creates the
:class:`Flask` instance.
* ``__name__`` is the name of the current Python module. The app
needs to know where it's located to set up some paths, and
``__name__`` is a convenient way to tell it that.
* ``instance_relative_config=True`` tells the app that
configuration files are relative to the
:ref:`instance folder <instance-folders>`. The instance folder
is located outside the ``flaskr`` package and can hold local
data that shouldn't be committed to version control, such as
configuration secrets and the database file.
#. :meth:`app.config.from_mapping() <Config.from_mapping>` sets
some default configuration that the app will use:
* :data:`SECRET_KEY` is used by Flask and extensions to keep data
safe. It's set to ``'dev'`` to provide a convenient value
during development, but it should be overridden with a random
value when deploying.
* ``DATABASE`` is the path where the SQLite database file will be
saved. It's under
:attr:`app.instance_path <Flask.instance_path>`, which is the
path that Flask has chosen for the instance folder. You'll learn
more about the database in the next section.
#. :meth:`app.config.from_pyfile() <Config.from_pyfile>` overrides
the default configuration with values taken from the ``config.py``
file in the instance folder if it exists. For example, when
deploying, this can be used to set a real ``SECRET_KEY``.
* ``test_config`` 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.
#. :func:`os.makedirs` ensures that
:attr:`app.instance_path <Flask.instance_path>` 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.
#. :meth:`@app.route() <Flask.route>` 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 ``/hello`` and a
function that returns a response, the string ``'Hello, World!'`` in
this case.
Run The Application
-------------------
Now you can run your application using the ``flask`` command. From the
terminal, tell Flask where to find your application, then run it in
development mode.
Development 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.
For Linux and Mac:
.. code-block:: none
export FLASK_APP=flaskr
export FLASK_ENV=development
flask run
For Windows cmd, use ``set`` instead of ``export``:
.. code-block:: none
set FLASK_APP=flaskr
set FLASK_ENV=development
flask run
For Windows PowerShell, use ``$env:`` instead of ``export``:
.. code-block:: none
$env:FLASK_APP = "flaskr"
$env:FLASK_ENV = "development"
flask run
You'll see output similar to this:
.. code-block:: none
* Serving Flask app "flaskr"
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 855-212-761
Visit http://127.0.0.1:5000/hello in a browser and you should see the
"Hello, World!" message. Congratulations, you're now running your Flask
web application!
Continue to :doc:`database`.

BIN
docs/tutorial/flaskr_edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/tutorial/flaskr_index.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/tutorial/flaskr_login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

31
docs/tutorial/folders.rst

@ -1,31 +0,0 @@
.. _tutorial-folders:
Step 0: Creating The Folders
============================
It is recommended to install your Flask application within a virtualenv. Please
read the :ref:`installation` section to set up your environment.
Now that you have installed Flask, you will need to create the folders required
for this tutorial. Your directory structure will look like this::
/flaskr
/flaskr
/static
/templates
The application will be installed and run as Python package. This is the
recommended way to install and run Flask applications. You will see exactly
how to run ``flaskr`` later on in this tutorial.
For now go ahead and create the applications directory structure. In the next
few steps you will be creating the database schema as well as the main module.
As a quick side note, the files inside of the :file:`static` folder are
available to users of the application via HTTP. This is the place where CSS and
JavaScript files go. Inside the :file:`templates` folder, Flask will look for
`Jinja2`_ templates. You will see examples of this later on.
For now you should continue with :ref:`tutorial-schema`.
.. _Jinja2: http://jinja.pocoo.org/

79
docs/tutorial/index.rst

@ -3,31 +3,64 @@
Tutorial Tutorial
======== ========
Learn by example to develop an application with Python and Flask. .. toctree::
:caption: Contents:
:maxdepth: 1
In this tutorial, we will create a simple blogging application. It only layout
supports one user, only allows text entries, and has no feeds or comments. factory
database
views
templates
static
blog
install
tests
deploy
next
While very simple, this example still features everything you need to get This tutorial will walk you through creating a basic blog application
started. In addition to Flask, we will use SQLite for the database, which is called Flaskr. Users will be able to register, log in, create posts,
built-in to Python, so there is nothing else you need. and edit or delete their own posts. You will be able to package and
install the application on other computers.
If you want the full source code in advance or for comparison, check out .. image:: flaskr_index.png
the `example source`_. :align: center
:class: screenshot
:alt: screenshot of index page
.. _example source: https://github.com/pallets/flask/tree/master/examples/flaskr/ It's assumed that you're already familiar with Python. The `official
tutorial`_ in the Python docs is a great way to learn or review first.
.. toctree:: .. _official tutorial: https://docs.python.org/3/tutorial/
:maxdepth: 2
While it's designed to give a good starting point, the tutorial doesn't
introduction cover all of Flask's features. Check out the :ref:`quickstart` for an
folders overview of what Flask can do, then dive into the docs to find out more.
schema The tutorial only uses what's provided by Flask and Python. In another
setup project, you might decide to use :ref:`extensions` or other libraries to
packaging make some tasks simpler.
dbcon
dbinit .. image:: flaskr_login.png
views :align: center
templates :class: screenshot
css :alt: screenshot of login page
testing
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.
.. image:: flaskr_edit.png
:align: center
:class: screenshot
:alt: screenshot of login page
:gh:`The tutorial project is available as an example in the Flask
repository <examples/tutorial>`, if you want to compare your project
with the final product as you follow the tutorial.
Continue to :doc:`layout`.

113
docs/tutorial/install.rst

@ -0,0 +1,113 @@
Make the Project Installable
============================
Making your project installable means that you can build a
*distribution* 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.
Installing also comes with other benefits that might not be obvious from
the tutorial or as a new Python user, including:
* Currently, Python and Flask understand how to use the ``flaskr``
package only because you're running from your project's directory.
Installing means you can import it no matter where you run from.
* You can manage your project's dependencies just like other packages
do, so ``pip install yourproject.whl`` installs them.
* Test tools can isolate your test environment from your development
environment.
.. note::
This is being introduced late in the tutorial, but in your future
projects you should always start with this.
Describe the Project
--------------------
The ``setup.py`` file describes your project and the files that belong
to it.
.. code-block:: python
:caption: ``setup.py``
from setuptools import find_packages, setup
setup(
name='flaskr',
version='1.0.0',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'flask',
],
)
``packages`` tells Python what package directories (and the Python files
they contain) to include. ``find_packages()`` finds these directories
automatically so you don't have to type them out. To include other
files, such as the static and templates directories,
``include_package_data`` is set. Python needs another file named
``MANIFEST.in`` to tell what this other data is.
.. code-block:: none
:caption: ``MANIFEST.in``
include flaskr/schema.sql
graft flaskr/static
graft flaskr/templates
global-exclude *.pyc
This tells Python to copy everything in the ``static`` and ``templates``
directories, and the ``schema.sql`` file, but to exclude all bytecode
files.
See the `official packaging guide`_ for another explanation of the files
and options used.
.. _official packaging guide: https://packaging.python.org/tutorials/distributing-packages/
Install the Project
-------------------
Use ``pip`` to install your project in the virtual environment.
.. code-block:: none
pip install -e .
This tells pip to find ``setup.py`` in the current directory and install
it in *editable* or *development* 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.
You can observe that the project is now installed with ``pip list``.
.. code-block:: none
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
setuptools 39.0.1
Werkzeug 0.14.1
wheel 0.30.0
Nothing changes from how you've been running your project so far.
``FLASK_APP`` is still set to ``flaskr`` and ``flask run`` still runs
the application.
Continue to :doc:`tests`.

39
docs/tutorial/introduction.rst

@ -1,39 +0,0 @@
.. _tutorial-introduction:
Introducing Flaskr
==================
This tutorial will demonstrate a blogging application named Flaskr, but feel
free to choose your own less Web-2.0-ish name ;) Essentially, it will do the
following things:
1. Let the user sign in and out with credentials specified in the
configuration. Only one user is supported.
2. When the user is logged in, they can add new entries to the page
consisting of a text-only title and some HTML for the text. This HTML
is not sanitized because we trust the user here.
3. The index page shows all entries so far in reverse chronological order
(newest on top) and the user can add new ones from there if logged in.
SQLite3 will be used directly for this application because it's good enough
for an application of this size. For larger applications, however,
it makes a lot of sense to use `SQLAlchemy`_, as it handles database
connections in a more intelligent way, allowing you to target different
relational databases at once and more. You might also want to consider
one of the popular NoSQL databases if your data is more suited for those.
.. warning::
If you're following the tutorial from a specific version of the docs, be
sure to check out the same tag in the repository, otherwise the tutorial
may be different than the example.
Here is a screenshot of the final application:
.. image:: ../_static/flaskr.png
:align: center
:class: screenshot
:alt: screenshot of the final application
Continue with :ref:`tutorial-folders`.
.. _SQLAlchemy: https://www.sqlalchemy.org/

110
docs/tutorial/layout.rst

@ -0,0 +1,110 @@
Project Layout
==============
Create a project directory and enter it:
.. code-block:: none
mkdir flask-tutorial
cd flask-tutorial
Then follow the :doc:`installation instructions </installation>` to set
up a Python virtual environment and install Flask for your project.
The tutorial will assume you're working from the ``flask-tutorial``
directory from now on. The file names at the top of each code block are
relative to this directory.
----
A Flask application can be as simple as a single file.
.. code-block:: python
:caption: ``hello.py``
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
However, as a project get bigger, it becomes overwhelming to keep all
the code in one file. Python projects use *packages* to organize code
into multiple modules that can be imported where needed, and the
tutorial will do this as well.
The project directory will contain:
* ``flaskr/``, a Python package containing your application code and
files.
* ``tests/``, a directory containing test modules.
* ``venv/``, a Python virtual environment where Flask and other
dependencies are installed.
* Installation files telling Python how to install your project.
* Version control config, such as `git`_. You should make a habit of
using some type of version control for all your projects, no matter
the size.
* Any other project files you might add in the future.
.. _git: https://git-scm.com/
By the end, your project layout will look like this:
.. code-block:: none
/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/
├── setup.py
└── MANIFEST.in
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:
.. code-block:: none
:caption: ``.gitignore``
venv/
*.pyc
__pycache__/
instance/
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/
Continue to :doc:`factory`.

38
docs/tutorial/next.rst

@ -0,0 +1,38 @@
Keep Developing!
================
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
:gh:`example project <examples/tutorial>`, which might look a bit
different due to the step-by-step nature of the tutorial.
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 :ref:`quickstart` for an overview of what Flask can do, then
dive into the docs to keep learning. Flask uses `Jinja`_, `Click`_,
`Werkzeug`_, and `ItsDangerous`_ behind the scenes, and they all have
their own documentation too. You'll also be interested in
:ref:`extensions` which make tasks like working with the database or
validating form data easier and more powerful.
If you want to keep developing your Flaskr project, here are some ideas
for what to try next:
* A detail view to show a single post. Click a post's title to go to
its page.
* Like / unlike a post.
* Comments.
* Tags. Clicking a tag shows all the posts with that tag.
* A search box that filters the index page by name.
* Paged display. Only show 5 posts per page.
* Upload an image to go along with a post.
* Format posts using Markdown.
* An RSS feed of new posts.
Have fun and make awesome applications!
.. _Jinja: https://palletsprojects.com/p/jinja/
.. _Click: https://palletsprojects.com/p/click/
.. _Werkzeug: https://palletsprojects.com/p/werkzeug/
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/

108
docs/tutorial/packaging.rst

@ -1,108 +0,0 @@
.. _tutorial-packaging:
Step 3: Installing flaskr as a Package
======================================
Flask is now shipped with built-in support for `Click`_. Click provides
Flask with enhanced and extensible command line utilities. Later in this
tutorial you will see exactly how to extend the ``flask`` command line
interface (CLI).
A useful pattern to manage a Flask application is to install your app
following the `Python Packaging Guide`_. Presently this involves
creating two new files; :file:`setup.py` and :file:`MANIFEST.in` in the
projects root directory. You also need to add an :file:`__init__.py`
file to make the :file:`flaskr/flaskr` directory a package. After these
changes, your code structure should be::
/flaskr
/flaskr
__init__.py
/static
/templates
flaskr.py
schema.sql
setup.py
MANIFEST.in
Create the ``setup.py`` file for ``flaskr`` with the following content::
from setuptools import setup
setup(
name='flaskr',
packages=['flaskr'],
include_package_data=True,
install_requires=[
'flask',
],
)
When using setuptools, it is also necessary to specify any special files
that should be included in your package (in the :file:`MANIFEST.in`).
In this case, the static and templates directories need to be included,
as well as the schema.
Create the :file:`MANIFEST.in` and add the following lines::
graft flaskr/templates
graft flaskr/static
include flaskr/schema.sql
Next, to simplify locating the application, create the file,
:file:`flaskr/__init__.py` containing only the following import statement::
from .flaskr import app
This import statement brings the application instance into the top-level
of the application package. When it is time to run the application, the
Flask development server needs the location of the app instance. This
import statement simplifies the location process. Without the above
import statement, the export statement a few steps below would need to be
``export FLASK_APP=flaskr.flaskr``.
At this point you should be able to install the application. As usual, it
is recommended to install your Flask application within a `virtualenv`_.
With that said, from the ``flaskr/`` directory, go ahead and install the
application with::
pip install --editable .
The above installation command assumes that it is run within the projects
root directory, ``flaskr/``. The ``editable`` flag allows editing
source code without having to reinstall the Flask app each time you make
changes. The flaskr app is now installed in your virtualenv (see output
of ``pip freeze``).
With that out of the way, you should be able to start up the application.
Do this on Mac or Linux with the following commands in ``flaskr/``::
export FLASK_APP=flaskr
export FLASK_ENV=development
flask run
(In case you are on Windows you need to use ``set`` instead of ``export``).
Exporting ``FLASK_ENV=development`` turns on all development features
such as enabling the interactive debugger.
*Never leave debug mode activated in a production system*, because it will
allow users to execute code on the server!
You will see a message telling you that server has started along with
the address at which you can access it in a browser.
When you head over to the server in your browser, you will get a 404 error
because we don't have any views yet. That will be addressed a little later,
but first, you should get the database working.
.. admonition:: Externally Visible Server
Want your server to be publicly available? Check out the
:ref:`externally visible server <public-server>` section for more
information.
Continue with :ref:`tutorial-dbcon`.
.. _Click: http://click.pocoo.org
.. _Python Packaging Guide: https://packaging.python.org
.. _virtualenv: https://virtualenv.pypa.io

25
docs/tutorial/schema.rst

@ -1,25 +0,0 @@
.. _tutorial-schema:
Step 1: Database Schema
=======================
In this step, you will create the database schema. Only a single table is
needed for this application and it will only support SQLite. All you need to do
is put the following contents into a file named :file:`schema.sql` in the
:file:`flaskr/flaskr` folder:
.. sourcecode:: sql
drop table if exists entries;
create table entries (
id integer primary key autoincrement,
title text not null,
'text' text not null
);
This schema consists of a single table called ``entries``. Each row in
this table has an ``id``, a ``title``, and a ``text``. The ``id`` is an
automatically incrementing integer and a primary key, the other two are
strings that must not be null.
Continue with :ref:`tutorial-setup`.

101
docs/tutorial/setup.rst

@ -1,101 +0,0 @@
.. _tutorial-setup:
Step 2: Application Setup Code
==============================
Next, we will create the application module, :file:`flaskr.py`. Just like the
:file:`schema.sql` file you created in the previous step, this file should be
placed inside of the :file:`flaskr/flaskr` folder.
For this tutorial, all the Python code we use will be put into this file
(except for one line in ``__init__.py``, and any testing or optional files you
decide to create).
The first several lines of code in the application module are the needed import
statements. After that there will be a few lines of configuration code.
For small applications like ``flaskr``, it is possible to drop the configuration
directly into the module. However, a cleaner solution is to create a separate
``.py`` file, load that, and import the values from there.
Here are the import statements (in :file:`flaskr.py`)::
import os
import sqlite3
from flask import (Flask, request, session, g, redirect, url_for, abort,
render_template, flash)
The next couple lines will create the actual application instance and
initialize it with the config from the same file in :file:`flaskr.py`::
app = Flask(__name__) # create the application instance :)
app.config.from_object(__name__) # load config from this file , flaskr.py
# Load default config and override config from an environment variable
app.config.update(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/',
USERNAME='admin',
PASSWORD='default'
)
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
In the above code, the :class:`~flask.Config` object works similarly to a
dictionary, so it can be updated with new values.
.. admonition:: Database Path
Operating systems know the concept of a current working directory for
each process. Unfortunately, you cannot depend on this in web
applications because you might have more than one application in the
same process.
For this reason the ``app.root_path`` attribute can be used to
get the path to the application. Together with the ``os.path`` module,
files can then easily be found. In this example, we place the
database right next to it.
For a real-world application, it's recommended to use
:ref:`instance-folders` instead.
Usually, it is a good idea to load a separate, environment-specific
configuration file. Flask allows you to import multiple configurations and it
will use the setting defined in the last import. This enables robust
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve
this. ::
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
If you want to do this (not required for this tutorial) simply define the
environment variable :envvar:`FLASKR_SETTINGS` that points to a config file
to be loaded. The silent switch just tells Flask to not complain if no such
environment key is set.
In addition to that, you can use the :meth:`~flask.Config.from_object`
method on the config object and provide it with an import name of a
module. Flask will then initialize the variable from that module. Note
that in all cases, only variable names that are uppercase are considered.
The :data:`SECRET_KEY` is needed to keep the client-side sessions secure.
Choose that key wisely and as hard to guess and complex as possible.
Lastly, add a method that allows for easy connections to the specified
database. ::
def connect_db():
"""Connects to the specific database."""
rv = sqlite3.connect(app.config['DATABASE'])
rv.row_factory = sqlite3.Row
return rv
This can be used to open a connection on request and also from the
interactive Python shell or a script. This will come in handy later.
You can create a simple database connection through SQLite and then tell
it to use the :class:`sqlite3.Row` object to represent rows. This allows
the rows to be treated as if they were dictionaries instead of tuples.
In the next section you will see how to run the application.
Continue with :ref:`tutorial-packaging`.

72
docs/tutorial/static.rst

@ -0,0 +1,72 @@
Static Files
============
The authentication views and templates work, but they look very plain
right now. Some `CSS`_ can be added to add style to the HTML layout you
constructed. The style won't change, so it's a *static* file rather than
a template.
Flask automatically adds a ``static`` view that takes a path relative
to the ``flaskr/static`` directory and serves it. The ``base.html``
template already has a link to the ``style.css`` file:
.. code-block:: html+jinja
{{ url_for('static', filename='style.css') }}
Besides CSS, other types of static files might be files with JavaScript
functions, or a logo image. They are all placed under the
``flaskr/static`` directory and referenced with
``url_for('static', filename='...')``.
This tutorial isn't focused on how to write CSS, so you can just copy
the following into the ``flaskr/static/style.css`` file:
.. code-block:: css
:caption: ``flaskr/static/style.css``
html { font-family: sans-serif; background: #eee; padding: 1rem; }
body { max-width: 960px; margin: 0 auto; background: white; }
h1 { font-family: serif; color: #377ba8; margin: 1rem 0; }
a { color: #377ba8; }
hr { border: none; border-top: 1px solid lightgray; }
nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; }
nav h1 { flex: auto; margin: 0; }
nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; }
nav ul { display: flex; list-style: none; margin: 0; padding: 0; }
nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; }
.content { padding: 0 1rem 1rem; }
.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; }
.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; }
.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; }
.post > header { display: flex; align-items: flex-end; font-size: 0.85em; }
.post > header > div:first-of-type { flex: auto; }
.post > header h1 { font-size: 1.5em; margin-bottom: 0; }
.post .about { color: slategray; font-style: italic; }
.post .body { white-space: pre-line; }
.content:last-child { margin-bottom: 0; }
.content form { margin: 1em 0; display: flex; flex-direction: column; }
.content label { font-weight: bold; margin-bottom: 0.5em; }
.content input, .content textarea { margin-bottom: 1em; }
.content textarea { min-height: 12em; resize: vertical; }
input.danger { color: #cc2f2e; }
input[type=submit] { align-self: start; min-width: 10em; }
You can find a less compact version of ``style.css`` in the
:gh:`example code <examples/tutorial/flaskr/static/style.css>`.
Go to http://127.0.0.1/auth/login and the page should look like the
screenshot below.
.. image:: flaskr_login.png
:align: center
:class: screenshot
:alt: screenshot of login page
You can read more about CSS from `Mozilla's documentation <CSS_>`_. If
you change a static file, refresh the browser page. If the change
doesn't show up, try clearing your browser's cache.
.. _CSS: https://developer.mozilla.org/docs/Web/CSS
Continue to :doc:`blog`.

268
docs/tutorial/templates.rst

@ -1,113 +1,187 @@
.. _tutorial-templates: .. currentmodule:: flask
Templates
=========
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.
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.
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.
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.
.. _Jinja: http://jinja.pocoo.org/docs/templates/
.. _HTML: https://developer.mozilla.org/docs/Web/HTML
The Base Layout
---------------
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``
Step 7: The Templates <!doctype html>
===================== <title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1>Flaskr</h1>
<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>
Now it is time to start working on the templates. As you may have :data:`g` is automatically available in templates. Based on if
noticed, if you make requests with the app running, you will get ``g.user`` is set (from ``load_logged_in_user``), either the username
an exception that Flask cannot find the templates. The templates and a log out link are displayed, otherwise links to register and log in
are using `Jinja2`_ syntax and have autoescaping enabled by are displayed. :func:`url_for` is also automatically available, and is
default. This means that unless you mark a value in the code with used to generate URLs to views instead of writing them out manually.
: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.
We are also using template inheritance which makes it possible to reuse After the page title, and before the content, the template loops over
the layout of the website in all pages. 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.
Create the follwing three HTML files and place them in the There are three blocks defined here that will be overridden in the other
:file:`templates` folder: templates:
.. _Jinja2: http://jinja.pocoo.org/docs/templates #. ``{% block title %}`` will change the title displayed in the
browser's tab and window title.
layout.html #. ``{% block header %}`` is similar to ``title`` but will change the
----------- title displayed on the page.
This template contains the HTML skeleton, the header and a link to log in #. ``{% block content %}`` is where the content of each page goes, such
(or log out if the user was already logged in). It also displays the as the login form or a blog post.
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 The base template is directly in the ``templates`` directory. To keep
you can use that to check if the user is logged in or not. Note that in the others organized, the templates for a blueprint will be placed in a
Jinja you can access missing attributes and items of objects / dicts which directory with the same name as the blueprint.
makes the following code work, even if there is no ``'logged_in'`` key in
the session:
.. sourcecode:: html+jinja
<!doctype html> Register
<title>Flaskr</title> --------
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page> .. code-block:: html+jinja
<h1>Flaskr</h1> :caption: ``flaskr/templates/auth/register.html``
<div class=metanav>
{% if not session.logged_in %} {% extends 'base.html' %}
<a href="{{ url_for('login') }}">log in</a>
{% else %} {% block header %}
<a href="{{ url_for('logout') }}">log out</a> <h1>{% block title %}Register{% endblock %}</h1>
{% endif %} {% endblock %}
</div>
{% for message in get_flashed_messages() %} {% block content %}
<div class=flash>{{ message }}</div> <form method="post">
{% endfor %} <label for="username">Username</label>
{% block body %}{% endblock %} <input name="username" id="username" required>
</div> <label for="password">Password</label>
<input type="password" name="password" id="password" required>
show_entries.html <input type="submit" value="Register">
-----------------
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> </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>
{% endblock %} {% endblock %}
login.html ``{% 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
This is the login template, which basically just displays a form to allow template.
the user to login:
A useful pattern used here is to place ``{% block title %}`` inside
.. sourcecode:: html+jinja ``{% 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
{% extends "layout.html" %} share the same title without writing it twice.
{% block body %}
<h2>Login</h2> The ``input`` tags are using the ``required`` attribute here. This tells
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} the browser not to submit the form until those fields are filled in. If
<form action="{{ url_for('login') }}" method=post> the user is using an older browser that doesn't support that attribute,
<dl> or if they are using something besides a browser to make requests, you
<dt>Username: still want to validate the data in the Flask view. It's important to
<dd><input type=text name=username> always fully validate the data on the server, even if the client does
<dt>Password: some validation as well.
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl> 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> </form>
{% endblock %} {% endblock %}
Continue with :ref:`tutorial-css`.
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`.

96
docs/tutorial/testing.rst

@ -1,96 +0,0 @@
.. _tutorial-testing:
Bonus: Testing the Application
==============================
Now that you have finished the application and everything works as
expected, it's probably not a bad idea to add automated tests to simplify
modifications in the future. The application above is used as a basic
example of how to perform unit testing in the :ref:`testing` section of the
documentation. Go there to see how easy it is to test Flask applications.
Adding tests to flaskr
----------------------
Assuming you have seen the :ref:`testing` section and have either written
your own tests for ``flaskr`` or have followed along with the examples
provided, you might be wondering about ways to organize the project.
One possible and recommended project structure is::
flaskr/
flaskr/
__init__.py
static/
templates/
tests/
test_flaskr.py
setup.py
MANIFEST.in
For now go ahead a create the :file:`tests/` directory as well as the
:file:`test_flaskr.py` file.
Running the tests
-----------------
At this point you can run the tests. Here ``pytest`` will be used.
.. note:: Make sure that ``pytest`` is installed in the same virtualenv
as flaskr. Otherwise ``pytest`` test will not be able to import the
required components to test the application::
pip install -e .
pip install pytest
Run and watch the tests pass, within the top-level :file:`flaskr/`
directory as::
pytest
Testing + setuptools
--------------------
One way to handle testing is to integrate it with ``setuptools``. Here
that requires adding a couple of lines to the :file:`setup.py` file and
creating a new file :file:`setup.cfg`. One benefit of running the tests
this way is that you do not have to install ``pytest``. Go ahead and
update the :file:`setup.py` file to contain::
from setuptools import setup
setup(
name='flaskr',
packages=['flaskr'],
include_package_data=True,
install_requires=[
'flask',
],
setup_requires=[
'pytest-runner',
],
tests_require=[
'pytest',
],
)
Now create :file:`setup.cfg` in the project root (alongside
:file:`setup.py`)::
[aliases]
test=pytest
Now you can run::
python setup.py test
This calls on the alias created in :file:`setup.cfg` which in turn runs
``pytest`` via ``pytest-runner``, as the :file:`setup.py` script has
been called. (Recall the `setup_requires` argument in :file:`setup.py`)
Following the standard rules of test-discovery your tests will be
found, run, and hopefully pass.
This is one possible way to run and manage testing. Here ``pytest`` is
used, but there are other options such as ``nose``. Integrating testing
with ``setuptools`` is convenient because it is not necessary to actually
download ``pytest`` or any other testing framework one might use.

561
docs/tutorial/tests.rst

@ -0,0 +1,561 @@
.. currentmodule:: flask
Test Coverage
=============
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.
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 ``if``
blocks, only runs when the condition is met. You want to make sure that
each function is tested with data that covers each branch.
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.
.. note::
This is being introduced late in the tutorial, but in your future
projects you should test as you develop.
You'll use `pytest`_ and `coverage`_ to test and measure your code.
Install them both:
.. code-block:: none
pip install pytest coverage
.. _pytest: https://pytest.readthedocs.io/
.. _coverage: https://coverage.readthedocs.io/
Setup and Fixtures
------------------
The test code is located in the ``tests`` directory. This directory is
*next to* the ``flaskr`` package, not inside it. The
``tests/conftest.py`` file contains setup functions called *fixtures*
that each test will use. Tests are in Python modules that start with
``test_``, and each test function in those modules also starts with
``test_``.
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.
.. code-block:: sql
:caption: ``tests/data.sql``
INSERT INTO user (username, password)
VALUES
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'),
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79');
INSERT INTO post (title, body, author_id, created)
VALUES
('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00');
The ``app`` fixture will call the factory and pass ``test_config`` to
configure the application and database for testing instead of using your
local development configuration.
.. code-block:: python
:caption: ``tests/conftest.py``
import os
import tempfile
import pytest
from flaskr import create_app
from flaskr.db import get_db, init_db
with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f:
_data_sql = f.read().decode('utf8')
@pytest.fixture
def app():
db_fd, db_path = tempfile.mkstemp()
app = create_app({
'TESTING': True,
'DATABASE': db_path,
})
with app.app_context():
init_db()
get_db().executescript(_data_sql)
yield app
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def runner(app):
return app.test_cli_runner()
:func:`tempfile.mkstemp` creates and opens a temporary file, returning
the file object and the path to it. The ``DATABASE`` 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.
:data:`TESTING` 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.
The ``client`` fixture calls
:meth:`app.test_client() <Flask.test_client>` with the application
object created by the ``app`` fixture. Tests will use the client to make
requests to the application without running the server.
The ``runner`` fixture is similar to ``client``.
:meth:`app.test_cli_runner() <Flask.test_cli_runner>` creates a runner
that can call the Click commands registered with the application.
Pytest uses fixtures by matching their function names with the names
of arguments in the test functions. For example, the ``test_hello``
function you'll write next takes a ``client`` argument. Pytest matches
that with the ``client`` fixture function, calls it, and passes the
returned value to the test function.
Factory
-------
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.
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.
.. code-block:: python
:caption: ``tests/test_factory.py``
from flaskr import create_app
def test_config():
assert not create_app().testing
assert create_app({'TESTING': True}).testing
def test_hello(client):
response = client.get('/hello')
assert response.data == b'Hello, World!'
You added the ``hello`` 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.
Database
--------
Within an application context, ``get_db`` should return the same
connection each time it's called. After the context, the connection
should be closed.
.. code-block:: python
:caption: ``tests/test_db.py``
import sqlite3
import pytest
from flaskr.db import get_db
def test_get_close_db(app):
with app.app_context():
db = get_db()
assert db is get_db()
with pytest.raises(sqlite3.ProgrammingError) as e:
db.execute('SELECT 1')
assert 'closed' in str(e)
The ``init-db`` command should call the ``init_db`` function and output
a message.
.. code-block:: python
:caption: ``tests/test_db.py``
def test_init_db_command(runner, monkeypatch):
class Recorder(object):
called = False
def fake_init_db():
Recorder.called = True
monkeypatch.setattr('flaskr.db.init_db', fake_init_db)
result = runner.invoke(args=['init-db'])
assert 'Initialized' in result.output
assert Recorder.called
This test uses Pytest's ``monkeypatch`` fixture to replace the
``init_db`` function with one that records that it's been called. The
``runner`` fixture you wrote above is used to call the ``init-db``
command by name.
Authentication
--------------
For most of the views, a user needs to be logged in. The easiest way to
do this in tests is to make a ``POST`` request to the ``login`` 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.
.. code-block:: python
:caption: ``tests/conftest.py``
class AuthActions(object):
def __init__(self, client):
self._client = client
def login(self, username='test', password='test'):
return self._client.post(
'/auth/login',
data={'username': username, 'password': password}
)
def logout(self):
return self._client.get('/auth/logout')
@pytest.fixture
def auth(client):
return AuthActions(client)
With the ``auth`` fixture, you can call ``auth.login()`` in a test to
log in as the ``test`` user, which was inserted as part of the test
data in the ``app`` fixture.
The ``register`` view should render successfully on ``GET``. On ``POST``
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.
.. code-block:: python
:caption: ``tests/test_auth.py``
import pytest
from flask import g, session
from flaskr.db import get_db
def test_register(client, app):
assert client.get('/auth/register').status_code == 200
response = client.post(
'/auth/register', data={'username': 'a', 'password': 'a'}
)
assert 'http://localhost/auth/login' == response.headers['Location']
with app.app_context():
assert get_db().execute(
"select * from user where username = 'a'",
).fetchone() is not None
@pytest.mark.parametrize(('username', 'password', 'message'), (
('', '', b'Username is required.'),
('a', '', b'Password is required.'),
('test', 'test', b'already registered'),
))
def test_register_validate_input(client, username, password, message):
response = client.post(
'/auth/register',
data={'username': username, 'password': password}
)
assert message in response.data
:meth:`client.get() <werkzeug.test.Client.get>` makes a ``GET`` request
and returns the :class:`Response` object returned by Flask. Similarly,
:meth:`client.post() <werkzeug.test.Client.post>` makes a ``POST``
request, converting the ``data`` dict into form data.
To test that the page renders successfully, a simple request is made and
checked for a ``200 OK`` :attr:`~Response.status_code`. If
rendering failed, Flask would return a ``500 Internal Server Error``
code.
:attr:`~Response.headers` will have a ``Location`` header with the login
URL when the register view redirects to the login view.
:attr:`~Response.data` contains the body of the response as bytes. If
you expect a certain value to render on the page, check that it's in
``data``. Bytes must be compared to bytes. If you want to compare
Unicode text, use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>`
instead.
``pytest.mark.parametrize`` 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.
The tests for the ``login`` view are very similar to those for
``register``. Rather than testing the data in the database,
:data:`session` should have ``user_id`` set after logging in.
.. code-block:: python
:caption: ``tests/test_auth.py``
def test_login(client, auth):
assert client.get('/auth/login').status_code == 200
response = auth.login()
assert response.headers['Location'] == 'http://localhost/'
with client:
client.get('/')
assert session['user_id'] == 1
assert g.user['username'] == 'test'
@pytest.mark.parametrize(('username', 'password', 'message'), (
('a', 'test', b'Incorrect username.'),
('test', 'a', b'Incorrect password.'),
))
def test_login_validate_input(auth, username, password, message):
response = auth.login(username, password)
assert message in response.data
Using ``client`` in a ``with`` block allows accessing context variables
such as :data:`session` after the response is returned. Normally,
accessing ``session`` outside of a request would raise an error.
Testing ``logout`` is the opposite of ``login``. :data:`session` should
not contain ``user_id`` after logging out.
.. code-block:: python
:caption: ``tests/test_auth.py``
def test_logout(client, auth):
auth.login()
with client:
auth.logout()
assert 'user_id' not in session
Blog
----
All the blog views use the ``auth`` fixture you wrote earlier. Call
``auth.login()`` and subsequent requests from the client will be logged
in as the ``test`` user.
The ``index`` 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.
You can also test some more authentication behavior while testing the
``index`` view. When not logged in, each page shows links to log in or
register. When logged in, there's a link to log out.
.. code-block:: python
:caption: ``tests/test_blog.py``
import pytest
from flaskr.db import get_db
def test_index(client, auth):
response = client.get('/')
assert b"Log In" in response.data
assert b"Register" in response.data
auth.login()
response = client.get('/')
assert b'Log Out' in response.data
assert b'test title' in response.data
assert b'by test on 2018-01-01' in response.data
assert b'test\nbody' in response.data
assert b'href="/1/update"' in response.data
A user must be logged in to access the ``create``, ``update``, and
``delete`` views. The logged in user must be the author of the post to
access ``update`` and ``delete``, otherwise a ``403 Forbidden`` status
is returned. If a ``post`` with the given ``id`` doesn't exist,
``update`` and ``delete`` should return ``404 Not Found``.
.. code-block:: python
:caption: ``tests/test_blog.py``
@pytest.mark.parametrize('path', (
'/create',
'/1/update',
'/1/delete',
))
def test_login_required(client, path):
response = client.post(path)
assert response.headers['Location'] == 'http://localhost/auth/login'
def test_author_required(app, client, auth):
# change the post author to another user
with app.app_context():
db = get_db()
db.execute('UPDATE post SET author_id = 2 WHERE id = 1')
db.commit()
auth.login()
# current user can't modify other user's post
assert client.post('/1/update').status_code == 403
assert client.post('/1/delete').status_code == 403
# current user doesn't see edit link
assert b'href="/1/update"' not in client.get('/').data
@pytest.mark.parametrize('path', (
'/2/update',
'/2/delete',
))
def test_exists_required(client, auth, path):
auth.login()
assert client.post(path).status_code == 404
The ``create`` and ``update`` views should render and return a
``200 OK`` status for a ``GET`` request. When valid data is sent in a
``POST`` request, ``create`` should insert the new post data into the
database, and ``update`` should modify the existing data. Both pages
should show an error message on invalid data.
.. code-block:: python
:caption: ``tests/test_blog.py``
def test_create(client, auth, app):
auth.login()
assert client.get('/create').status_code == 200
client.post('/create', data={'title': 'created', 'body': ''})
with app.app_context():
db = get_db()
count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0]
assert count == 2
def test_update(client, auth, app):
auth.login()
assert client.get('/1/update').status_code == 200
client.post('/1/update', data={'title': 'updated', 'body': ''})
with app.app_context():
db = get_db()
post = db.execute('SELECT * FROM post WHERE id = 1').fetchone()
assert post['title'] == 'updated'
@pytest.mark.parametrize('path', (
'/create',
'/1/update',
))
def test_create_update_validate(client, auth, path):
auth.login()
response = client.post(path, data={'title': '', 'body': ''})
assert b'Title is required.' in response.data
The ``delete`` view should redirect to the index URL and the post should
no longer exist in the database.
.. code-block:: python
:caption: ``tests/test_blog.py``
def test_delete(client, auth, app):
auth.login()
response = client.post('/1/delete')
assert response.headers['Location'] == 'http://localhost/'
with app.app_context():
db = get_db()
post = db.execute('SELECT * FROM post WHERE id = 1').fetchone()
assert post is None
Running the Tests
-----------------
Some extra configuration, which is not required but makes running
tests with coverage less verbose, can be added to the project's
``setup.cfg`` file.
.. code-block:: none
:caption: ``setup.cfg``
[tool:pytest]
testpaths = tests
[coverage:run]
branch = True
source =
flaskr
To run the tests, use the ``pytest`` command. It will find and run all
the test functions you've written.
.. code-block:: none
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, inifile: setup.cfg
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 =======================
If any tests fail, pytest will show the error that was raised. You can
run ``pytest -v`` to get a list of each test function rather than dots.
To measure the code coverage of your tests, use the ``coverage`` command
to run pytest instead of running it directly.
.. code-block:: none
coverage run -m pytest
You can either view a simple coverage report in the terminal:
.. code-block:: none
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%
An HTML report allows you to see which lines were covered in each file:
.. code-block:: none
coverage html
This generates files in the ``htmlcov`` directory. Open
``htmlcov/index.html`` in your browser to see the report.
Continue to :doc:`deploy`.

363
docs/tutorial/views.rst

@ -1,118 +1,301 @@
.. _tutorial-views: .. currentmodule:: flask
Step 6: The View Functions Blueprints and Views
========================== ====================
Now that the database connections are working, you can start writing the A view function is the code you write to respond to requests to your
view functions. You will need four of them; Show Entries, Add New Entry, application. Flask uses patterns to match the incoming request URL to
Login and Logout. Add the following code snipets to :file:`flaskr.py`. 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.
Show Entries
------------
This view shows all the entries stored in the database. It listens on the Create a Blueprint
root of the application and will select title and text from the database. ------------------
The one with the highest id (the newest entry) will be on top. The rows
returned from the cursor look a bit like dictionaries because we are using
the :class:`sqlite3.Row` row factory.
The view function will pass the entries to the :file:`show_entries.html` A :class:`Blueprint` is a way to organize a group of related views and
template and return the rendered one:: 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.
@app.route('/') Flaskr will have two blueprints, one for authentication functions and
def show_entries(): one for the blog posts functions. The code for each blueprint will go
db = get_db() in a separate module. Since the blog needs to know about authentication,
cur = db.execute('select title, text from entries order by id desc') you'll write the authentication one first.
entries = cur.fetchall()
return render_template('show_entries.html', entries=entries) .. code-block:: python
:caption: ``flaskr/auth.py``
Add New Entry
------------- import functools
This view lets the user add new entries if they are logged in. This only from flask import (
responds to ``POST`` requests; the actual form is shown on the Blueprint, flash, g, redirect, render_template, request, session, url_for
`show_entries` page. If everything worked out well, it will )
:func:`~flask.flash` an information message to the next request and from werkzeug.security import check_password_hash, generate_password_hash
redirect back to the `show_entries` page::
from flaskr.db import get_db
@app.route('/add', methods=['POST'])
def add_entry(): bp = Blueprint('auth', __name__, url_prefix='/auth')
if not session.get('logged_in'):
abort(401) This creates a :class:`Blueprint` named ``'auth'``. Like the application
object, the blueprint needs to know where it's defined, so ``__name__``
is passed as the second argument. The ``url_prefix`` will be prepended
to all the URLs associated with the blueprint.
Import and register the blueprint from the factory using
:meth:`app.register_blueprint() <Flask.register_blueprint>`. Place the
new code at the end of the factory function before returning the app.
.. code-block:: python
:caption: ``flaskr/__init__.py``
def create_app():
app = ...
# existing code omitted
from . import auth
app.register_blueprint(auth.bp)
return app
The authentication blueprint will have views to register new users and
to log in and log out.
The First View: Register
------------------------
When the user visits the ``/auth/register`` URL, the ``register`` view
will return `HTML`_ 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.
.. _HTML: https://developer.mozilla.org/docs/Web/HTML
For now you will just write the view code. On the next page, you'll
write templates to generate the HTML form.
.. code-block:: python
:caption: ``flaskr/auth.py``
@bp.route('/register', methods=('GET', 'POST'))
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db() db = get_db()
db.execute('insert into entries (title, text) values (?, ?)', error = None
[request.form['title'], request.form['text']])
if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = 'User {} is already registered.'.format(username)
if error is None:
db.execute(
'INSERT INTO user (username, password) VALUES (?, ?)',
(username, generate_password_hash(password))
)
db.commit() db.commit()
flash('New entry was successfully posted') return redirect(url_for('auth.login'))
return redirect(url_for('show_entries'))
flash(error)
return render_template('auth/register.html')
Here's what the ``register`` view function is doing:
#. :meth:`@bp.route <Blueprint.route>` associates the URL ``/register``
with the ``register`` view function. When Flask receives a request
to ``/auth/register``, it will call the ``register`` view and use
the return value as the response.
#. If the user submitted the form,
:attr:`request.method <Request.method>` will be ``'POST'``. In this
case, start validating the input.
#. :attr:`request.form <Request.form>` is a special type of
:class:`dict` mapping submitted form keys and values. The user will
input their ``username`` and ``password``.
Note that this view checks that the user is logged in (that is, if the #. Validate that ``username`` and ``password`` are not empty.
`logged_in` key is present in the session and ``True``).
.. admonition:: Security Note #. Validate that ``username`` is not already registered by querying the
database and checking if a result is returned.
:meth:`db.execute <sqlite3.Connection.execute>` takes a SQL query
with ``?`` 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
*SQL injection attack*.
Be sure to use question marks when building SQL statements, as done in the :meth:`~sqlite3.Cursor.fetchone` returns one row from the query.
example above. Otherwise, your app will be vulnerable to SQL injection when If the query returned no results, it returns ``None``. Later,
you use string formatting to build SQL statements. :meth:`~sqlite3.Cursor.fetchall` is used, which returns a list of
See :ref:`sqlite3` for more. all results.
Login and Logout #. If validation succeeds, insert the new user data into the database.
---------------- For security, passwords should never be stored in the database
directly. Instead,
:func:`~werkzeug.security.generate_password_hash` is used to
securely hash the password, and that hash is stored. Since this
query modifies data, :meth:`db.commit() <sqlite3.Connection.commit>`
needs to be called afterwards to save the changes.
These functions are used to sign the user in and out. Login checks the #. After storing the user, they are redirected to the login page.
username and password against the ones from the configuration and sets the :func:`url_for` generates the URL for the login view based on its
`logged_in` key for the session. If the user logged in successfully, that name. This is preferable to writing the URL directly as it allows
key is set to ``True``, and the user is redirected back to the `show_entries` you to change the URL later without changing all code that links to
page. In addition, a message is flashed that informs the user that he or it. :func:`redirect` generates a redirect response to the generated
she was logged in successfully. If an error occurred, the template is URL.
notified about that, and the user is asked again::
@app.route('/login', methods=['GET', 'POST']) #. If validation fails, the error is shown to the user. :func:`flash`
stores messages that can be retrieved when rendering the template.
#. When the user initially navigates to ``auth/register``, or
there was an validation error, an HTML page with the registration
form should be shown. :func:`render_template` will render a template
containing the HTML, which you'll write in the next step of the
tutorial.
Login
-----
This view follows the same pattern as the ``register`` view above.
.. code-block:: python
:caption: ``flaskr/auth.py``
@bp.route('/login', methods=('GET', 'POST'))
def login(): def login():
error = None
if request.method == 'POST': if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']: username = request.form['username']
error = 'Invalid username' password = request.form['password']
elif request.form['password'] != app.config['PASSWORD']: db = get_db()
error = 'Invalid password' error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'
if error is None:
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html')
There are a few differences from the ``register`` view:
#. The user is queried first and stored in a variable for later use.
#. :func:`~werkzeug.security.check_password_hash` hashes the submitted
password in the same way as the stored hash and securely compares
them. If they match, the password is valid.
#. :data:`session` is a :class:`dict` that stores data across requests.
When validation succeeds, the user's ``id`` is stored in a new
session. The data is stored in a *cookie* that is sent to the
browser, and the browser then sends it back with subsequent requests.
Flask securely *signs* the data so that it can't be tampered with.
Now that the user's ``id`` is stored in the :data:`session`, 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.
.. code-block:: python
:caption: ``flaskr/auth.py``
@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else: else:
session['logged_in'] = True g.user = get_db().execute(
flash('You were logged in') 'SELECT * FROM user WHERE id = ?', (user_id,)
return redirect(url_for('show_entries')) ).fetchone()
return render_template('login.html', error=error)
:meth:`bp.before_app_request() <Blueprint.before_app_request>` registers
a function that runs before the view function, no matter what URL is
requested. ``load_logged_in_user`` checks if a user id is stored in the
:data:`session` and gets that user's data from the database, storing it
on :data:`g.user <g>`, which lasts for the length of the request. If
there is no user id, or if the id doesn't exist, ``g.user`` will be
``None``.
The `logout` function, on the other hand, removes that key from the session Logout
again. There is a neat trick here: if you use the :meth:`~dict.pop` method ------
of the dict and pass a second parameter to it (the default), the method
will delete the key from the dictionary if present or do nothing when that
key is not in there. This is helpful because now it is not necessary to
check if the user was logged in.
:: To log out, you need to remove the user id from the :data:`session`.
Then ``load_logged_in_user`` won't load a user on subsequent requests.
@app.route('/logout') .. code-block:: python
:caption: ``flaskr/auth.py``
@bp.route('/logout')
def logout(): def logout():
session.pop('logged_in', None) session.clear()
flash('You were logged out') return redirect(url_for('index'))
return redirect(url_for('show_entries'))
Require Authentication in Other Views
-------------------------------------
Creating, editing, and deleting blog posts will require a user to be
logged in. A *decorator* can be used to check this for each view it's
applied to.
.. code-block:: python
:caption: ``flaskr/auth.py``
def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))
return view(**kwargs)
.. admonition:: Security Note return wrapped_view
Passwords should never be stored in plain text in a production This decorator returns a new view function that wraps the original view
system. This tutorial uses plain text passwords for simplicity. If you it's applied to. The new function checks if a user is loaded and
plan to release a project based off this tutorial out into the world, redirects to the login page otherwise. If a user is loaded the original
passwords should be both `hashed and salted`_ before being stored in a view is called and continues normally. You'll use this decorator when
database or file. writing the blog views.
Fortunately, there are Flask extensions for the purpose of Endpoints and URLs
hashing passwords and verifying passwords against hashes, so adding ------------------
this functionality is fairly straight forward. There are also
many general python libraries that can be used for hashing.
You can find a list of recommended Flask extensions The :func:`url_for` function generates the URL to a view based on a name
`here <http://flask.pocoo.org/extensions/>`_ and arguments. The name associated with a view is also called the
*endpoint*, and by default it's the same as the name of the view
function.
For example, the ``hello()`` view that was added to the app
factory earlier in the tutorial has the name ``'hello'`` and can be
linked to with ``url_for('hello')``. If it took an argument, which
you'll see later, it would be linked to using
``url_for('hello', who='World')``.
Continue with :ref:`tutorial-templates`. When using a blueprint, the name of the blueprint is prepended to the
name of the function, so the endpoint for the ``login`` function you
wrote above is ``'auth.login'`` because you added it to the ``'auth'``
blueprint.
.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/ Continue to :doc:`templates`.

19
examples/blueprintexample/blueprintexample.py

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""
Blueprint Example
~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from flask import Flask
from simple_page.simple_page import simple_page
app = Flask(__name__)
app.register_blueprint(simple_page)
# Blueprint can be registered many times
app.register_blueprint(simple_page, url_prefix='/pages')
if __name__=='__main__':
app.run()

0
examples/blueprintexample/simple_page/__init__.py

13
examples/blueprintexample/simple_page/simple_page.py

@ -1,13 +0,0 @@
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
simple_page = Blueprint('simple_page', __name__,
template_folder='templates')
@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
try:
return render_template('pages/%s.html' % page)
except TemplateNotFound:
abort(404)

5
examples/blueprintexample/simple_page/templates/pages/hello.html

@ -1,5 +0,0 @@
{% extends "pages/layout.html" %}
{% block body %}
Hello
{% endblock %}

5
examples/blueprintexample/simple_page/templates/pages/index.html

@ -1,5 +0,0 @@
{% extends "pages/layout.html" %}
{% block body %}
Blueprint example page
{% endblock %}

20
examples/blueprintexample/simple_page/templates/pages/layout.html

@ -1,20 +0,0 @@
<!doctype html>
<title>Simple Page Blueprint</title>
<div class="page">
<h1>This is blueprint example</h1>
<p>
A simple page blueprint is registered under / and /pages
you can access it using this URLs:
<ul>
<li><a href="{{ url_for('simple_page.show', page='hello') }}">/hello</a>
<li><a href="{{ url_for('simple_page.show', page='world') }}">/world</a>
</ul>
<p>
Also you can register the same blueprint under another path
<ul>
<li><a href="/pages/hello">/pages/hello</a>
<li><a href="/pages/world">/pages/world</a>
</ul>
{% block body %}{% endblock %}
</div>

4
examples/blueprintexample/simple_page/templates/pages/world.html

@ -1,4 +0,0 @@
{% extends "pages/layout.html" %}
{% block body %}
World
{% endblock %}

35
examples/blueprintexample/test_blueprintexample.py

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
"""
Blueprint Example Tests
~~~~~~~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import pytest
import blueprintexample
@pytest.fixture
def client():
return blueprintexample.app.test_client()
def test_urls(client):
r = client.get('/')
assert r.status_code == 200
r = client.get('/hello')
assert r.status_code == 200
r = client.get('/world')
assert r.status_code == 200
# second blueprint instance
r = client.get('/pages/hello')
assert r.status_code == 200
r = client.get('/pages/world')
assert r.status_code == 200

2
examples/flaskr/.gitignore vendored

@ -1,2 +0,0 @@
flaskr.db
.eggs/

40
examples/flaskr/README

@ -1,40 +0,0 @@
/ Flaskr /
a minimal blog application
~ What is Flaskr?
A sqlite powered thumble blog application
~ How do I use it?
1. edit the configuration in the factory.py file or
export a FLASKR_SETTINGS environment variable
pointing to a configuration file or pass in a
dictionary with config values using the create_app
function.
2. install the app from the root of the project directory
pip install --editable .
3. instruct flask to use the right application
export FLASK_APP="flaskr.factory:create_app()"
4. initialize the database with this command:
flask initdb
5. now you can run flaskr:
flask run
the application will greet you on
http://localhost:5000/
~ Is it tested?
You betcha. Run `python setup.py test` to see
the tests pass.

0
examples/flaskr/flaskr/__init__.py

0
examples/flaskr/flaskr/blueprints/__init__.py

85
examples/flaskr/flaskr/blueprints/flaskr.py

@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
"""
Flaskr
~~~~~~
A microblog example application written as Flask tutorial with
Flask and sqlite3.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from sqlite3 import dbapi2 as sqlite3
from flask import Blueprint, request, session, g, redirect, url_for, abort, \
render_template, flash, current_app
# create our blueprint :)
bp = Blueprint('flaskr', __name__)
def connect_db():
"""Connects to the specific database."""
rv = sqlite3.connect(current_app.config['DATABASE'])
rv.row_factory = sqlite3.Row
return rv
def init_db():
"""Initializes the database."""
db = get_db()
with current_app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db
@bp.route('/')
def show_entries():
db = get_db()
cur = db.execute('select title, text from entries order by id desc')
entries = cur.fetchall()
return render_template('show_entries.html', entries=entries)
@bp.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
db = get_db()
db.execute('insert into entries (title, text) values (?, ?)',
[request.form['title'], request.form['text']])
db.commit()
flash('New entry was successfully posted')
return redirect(url_for('flaskr.show_entries'))
@bp.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != current_app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != current_app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('flaskr.show_entries'))
return render_template('login.html', error=error)
@bp.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('flaskr.show_entries'))

64
examples/flaskr/flaskr/factory.py

@ -1,64 +0,0 @@
# -*- coding: utf-8 -*-
"""
Flaskr
~~~~~~
A microblog example application written as Flask tutorial with
Flask and sqlite3.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import os
from flask import Flask, g
from werkzeug.utils import find_modules, import_string
from flaskr.blueprints.flaskr import init_db
def create_app(config=None):
app = Flask('flaskr')
app.config.update(dict(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
DEBUG=True,
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/',
USERNAME='admin',
PASSWORD='default'
))
app.config.update(config or {})
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
register_blueprints(app)
register_cli(app)
register_teardowns(app)
return app
def register_blueprints(app):
"""Register all blueprint modules
Reference: Armin Ronacher, "Flask for Fun and for Profit" PyBay 2016.
"""
for name in find_modules('flaskr.blueprints'):
mod = import_string(name)
if hasattr(mod, 'bp'):
app.register_blueprint(mod.bp)
return None
def register_cli(app):
@app.cli.command('initdb')
def initdb_command():
"""Creates the database tables."""
init_db()
print('Initialized the database.')
def register_teardowns(app):
@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()

6
examples/flaskr/flaskr/schema.sql

@ -1,6 +0,0 @@
drop table if exists entries;
create table entries (
id integer primary key autoincrement,
title text not null,
'text' text not null
);

18
examples/flaskr/flaskr/static/style.css

@ -1,18 +0,0 @@
body { font-family: sans-serif; background: #eee; }
a, h1, h2 { color: #377BA8; }
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl { font-weight: bold; }
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
margin-bottom: 1em; background: #fafafa; }
.flash { background: #CEE5F5; padding: 0.5em;
border: 1px solid #AACBE2; }
.error { background: #F0D6D6; padding: 0.5em; }

17
examples/flaskr/flaskr/templates/layout.html

@ -1,17 +0,0 @@
<!doctype html>
<title>Flaskr</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<div class="page">
<h1>Flaskr</h1>
<div class="metanav">
{% if not session.logged_in %}
<a href="{{ url_for('flaskr.login') }}">log in</a>
{% else %}
<a href="{{ url_for('flaskr.logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>

14
examples/flaskr/flaskr/templates/login.html

@ -1,14 +0,0 @@
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('flaskr.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>
</form>
{% endblock %}

21
examples/flaskr/flaskr/templates/show_entries.html

@ -1,21 +0,0 @@
{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}
<form action="{{ url_for('flaskr.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>
{% endblock %}

2
examples/flaskr/setup.cfg

@ -1,2 +0,0 @@
[aliases]
test=pytest

27
examples/flaskr/setup.py

@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
"""
Flaskr Tests
~~~~~~~~~~~~
Tests the Flaskr application.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from setuptools import setup, find_packages
setup(
name='flaskr',
packages=find_packages(),
include_package_data=True,
install_requires=[
'flask',
],
setup_requires=[
'pytest-runner',
],
tests_require=[
'pytest',
],
)

83
examples/flaskr/tests/test_flaskr.py

@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""
Flaskr Tests
~~~~~~~~~~~~
Tests the Flaskr application.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import os
import tempfile
import pytest
from flaskr.factory import create_app
from flaskr.blueprints.flaskr import init_db
@pytest.fixture
def app():
db_fd, db_path = tempfile.mkstemp()
config = {
'DATABASE': db_path,
'TESTING': True,
}
app = create_app(config=config)
with app.app_context():
init_db()
yield app
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(app):
return app.test_client()
def login(client, username, password):
return client.post('/login', data=dict(
username=username,
password=password
), follow_redirects=True)
def logout(client):
return client.get('/logout', follow_redirects=True)
def test_empty_db(client):
"""Start with a blank database."""
rv = client.get('/')
assert b'No entries here so far' in rv.data
def test_login_logout(client, app):
"""Make sure login and logout works"""
rv = login(client, app.config['USERNAME'],
app.config['PASSWORD'])
assert b'You were logged in' in rv.data
rv = logout(client)
assert b'You were logged out' in rv.data
rv = login(client,app.config['USERNAME'] + 'x',
app.config['PASSWORD'])
assert b'Invalid username' in rv.data
rv = login(client, app.config['USERNAME'],
app.config['PASSWORD'] + 'x')
assert b'Invalid password' in rv.data
def test_messages(client, app):
"""Test that messages work"""
login(client, app.config['USERNAME'],
app.config['PASSWORD'])
rv = client.post('/add', data=dict(
title='<Hello>',
text='<strong>HTML</strong> allowed here'
), follow_redirects=True)
assert b'No entries here so far' not in rv.data
assert b'&lt;Hello&gt;' in rv.data
assert b'<strong>HTML</strong> allowed here' in rv.data

29
examples/jqueryexample/jqueryexample.py

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
"""
jQuery Example
~~~~~~~~~~~~~~
A simple application that shows how Flask and jQuery get along.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from flask import Flask, jsonify, render_template, request
app = Flask(__name__)
@app.route('/_add_numbers')
def add_numbers():
"""Add two numbers server side, ridiculous but well..."""
a = request.args.get('a', 0, type=int)
b = request.args.get('b', 0, type=int)
return jsonify(result=a + b)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()

33
examples/jqueryexample/templates/index.html

@ -1,33 +0,0 @@
{% extends "layout.html" %}
{% block body %}
<script type="text/javascript">
$(function() {
var submit_form = function(e) {
$.getJSON($SCRIPT_ROOT + '/_add_numbers', {
a: $('input[name="a"]').val(),
b: $('input[name="b"]').val()
}, function(data) {
$('#result').text(data.result);
$('input[name=a]').focus().select();
});
return false;
};
$('a#calculate').bind('click', submit_form);
$('input[type=text]').bind('keydown', function(e) {
if (e.keyCode == 13) {
submit_form(e);
}
});
$('input[name=a]').focus();
});
</script>
<h1>jQuery Example</h1>
<p>
<input type="text" size="5" name="a"> +
<input type="text" size="5" name="b"> =
<span id="result">?</span>
<p><a href=# id="calculate">calculate server side</a>
{% endblock %}

8
examples/jqueryexample/templates/layout.html

@ -1,8 +0,0 @@
<!doctype html>
<title>jQuery Example</title>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
var $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>
{% block body %}{% endblock %}

2
examples/minitwit/.gitignore vendored

@ -1,2 +0,0 @@
minitwit.db
.eggs/

3
examples/minitwit/MANIFEST.in

@ -1,3 +0,0 @@
graft minitwit/templates
graft minitwit/static
include minitwit/schema.sql

39
examples/minitwit/README

@ -1,39 +0,0 @@
/ MiniTwit /
because writing todo lists is not fun
~ What is MiniTwit?
A SQLite and Flask powered twitter clone
~ How do I use it?
1. edit the configuration in the minitwit.py file or
export an MINITWIT_SETTINGS environment variable
pointing to a configuration file.
2. install the app from the root of the project directory
pip install --editable .
3. tell flask about the right application:
export FLASK_APP=minitwit
4. fire up a shell and run this:
flask initdb
5. now you can run minitwit:
flask run
the application will greet you on
http://localhost:5000/
~ Is it tested?
You betcha. Run the `python setup.py test` file to
see the tests pass.

1
examples/minitwit/minitwit/__init__.py

@ -1 +0,0 @@
from .minitwit import app

256
examples/minitwit/minitwit/minitwit.py

@ -1,256 +0,0 @@
# -*- coding: utf-8 -*-
"""
MiniTwit
~~~~~~~~
A microblogging application written with Flask and sqlite3.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import time
from sqlite3 import dbapi2 as sqlite3
from hashlib import md5
from datetime import datetime
from flask import Flask, request, session, url_for, redirect, \
render_template, abort, g, flash, _app_ctx_stack
from werkzeug import check_password_hash, generate_password_hash
# configuration
DATABASE = '/tmp/minitwit.db'
PER_PAGE = 30
DEBUG = True
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
# create our little application :)
app = Flask('minitwit')
app.config.from_object(__name__)
app.config.from_envvar('MINITWIT_SETTINGS', silent=True)
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
top = _app_ctx_stack.top
if not hasattr(top, 'sqlite_db'):
top.sqlite_db = sqlite3.connect(app.config['DATABASE'])
top.sqlite_db.row_factory = sqlite3.Row
return top.sqlite_db
@app.teardown_appcontext
def close_database(exception):
"""Closes the database again at the end of the request."""
top = _app_ctx_stack.top
if hasattr(top, 'sqlite_db'):
top.sqlite_db.close()
def init_db():
"""Initializes the database."""
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
@app.cli.command('initdb')
def initdb_command():
"""Creates the database tables."""
init_db()
print('Initialized the database.')
def query_db(query, args=(), one=False):
"""Queries the database and returns a list of dictionaries."""
cur = get_db().execute(query, args)
rv = cur.fetchall()
return (rv[0] if rv else None) if one else rv
def get_user_id(username):
"""Convenience method to look up the id for a username."""
rv = query_db('select user_id from user where username = ?',
[username], one=True)
return rv[0] if rv else None
def format_datetime(timestamp):
"""Format a timestamp for display."""
return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M')
def gravatar_url(email, size=80):
"""Return the gravatar image for the given email address."""
return 'https://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
@app.before_request
def before_request():
g.user = None
if 'user_id' in session:
g.user = query_db('select * from user where user_id = ?',
[session['user_id']], one=True)
@app.route('/')
def timeline():
"""Shows a users timeline or if no user is logged in it will
redirect to the public timeline. This timeline shows the user's
messages as well as all the messages of followed users.
"""
if not g.user:
return redirect(url_for('public_timeline'))
return render_template('timeline.html', messages=query_db('''
select message.*, user.* from message, user
where message.author_id = user.user_id and (
user.user_id = ? or
user.user_id in (select whom_id from follower
where who_id = ?))
order by message.pub_date desc limit ?''',
[session['user_id'], session['user_id'], PER_PAGE]))
@app.route('/public')
def public_timeline():
"""Displays the latest messages of all users."""
return render_template('timeline.html', messages=query_db('''
select message.*, user.* from message, user
where message.author_id = user.user_id
order by message.pub_date desc limit ?''', [PER_PAGE]))
@app.route('/<username>')
def user_timeline(username):
"""Display's a users tweets."""
profile_user = query_db('select * from user where username = ?',
[username], one=True)
if profile_user is None:
abort(404)
followed = False
if g.user:
followed = query_db('''select 1 from follower where
follower.who_id = ? and follower.whom_id = ?''',
[session['user_id'], profile_user['user_id']],
one=True) is not None
return render_template('timeline.html', messages=query_db('''
select message.*, user.* from message, user where
user.user_id = message.author_id and user.user_id = ?
order by message.pub_date desc limit ?''',
[profile_user['user_id'], PER_PAGE]), followed=followed,
profile_user=profile_user)
@app.route('/<username>/follow')
def follow_user(username):
"""Adds the current user as follower of the given user."""
if not g.user:
abort(401)
whom_id = get_user_id(username)
if whom_id is None:
abort(404)
db = get_db()
db.execute('insert into follower (who_id, whom_id) values (?, ?)',
[session['user_id'], whom_id])
db.commit()
flash('You are now following "%s"' % username)
return redirect(url_for('user_timeline', username=username))
@app.route('/<username>/unfollow')
def unfollow_user(username):
"""Removes the current user as follower of the given user."""
if not g.user:
abort(401)
whom_id = get_user_id(username)
if whom_id is None:
abort(404)
db = get_db()
db.execute('delete from follower where who_id=? and whom_id=?',
[session['user_id'], whom_id])
db.commit()
flash('You are no longer following "%s"' % username)
return redirect(url_for('user_timeline', username=username))
@app.route('/add_message', methods=['POST'])
def add_message():
"""Registers a new message for the user."""
if 'user_id' not in session:
abort(401)
if request.form['text']:
db = get_db()
db.execute('''insert into message (author_id, text, pub_date)
values (?, ?, ?)''', (session['user_id'], request.form['text'],
int(time.time())))
db.commit()
flash('Your message was recorded')
return redirect(url_for('timeline'))
@app.route('/login', methods=['GET', 'POST'])
def login():
"""Logs the user in."""
if g.user:
return redirect(url_for('timeline'))
error = None
if request.method == 'POST':
user = query_db('''select * from user where
username = ?''', [request.form['username']], one=True)
if user is None:
error = 'Invalid username'
elif not check_password_hash(user['pw_hash'],
request.form['password']):
error = 'Invalid password'
else:
flash('You were logged in')
session['user_id'] = user['user_id']
return redirect(url_for('timeline'))
return render_template('login.html', error=error)
@app.route('/register', methods=['GET', 'POST'])
def register():
"""Registers the user."""
if g.user:
return redirect(url_for('timeline'))
error = None
if request.method == 'POST':
if not request.form['username']:
error = 'You have to enter a username'
elif not request.form['email'] or \
'@' not in request.form['email']:
error = 'You have to enter a valid email address'
elif not request.form['password']:
error = 'You have to enter a password'
elif request.form['password'] != request.form['password2']:
error = 'The two passwords do not match'
elif get_user_id(request.form['username']) is not None:
error = 'The username is already taken'
else:
db = get_db()
db.execute('''insert into user (
username, email, pw_hash) values (?, ?, ?)''',
[request.form['username'], request.form['email'],
generate_password_hash(request.form['password'])])
db.commit()
flash('You were successfully registered and can login now')
return redirect(url_for('login'))
return render_template('register.html', error=error)
@app.route('/logout')
def logout():
"""Logs the user out."""
flash('You were logged out')
session.pop('user_id', None)
return redirect(url_for('public_timeline'))
# add some filters to jinja
app.jinja_env.filters['datetimeformat'] = format_datetime
app.jinja_env.filters['gravatar'] = gravatar_url

21
examples/minitwit/minitwit/schema.sql

@ -1,21 +0,0 @@
drop table if exists user;
create table user (
user_id integer primary key autoincrement,
username text not null,
email text not null,
pw_hash text not null
);
drop table if exists follower;
create table follower (
who_id integer,
whom_id integer
);
drop table if exists message;
create table message (
message_id integer primary key autoincrement,
author_id integer not null,
text text not null,
pub_date integer
);

178
examples/minitwit/minitwit/static/style.css

@ -1,178 +0,0 @@
body {
background: #CAECE9;
font-family: 'Trebuchet MS', sans-serif;
font-size: 14px;
}
a {
color: #26776F;
}
a:hover {
color: #333;
}
input[type="text"],
input[type="password"] {
background: white;
border: 1px solid #BFE6E2;
padding: 2px;
font-family: 'Trebuchet MS', sans-serif;
font-size: 14px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
color: #105751;
}
input[type="submit"] {
background: #105751;
border: 1px solid #073B36;
padding: 1px 3px;
font-family: 'Trebuchet MS', sans-serif;
font-size: 14px;
font-weight: bold;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
color: white;
}
div.page {
background: white;
border: 1px solid #6ECCC4;
width: 700px;
margin: 30px auto;
}
div.page h1 {
background: #6ECCC4;
margin: 0;
padding: 10px 14px;
color: white;
letter-spacing: 1px;
text-shadow: 0 0 3px #24776F;
font-weight: normal;
}
div.page div.navigation {
background: #DEE9E8;
padding: 4px 10px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #eee;
color: #888;
font-size: 12px;
letter-spacing: 0.5px;
}
div.page div.navigation a {
color: #444;
font-weight: bold;
}
div.page h2 {
margin: 0 0 15px 0;
color: #105751;
text-shadow: 0 1px 2px #ccc;
}
div.page div.body {
padding: 10px;
}
div.page div.footer {
background: #eee;
color: #888;
padding: 5px 10px;
font-size: 12px;
}
div.page div.followstatus {
border: 1px solid #ccc;
background: #E3EBEA;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
padding: 3px;
font-size: 13px;
}
div.page ul.messages {
list-style: none;
margin: 0;
padding: 0;
}
div.page ul.messages li {
margin: 10px 0;
padding: 5px;
background: #F0FAF9;
border: 1px solid #DBF3F1;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
min-height: 48px;
}
div.page ul.messages p {
margin: 0;
}
div.page ul.messages li img {
float: left;
padding: 0 10px 0 0;
}
div.page ul.messages li small {
font-size: 0.9em;
color: #888;
}
div.page div.twitbox {
margin: 10px 0;
padding: 5px;
background: #F0FAF9;
border: 1px solid #94E2DA;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
div.page div.twitbox h3 {
margin: 0;
font-size: 1em;
color: #2C7E76;
}
div.page div.twitbox p {
margin: 0;
}
div.page div.twitbox input[type="text"] {
width: 585px;
}
div.page div.twitbox input[type="submit"] {
width: 70px;
margin-left: 5px;
}
ul.flashes {
list-style: none;
margin: 10px 10px 0 10px;
padding: 0;
}
ul.flashes li {
background: #B9F3ED;
border: 1px solid #81CEC6;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
padding: 4px;
font-size: 13px;
}
div.error {
margin: 10px 0;
background: #FAE4E4;
border: 1px solid #DD6F6F;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
padding: 4px;
font-size: 13px;
}

32
examples/minitwit/minitwit/templates/layout.html

@ -1,32 +0,0 @@
<!doctype html>
<title>{% block title %}Welcome{% endblock %} | MiniTwit</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<div class="page">
<h1>MiniTwit</h1>
<div class="navigation">
{% if g.user %}
<a href="{{ url_for('timeline') }}">my timeline</a> |
<a href="{{ url_for('public_timeline') }}">public timeline</a> |
<a href="{{ url_for('logout') }}">sign out [{{ g.user.username }}]</a>
{% else %}
<a href="{{ url_for('public_timeline') }}">public timeline</a> |
<a href="{{ url_for('register') }}">sign up</a> |
<a href="{{ url_for('login') }}">sign in</a>
{% endif %}
</div>
{% with flashes = get_flashed_messages() %}
{% if flashes %}
<ul class="flashes">
{% for message in flashes %}
<li>{{ message }}
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="body">
{% block body %}{% endblock %}
</div>
<div class="footer">
MiniTwit &mdash; A Flask Application
</div>
</div>

16
examples/minitwit/minitwit/templates/login.html

@ -1,16 +0,0 @@
{% extends "layout.html" %}
{% block title %}Sign In{% endblock %}
{% block body %}
<h2>Sign In</h2>
{% if error %}<div class="error"><strong>Error:</strong> {{ error }}</div>{% endif %}
<form action="" method="post">
<dl>
<dt>Username:
<dd><input type="text" name="username" size="30" value="{{ request.form.username }}">
<dt>Password:
<dd><input type="password" name="password" size="30">
</dl>
<div class="actions"><input type="submit" value="Sign In"></div>
</form>
{% endblock %}

19
examples/minitwit/minitwit/templates/register.html

@ -1,19 +0,0 @@
{% extends "layout.html" %}
{% block title %}Sign Up{% endblock %}
{% block body %}
<h2>Sign Up</h2>
{% if error %}<div class="error"><strong>Error:</strong> {{ error }}</div>{% endif %}
<form action="" method="post">
<dl>
<dt>Username:
<dd><input type="text" name="username" size="30" value="{{ request.form.username }}">
<dt>E-Mail:
<dd><input type="text" name="email" size="30" value="{{ request.form.email }}">
<dt>Password:
<dd><input type="password" name="password" size="30">
<dt>Password <small>(repeat)</small>:
<dd><input type="password" name="password2" size="30">
</dl>
<div class="actions"><input type="submit" value="Sign Up"></div>
</form>
{% endblock %}

49
examples/minitwit/minitwit/templates/timeline.html

@ -1,49 +0,0 @@
{% extends "layout.html" %}
{% block title %}
{% if request.endpoint == 'public_timeline' %}
Public Timeline
{% elif request.endpoint == 'user_timeline' %}
{{ profile_user.username }}'s Timeline
{% else %}
My Timeline
{% endif %}
{% endblock %}
{% block body %}
<h2>{{ self.title() }}</h2>
{% if g.user %}
{% if request.endpoint == 'user_timeline' %}
<div class="followstatus">
{% if g.user.user_id == profile_user.user_id %}
This is you!
{% elif followed %}
You are currently following this user.
<a class="unfollow" href="{{ url_for('unfollow_user', username=profile_user.username)
}}">Unfollow user</a>.
{% else %}
You are not yet following this user.
<a class="follow" href="{{ url_for('follow_user', username=profile_user.username)
}}">Follow user</a>.
{% endif %}
</div>
{% elif request.endpoint == 'timeline' %}
<div class="twitbox">
<h3>What's on your mind {{ g.user.username }}?</h3>
<form action="{{ url_for('add_message') }}" method="post">
<p><input type="text" name="text" size="60"><!--
--><input type="submit" value="Share">
</form>
</div>
{% endif %}
{% endif %}
<ul class="messages">
{% for message in messages %}
<li><img src="{{ message.email|gravatar(size=48) }}"><p>
<strong><a href="{{ url_for('user_timeline', username=message.username)
}}">{{ message.username }}</a></strong>
{{ message.text }}
<small>&mdash; {{ message.pub_date|datetimeformat }}</small>
{% else %}
<li><em>There's no message so far.</em>
{% endfor %}
</ul>
{% endblock %}

2
examples/minitwit/setup.cfg

@ -1,2 +0,0 @@
[aliases]
test=pytest

16
examples/minitwit/setup.py

@ -1,16 +0,0 @@
from setuptools import setup
setup(
name='minitwit',
packages=['minitwit'],
include_package_data=True,
install_requires=[
'flask',
],
setup_requires=[
'pytest-runner',
],
tests_require=[
'pytest',
],
)

150
examples/minitwit/tests/test_minitwit.py

@ -1,150 +0,0 @@
# -*- coding: utf-8 -*-
"""
MiniTwit Tests
~~~~~~~~~~~~~~
Tests the MiniTwit application.
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import os
import tempfile
import pytest
from minitwit import minitwit
@pytest.fixture
def client():
db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp()
client = minitwit.app.test_client()
with minitwit.app.app_context():
minitwit.init_db()
yield client
os.close(db_fd)
os.unlink(minitwit.app.config['DATABASE'])
def register(client, username, password, password2=None, email=None):
"""Helper function to register a user"""
if password2 is None:
password2 = password
if email is None:
email = username + '@example.com'
return client.post('/register', data={
'username': username,
'password': password,
'password2': password2,
'email': email,
}, follow_redirects=True)
def login(client, username, password):
"""Helper function to login"""
return client.post('/login', data={
'username': username,
'password': password
}, follow_redirects=True)
def register_and_login(client, username, password):
"""Registers and logs in in one go"""
register(client, username, password)
return login(client, username, password)
def logout(client):
"""Helper function to logout"""
return client.get('/logout', follow_redirects=True)
def add_message(client, text):
"""Records a message"""
rv = client.post('/add_message', data={'text': text},
follow_redirects=True)
if text:
assert b'Your message was recorded' in rv.data
return rv
def test_register(client):
"""Make sure registering works"""
rv = register(client, 'user1', 'default')
assert b'You were successfully registered ' \
b'and can login now' in rv.data
rv = register(client, 'user1', 'default')
assert b'The username is already taken' in rv.data
rv = register(client, '', 'default')
assert b'You have to enter a username' in rv.data
rv = register(client, 'meh', '')
assert b'You have to enter a password' in rv.data
rv = register(client, 'meh', 'x', 'y')
assert b'The two passwords do not match' in rv.data
rv = register(client, 'meh', 'foo', email='broken')
assert b'You have to enter a valid email address' in rv.data
def test_login_logout(client):
"""Make sure logging in and logging out works"""
rv = register_and_login(client, 'user1', 'default')
assert b'You were logged in' in rv.data
rv = logout(client)
assert b'You were logged out' in rv.data
rv = login(client, 'user1', 'wrongpassword')
assert b'Invalid password' in rv.data
rv = login(client, 'user2', 'wrongpassword')
assert b'Invalid username' in rv.data
def test_message_recording(client):
"""Check if adding messages works"""
register_and_login(client, 'foo', 'default')
add_message(client, 'test message 1')
add_message(client, '<test message 2>')
rv = client.get('/')
assert b'test message 1' in rv.data
assert b'&lt;test message 2&gt;' in rv.data
def test_timelines(client):
"""Make sure that timelines work"""
register_and_login(client, 'foo', 'default')
add_message(client, 'the message by foo')
logout(client)
register_and_login(client, 'bar', 'default')
add_message(client, 'the message by bar')
rv = client.get('/public')
assert b'the message by foo' in rv.data
assert b'the message by bar' in rv.data
# bar's timeline should just show bar's message
rv = client.get('/')
assert b'the message by foo' not in rv.data
assert b'the message by bar' in rv.data
# now let's follow foo
rv = client.get('/foo/follow', follow_redirects=True)
assert b'You are now following &#34;foo&#34;' in rv.data
# we should now see foo's message
rv = client.get('/')
assert b'the message by foo' in rv.data
assert b'the message by bar' in rv.data
# but on the user's page we only want the user's message
rv = client.get('/bar')
assert b'the message by foo' not in rv.data
assert b'the message by bar' in rv.data
rv = client.get('/foo')
assert b'the message by foo' in rv.data
assert b'the message by bar' not in rv.data
# now unfollow and check if that worked
rv = client.get('/foo/unfollow', follow_redirects=True)
assert b'You are no longer following &#34;foo&#34;' in rv.data
rv = client.get('/')
assert b'the message by foo' not in rv.data
assert b'the message by bar' in rv.data

10
examples/patterns/largerapp/setup.py

@ -1,10 +0,0 @@
from setuptools import setup
setup(
name='yourapplication',
packages=['yourapplication'],
include_package_data=True,
install_requires=[
'flask',
],
)

21
examples/patterns/largerapp/tests/test_largerapp.py

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
"""
Larger App Tests
~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from yourapplication import app
import pytest
@pytest.fixture
def client():
app.config['TESTING'] = True
client = app.test_client()
return client
def test_index(client):
rv = client.get('/')
assert b"Hello World!" in rv.data

13
examples/patterns/largerapp/yourapplication/__init__.py

@ -1,13 +0,0 @@
# -*- coding: utf-8 -*-
"""
yourapplication
~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from flask import Flask
app = Flask('yourapplication')
import yourapplication.views

0
examples/patterns/largerapp/yourapplication/static/style.css

0
examples/patterns/largerapp/yourapplication/templates/index.html

0
examples/patterns/largerapp/yourapplication/templates/layout.html

0
examples/patterns/largerapp/yourapplication/templates/login.html

14
examples/patterns/largerapp/yourapplication/views.py

@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
"""
yourapplication.views
~~~~~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from yourapplication import app
@app.route('/')
def index():
return 'Hello World!'

14
examples/tutorial/.gitignore vendored

@ -0,0 +1,14 @@
venv/
*.pyc
__pycache__/
instance/
.cache/
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/
.idea/
*.swp
*~

31
examples/tutorial/LICENSE

@ -0,0 +1,31 @@
Copyright © 2010 by the Pallets team.
Some rights reserved.
Redistribution and use in source and binary forms of the software as
well as documentation, with or without modification, are permitted
provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

7
examples/flaskr/MANIFEST.in → examples/tutorial/MANIFEST.in

@ -1,3 +1,6 @@
graft flaskr/templates include LICENSE
graft flaskr/static
include flaskr/schema.sql include flaskr/schema.sql
graft flaskr/static
graft flaskr/templates
graft tests
global-exclude *.pyc

76
examples/tutorial/README.rst

@ -0,0 +1,76 @@
Flaskr
======
The basic blog app built in the Flask `tutorial`_.
.. _tutorial: http://flask.pocoo.org/docs/tutorial/
Install
-------
**Be sure to use the same version of the code as the version of the docs
you're reading.** You probably want the latest tagged version, but the
default Git version is the master branch. ::
# clone the repository
git clone https://github.com/pallets/flask
cd flask
# checkout the correct version
git tag # shows the tagged versions
git checkout latest-tag-found-above
cd examples/tutorial
Create a virtualenv and activate it::
python3 -m venv venv
. venv/bin/activate
Or on Windows cmd::
py -3 -m venv venv
venv\Scripts\activate.bat
Install Flaskr::
pip install -e .
Or if you are using the master branch, install Flask from source before
installing Flaskr::
pip install -e ../..
pip install -e .
Run
---
::
export FLASK_APP=flaskr
export FLASK_ENV=development
flask run
Or on Windows cmd::
set FLASK_APP=flaskr
set FLASK_ENV=development
flask run
Open http://127.0.0.1:5000 in a browser.
Test
----
::
pip install pytest
pytest
Run with coverage report::
pip install pytest coverage
coverage run -m pytest
coverage report
coverage html # open htmlcov/index.html in a browser

48
examples/tutorial/flaskr/__init__.py

@ -0,0 +1,48 @@
import os
from flask import Flask
def create_app(test_config=None):
"""Create and configure an instance of the Flask application."""
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
# a default secret that should be overridden by instance config
SECRET_KEY='dev',
# store the database in the instance folder
DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.update(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
@app.route('/hello')
def hello():
return 'Hello, World!'
# register the database commands
from flaskr import db
db.init_app(app)
# apply the blueprints to the app
from flaskr import auth, blog
app.register_blueprint(auth.bp)
app.register_blueprint(blog.bp)
# make url_for('index') == url_for('blog.index')
# in another app, you might define a separate main index here with
# app.route, while giving the blog blueprint a url_prefix, but for
# the tutorial the blog will be the main index
app.add_url_rule('/', endpoint='index')
return app

108
examples/tutorial/flaskr/auth.py

@ -0,0 +1,108 @@
import functools
from flask import (
Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash
from flaskr.db import get_db
bp = Blueprint('auth', __name__, url_prefix='/auth')
def login_required(view):
"""View decorator that redirects anonymous users to the login page."""
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))
return view(**kwargs)
return wrapped_view
@bp.before_app_request
def load_logged_in_user():
"""If a user id is stored in the session, load the user object from
the database into ``g.user``."""
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM user WHERE id = ?', (user_id,)
).fetchone()
@bp.route('/register', methods=('GET', 'POST'))
def register():
"""Register a new user.
Validates that the username is not already taken. Hashes the
password for security.
"""
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = 'User {} is already registered.'.format(username)
if error is None:
# the name is available, store it in the database and go to
# the login page
db.execute(
'INSERT INTO user (username, password) VALUES (?, ?)',
(username, generate_password_hash(password))
)
db.commit()
return redirect(url_for('auth.login'))
flash(error)
return render_template('auth/register.html')
@bp.route('/login', methods=('GET', 'POST'))
def login():
"""Log in a registered user by adding the user id to the session."""
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'
if error is None:
# store the user id in a new session and return to the index
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html')
@bp.route('/logout')
def logout():
"""Clear the current session, including the stored user id."""
session.clear()
return redirect(url_for('index'))

119
examples/tutorial/flaskr/blog.py

@ -0,0 +1,119 @@
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
from werkzeug.exceptions import abort
from flaskr.auth import login_required
from flaskr.db import get_db
bp = Blueprint('blog', __name__)
@bp.route('/')
def index():
"""Show all the posts, most recent first."""
db = get_db()
posts = db.execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' ORDER BY created DESC'
).fetchall()
return render_template('blog/index.html', posts=posts)
def get_post(id, check_author=True):
"""Get a post and its author by id.
Checks that the id exists and optionally that the current user is
the author.
:param id: id of post to get
:param check_author: require the current user to be the author
:return: the post with author information
:raise 404: if a post with the given id doesn't exist
:raise 403: if the current user isn't the author
"""
post = get_db().execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' WHERE p.id = ?',
(id,)
).fetchone()
if post is None:
abort(404, "Post id {0} doesn't exist.".format(id))
if check_author and post['author_id'] != g.user['id']:
abort(403)
return post
@bp.route('/create', methods=('GET', 'POST'))
@login_required
def create():
"""Create a new post for the current user."""
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'INSERT INTO post (title, body, author_id)'
' VALUES (?, ?, ?)',
(title, body, g.user['id'])
)
db.commit()
return redirect(url_for('blog.index'))
return render_template('blog/create.html')
@bp.route('/<int:id>/update', methods=('GET', 'POST'))
@login_required
def update(id):
"""Update a post if the current user is the author."""
post = get_post(id)
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'UPDATE post SET title = ?, body = ? WHERE id = ?',
(title, body, id)
)
db.commit()
return redirect(url_for('blog.index'))
return render_template('blog/update.html', post=post)
@bp.route('/<int:id>/delete', methods=('POST',))
@login_required
def delete(id):
"""Delete a post.
Ensures that the post exists and that the logged in user is the
author of the post.
"""
get_post(id)
db = get_db()
db.execute('DELETE FROM post WHERE id = ?', (id,))
db.commit()
return redirect(url_for('blog.index'))

54
examples/tutorial/flaskr/db.py

@ -0,0 +1,54 @@
import sqlite3
import click
from flask import current_app, g
from flask.cli import with_appcontext
def get_db():
"""Connect to the application's configured database. The connection
is unique for each request and will be reused if this is called
again.
"""
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
"""If this request connected to the database, close the
connection.
"""
db = g.pop('db', None)
if db is not None:
db.close()
def init_db():
"""Clear existing data and create new tables."""
db = get_db()
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))
@click.command('init-db')
@with_appcontext
def init_db_command():
"""Clear existing data and create new tables."""
init_db()
click.echo('Initialized the database.')
def init_app(app):
"""Register database functions with the Flask app. This is called by
the application factory.
"""
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)

20
examples/tutorial/flaskr/schema.sql

@ -0,0 +1,20 @@
-- Initialize the database.
-- Drop any existing data and create empty tables.
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS post;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES user (id)
);

134
examples/tutorial/flaskr/static/style.css

@ -0,0 +1,134 @@
html {
font-family: sans-serif;
background: #eee;
padding: 1rem;
}
body {
max-width: 960px;
margin: 0 auto;
background: white;
}
h1, h2, h3, h4, h5, h6 {
font-family: serif;
color: #377ba8;
margin: 1rem 0;
}
a {
color: #377ba8;
}
hr {
border: none;
border-top: 1px solid lightgray;
}
nav {
background: lightgray;
display: flex;
align-items: center;
padding: 0 0.5rem;
}
nav h1 {
flex: auto;
margin: 0;
}
nav h1 a {
text-decoration: none;
padding: 0.25rem 0.5rem;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav ul li a, nav ul li span, header .action {
display: block;
padding: 0.5rem;
}
.content {
padding: 0 1rem 1rem;
}
.content > header {
border-bottom: 1px solid lightgray;
display: flex;
align-items: flex-end;
}
.content > header h1 {
flex: auto;
margin: 1rem 0 0.25rem 0;
}
.flash {
margin: 1em 0;
padding: 1em;
background: #cae6f6;
border: 1px solid #377ba8;
}
.post > header {
display: flex;
align-items: flex-end;
font-size: 0.85em;
}
.post > header > div:first-of-type {
flex: auto;
}
.post > header h1 {
font-size: 1.5em;
margin-bottom: 0;
}
.post .about {
color: slategray;
font-style: italic;
}
.post .body {
white-space: pre-line;
}
.content:last-child {
margin-bottom: 0;
}
.content form {
margin: 1em 0;
display: flex;
flex-direction: column;
}
.content label {
font-weight: bold;
margin-bottom: 0.5em;
}
.content input, .content textarea {
margin-bottom: 1em;
}
.content textarea {
min-height: 12em;
resize: vertical;
}
input.danger {
color: #cc2f2e;
}
input[type=submit] {
align-self: start;
min-width: 10em;
}

15
examples/tutorial/flaskr/templates/auth/login.html

@ -0,0 +1,15 @@
{% 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 %}

15
examples/tutorial/flaskr/templates/auth/register.html

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Register{% 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="Register">
</form>
{% endblock %}

24
examples/tutorial/flaskr/templates/base.html

@ -0,0 +1,24 @@
<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1><a href="{{ url_for('index') }}">Flaskr</a></h1>
<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>

15
examples/tutorial/flaskr/templates/blog/create.html

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
<input type="submit" value="Save">
</form>
{% endblock %}

28
examples/tutorial/flaskr/templates/blog/index.html

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Posts{% endblock %}</h1>
{% if g.user %}
<a class="action" href="{{ url_for('blog.create') }}">New</a>
{% endif %}
{% endblock %}
{% block content %}
{% for post in posts %}
<article class="post">
<header>
<div>
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
</div>
{% if g.user['id'] == post['author_id'] %}
<a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ post['body'] }}</p>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}

19
examples/tutorial/flaskr/templates/blog/update.html

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] or post['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
<input type="submit" value="Save">
</form>
<hr>
<form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
</form>
{% endblock %}

13
examples/tutorial/setup.cfg

@ -0,0 +1,13 @@
[metadata]
license_file = LICENSE
[bdist_wheel]
universal = False
[tool:pytest]
testpaths = tests
[coverage:run]
branch = True
source =
flaskr

23
examples/tutorial/setup.py

@ -0,0 +1,23 @@
import io
from setuptools import find_packages, setup
with io.open('README.rst', 'rt', encoding='utf8') as f:
readme = f.read()
setup(
name='flaskr',
version='1.0.0',
url='http://flask.pocoo.org/docs/tutorial/',
license='BSD',
maintainer='Pallets team',
maintainer_email='contact@palletsprojects.com',
description='The basic blog app built in the Flask tutorial.',
long_description=readme,
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'flask',
],
)

64
examples/tutorial/tests/conftest.py

@ -0,0 +1,64 @@
import os
import tempfile
import pytest
from flaskr import create_app
from flaskr.db import get_db, init_db
# read in SQL for populating test data
with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f:
_data_sql = f.read().decode('utf8')
@pytest.fixture
def app():
"""Create and configure a new app instance for each test."""
# create a temporary file to isolate the database for each test
db_fd, db_path = tempfile.mkstemp()
# create the app with common test config
app = create_app({
'TESTING': True,
'DATABASE': db_path,
})
# create the database and load test data
with app.app_context():
init_db()
get_db().executescript(_data_sql)
yield app
# close and remove the temporary database
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(app):
"""A test client for the app."""
return app.test_client()
@pytest.fixture
def runner(app):
"""A test runner for the app's Click commands."""
return app.test_cli_runner()
class AuthActions(object):
def __init__(self, client):
self._client = client
def login(self, username='test', password='test'):
return self._client.post(
'/auth/login',
data={'username': username, 'password': password}
)
def logout(self):
return self._client.get('/auth/logout')
@pytest.fixture
def auth(client):
return AuthActions(client)

8
examples/tutorial/tests/data.sql

@ -0,0 +1,8 @@
INSERT INTO user (username, password)
VALUES
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'),
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79');
INSERT INTO post (title, body, author_id, created)
VALUES
('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00');

66
examples/tutorial/tests/test_auth.py

@ -0,0 +1,66 @@
import pytest
from flask import g, session
from flaskr.db import get_db
def test_register(client, app):
# test that viewing the page renders without template errors
assert client.get('/auth/register').status_code == 200
# test that successful registration redirects to the login page
response = client.post(
'/auth/register', data={'username': 'a', 'password': 'a'}
)
assert 'http://localhost/auth/login' == response.headers['Location']
# test that the user was inserted into the database
with app.app_context():
assert get_db().execute(
"select * from user where username = 'a'",
).fetchone() is not None
@pytest.mark.parametrize(('username', 'password', 'message'), (
('', '', b'Username is required.'),
('a', '', b'Password is required.'),
('test', 'test', b'already registered'),
))
def test_register_validate_input(client, username, password, message):
response = client.post(
'/auth/register',
data={'username': username, 'password': password}
)
assert message in response.data
def test_login(client, auth):
# test that viewing the page renders without template errors
assert client.get('/auth/login').status_code == 200
# test that successful login redirects to the index page
response = auth.login()
assert response.headers['Location'] == 'http://localhost/'
# login request set the user_id in the session
# check that the user is loaded from the session
with client:
client.get('/')
assert session['user_id'] == 1
assert g.user['username'] == 'test'
@pytest.mark.parametrize(('username', 'password', 'message'), (
('a', 'test', b'Incorrect username.'),
('test', 'a', b'Incorrect password.'),
))
def test_login_validate_input(auth, username, password, message):
response = auth.login(username, password)
assert message in response.data
def test_logout(client, auth):
auth.login()
with client:
auth.logout()
assert 'user_id' not in session

92
examples/tutorial/tests/test_blog.py

@ -0,0 +1,92 @@
import pytest
from flaskr.db import get_db
def test_index(client, auth):
response = client.get('/')
assert b"Log In" in response.data
assert b"Register" in response.data
auth.login()
response = client.get('/')
assert b'test title' in response.data
assert b'by test on 2018-01-01' in response.data
assert b'test\nbody' in response.data
assert b'href="/1/update"' in response.data
@pytest.mark.parametrize('path', (
'/create',
'/1/update',
'/1/delete',
))
def test_login_required(client, path):
response = client.post(path)
assert response.headers['Location'] == 'http://localhost/auth/login'
def test_author_required(app, client, auth):
# change the post author to another user
with app.app_context():
db = get_db()
db.execute('UPDATE post SET author_id = 2 WHERE id = 1')
db.commit()
auth.login()
# current user can't modify other user's post
assert client.post('/1/update').status_code == 403
assert client.post('/1/delete').status_code == 403
# current user doesn't see edit link
assert b'href="/1/update"' not in client.get('/').data
@pytest.mark.parametrize('path', (
'/2/update',
'/2/delete',
))
def test_exists_required(client, auth, path):
auth.login()
assert client.post(path).status_code == 404
def test_create(client, auth, app):
auth.login()
assert client.get('/create').status_code == 200
client.post('/create', data={'title': 'created', 'body': ''})
with app.app_context():
db = get_db()
count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0]
assert count == 2
def test_update(client, auth, app):
auth.login()
assert client.get('/1/update').status_code == 200
client.post('/1/update', data={'title': 'updated', 'body': ''})
with app.app_context():
db = get_db()
post = db.execute('SELECT * FROM post WHERE id = 1').fetchone()
assert post['title'] == 'updated'
@pytest.mark.parametrize('path', (
'/create',
'/1/update',
))
def test_create_update_validate(client, auth, path):
auth.login()
response = client.post(path, data={'title': '', 'body': ''})
assert b'Title is required.' in response.data
def test_delete(client, auth, app):
auth.login()
response = client.post('/1/delete')
assert response.headers['Location'] == 'http://localhost/'
with app.app_context():
db = get_db()
post = db.execute('SELECT * FROM post WHERE id = 1').fetchone()
assert post is None

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save