forked from orbit-oss/flask
* Converts example/flaskr to have a setup.py Makes the flaskr app easier to run, ex. workflow: - pip install --editable . - export FLASK_APP=flaskr.flaskr - flask initdb - flask run Testing is also easier now: - python setup.py test * Fixed an import error in flaskr/tests - the statement `import flaskr` caused errors in python3 - `from . import flaskr` fixes the issue in 2.7.11 and 3.5.1 * Better project structure and updates the docs - Re-factors *flaskr*'s project structure a bit - Updates docs to make sense with the new structure - Adds a new step about installing Flask apps with setuptools - Switches first-person style writing to second-person (reads better IMO) - Adds segments in *testing.rst* for running tests with setuptools * Remove __init__.py from tests - py.test recommends not using __init__.py * Fix testing import errors
This commit is contained in:
parent
1ffd07ff5a
commit
17d4cb3828
26 changed files with 323 additions and 127 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
.. _tutorial-css:
|
.. _tutorial-css:
|
||||||
|
|
||||||
Step 7: Adding Style
|
Step 9: Adding Style
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Now that everything else works, it's time to add some style to the
|
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
|
application. Just create a stylesheet called :file:`style.css` in the
|
||||||
:file:`static` folder we created before:
|
:file:`static` folder:
|
||||||
|
|
||||||
.. sourcecode:: css
|
.. sourcecode:: css
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,23 @@
|
||||||
.. _tutorial-dbcon:
|
.. _tutorial-dbcon:
|
||||||
|
|
||||||
Step 3: Database Connections
|
Step 4: Database Connections
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
We have created a function for establishing a database connection with
|
You now have a function for establishing a database connection with
|
||||||
`connect_db`, but by itself, that's not particularly useful. Creating and
|
`connect_db`, but by itself, it is not particularly useful. Creating and
|
||||||
closing database connections all the time is very inefficient, so we want
|
closing database connections all the time is very inefficient, so you will
|
||||||
to keep it around for longer. Because database connections encapsulate a
|
need to keep it around for longer. Because database connections
|
||||||
transaction, we also need to make sure that only one request at the time
|
encapsulate a transaction, you will need to make sure that only one
|
||||||
uses the connection. How can we elegantly do that with Flask?
|
request at a time uses the connection. An elegant way to do this is by
|
||||||
|
utilizing the *application context*.
|
||||||
|
|
||||||
This is where the application context comes into play, so let's start
|
Flask provides two contexts: the *application context* and the
|
||||||
there.
|
*request context*. For the time being, all you have to know is that there
|
||||||
|
|
||||||
Flask provides us with 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
|
are special variables that use these. For instance, the
|
||||||
:data:`~flask.request` variable is the request object associated with
|
:data:`~flask.request` variable is the request object associated with
|
||||||
the current request, whereas :data:`~flask.g` is a general purpose
|
the current request, whereas :data:`~flask.g` is a general purpose
|
||||||
variable associated with the current application context. We will go into
|
variable associated with the current application context. The tutorial
|
||||||
the details of this a bit later.
|
will cover some more details of this later on.
|
||||||
|
|
||||||
For the time being, all you have to know is that you can store information
|
For the time being, all you have to know is that you can store information
|
||||||
safely on the :data:`~flask.g` object.
|
safely on the :data:`~flask.g` object.
|
||||||
|
|
@ -37,7 +35,7 @@ already established connection::
|
||||||
g.sqlite_db = connect_db()
|
g.sqlite_db = connect_db()
|
||||||
return g.sqlite_db
|
return g.sqlite_db
|
||||||
|
|
||||||
So now we know how to connect, but how do we properly disconnect? For
|
Now you know how to connect, but how can you properly disconnect? For
|
||||||
that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
|
that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
|
||||||
decorator. It's executed every time the application context tears down::
|
decorator. It's executed every time the application context tears down::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.. _tutorial-dbinit:
|
.. _tutorial-dbinit:
|
||||||
|
|
||||||
Step 4: Creating The Database
|
Step 5: Creating The Database
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
As outlined earlier, Flaskr is a database powered application, and more
|
As outlined earlier, Flaskr is a database powered application, and more
|
||||||
|
|
@ -16,13 +16,14 @@ Such a schema can be created by piping the ``schema.sql`` file into the
|
||||||
|
|
||||||
The downside of this is that it requires the ``sqlite3`` command to be
|
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
|
installed, which is not necessarily the case on every system. This also
|
||||||
requires that we provide the path to the database, which can introduce
|
requires that you provide the path to the database, which can introduce
|
||||||
errors. It's a good idea to add a function that initializes the database
|
errors. It's a good idea to add a function that initializes the database
|
||||||
for you to the application.
|
for you, to the application.
|
||||||
|
|
||||||
To do this, we can create a function and hook it into the :command:`flask`
|
To do this, you can create a function and hook it into a :command:`flask`
|
||||||
command that initializes the database. Let me show you the code first. Just
|
command that initializes the database. For now just take a look at the
|
||||||
add this function below the `connect_db` function in :file:`flaskr.py`::
|
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():
|
def init_db():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
@ -38,23 +39,23 @@ add this function below the `connect_db` function in :file:`flaskr.py`::
|
||||||
|
|
||||||
The ``app.cli.command()`` decorator registers a new command with the
|
The ``app.cli.command()`` decorator registers a new command with the
|
||||||
:command:`flask` script. When the command executes, Flask will automatically
|
:command:`flask` script. When the command executes, Flask will automatically
|
||||||
create an application context for us bound to the right application.
|
create an application context which is bound to the right application.
|
||||||
Within the function, we can then access :attr:`flask.g` and other things as
|
Within the function, you can then access :attr:`flask.g` and other things as
|
||||||
we would expect. When the script ends, the application context tears down
|
you might expect. When the script ends, the application context tears down
|
||||||
and the database connection is released.
|
and the database connection is released.
|
||||||
|
|
||||||
We want to keep an actual function around that initializes the database,
|
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
|
though, so that we can easily create databases in unit tests later on. (For
|
||||||
more information see :ref:`testing`.)
|
more information see :ref:`testing`.)
|
||||||
|
|
||||||
The :func:`~flask.Flask.open_resource` method of the application object
|
The :func:`~flask.Flask.open_resource` method of the application object
|
||||||
is a convenient helper function that will open a resource that the
|
is a convenient helper function that will open a resource that the
|
||||||
application provides. This function opens a file from the resource
|
application provides. This function opens a file from the resource
|
||||||
location (your ``flaskr`` folder) and allows you to read from it. We are
|
location (the :file:`flaskr/flaskr` folder) and allows you to read from it.
|
||||||
using this here to execute a script on the database connection.
|
It is used in this example to execute a script on the database connection.
|
||||||
|
|
||||||
The connection object provided by SQLite can give us a cursor object.
|
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, we
|
On that cursor, there is a method to execute a complete script. Finally, you
|
||||||
only have to commit the changes. SQLite3 and other transactional
|
only have to commit the changes. SQLite3 and other transactional
|
||||||
databases will not commit unless you explicitly tell it to.
|
databases will not commit unless you explicitly tell it to.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,25 @@
|
||||||
Step 0: Creating The Folders
|
Step 0: Creating The Folders
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Before we get started, let's create the folders needed for this
|
Before getting started, you will need to create the folders needed for this
|
||||||
application::
|
application::
|
||||||
|
|
||||||
/flaskr
|
/flaskr
|
||||||
/static
|
/flaskr
|
||||||
/templates
|
/static
|
||||||
|
/templates
|
||||||
|
|
||||||
The ``flaskr`` folder is not a Python package, but just something where we
|
The application will be installed and run as Python package. This is the
|
||||||
drop our files. Later on, we will put our database schema as well as main
|
recommended way to install and run Flask applications. You will see exactly
|
||||||
module into this folder. It is done in the following way. The files inside
|
how to run ``flaskr`` later on in this tutorial. For now go ahead and create
|
||||||
the :file:`static` folder are available to users of the application via HTTP.
|
the applications directory structure. In the next few steps you will be
|
||||||
This is the place where CSS and Javascript files go. Inside the
|
creating the database schema as well as the main module.
|
||||||
:file:`templates` folder, Flask will look for `Jinja2`_ templates. The
|
|
||||||
templates you create later on in the tutorial will go in this directory.
|
|
||||||
|
|
||||||
Continue with :ref:`tutorial-schema`.
|
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/
|
.. _Jinja2: http://jinja.pocoo.org/
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ the `example source`_.
|
||||||
folders
|
folders
|
||||||
schema
|
schema
|
||||||
setup
|
setup
|
||||||
|
setuptools
|
||||||
dbcon
|
dbcon
|
||||||
dbinit
|
dbinit
|
||||||
views
|
views
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
Introducing Flaskr
|
Introducing Flaskr
|
||||||
==================
|
==================
|
||||||
|
|
||||||
We will call our blogging application Flaskr, but feel free to choose your own
|
This tutorial will demonstrate a blogging application named Flaskr, but feel
|
||||||
less Web-2.0-ish name ;) Essentially, we want it to do the following things:
|
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
|
1. Let the user sign in and out with credentials specified in the
|
||||||
configuration. Only one user is supported.
|
configuration. Only one user is supported.
|
||||||
|
|
@ -14,8 +15,8 @@ less Web-2.0-ish name ;) Essentially, we want it to do the following things:
|
||||||
3. The index page shows all entries so far in reverse chronological order
|
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.
|
(newest on top) and the user can add new ones from there if logged in.
|
||||||
|
|
||||||
We will be using SQLite3 directly for this application because it's good
|
SQLite3 will be used directly for this application because it's good enough
|
||||||
enough for an application of this size. For larger applications, however,
|
for an application of this size. For larger applications, however,
|
||||||
it makes a lot of sense to use `SQLAlchemy`_, as it handles database
|
it makes a lot of sense to use `SQLAlchemy`_, as it handles database
|
||||||
connections in a more intelligent way, allowing you to target different
|
connections in a more intelligent way, allowing you to target different
|
||||||
relational databases at once and more. You might also want to consider
|
relational databases at once and more. You might also want to consider
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
Step 1: Database Schema
|
Step 1: Database Schema
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
First, we want to create the database schema. Only a single table is needed
|
In this step, you will create the database schema. Only a single table is
|
||||||
for this application and we only want to support SQLite, so creating the
|
needed for this application and it will only support SQLite. All you need to do
|
||||||
database schema is quite easy. Just put the following contents into a file
|
is put the following contents into a file named :file:`schema.sql` in the
|
||||||
named `schema.sql` in the just created `flaskr` folder:
|
:file:`flaskr/flaskr` folder:
|
||||||
|
|
||||||
.. sourcecode:: sql
|
.. sourcecode:: sql
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ named `schema.sql` in the just created `flaskr` folder:
|
||||||
'text' text not null
|
'text' text not null
|
||||||
);
|
);
|
||||||
|
|
||||||
This schema consists of a single table called ``entries``. Each row in
|
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
|
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
|
automatically incrementing integer and a primary key, the other two are
|
||||||
strings that must not be null.
|
strings that must not be null.
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@
|
||||||
Step 2: Application Setup Code
|
Step 2: Application Setup Code
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Now that we have the schema in place, we can create the application module.
|
Now that the schema is in place, you can create the application module,
|
||||||
Let's call it ``flaskr.py``. We will place this file inside the ``flaskr``
|
:file:`flaskr.py`. This file should be placed inside of the
|
||||||
folder. We will begin by adding the imports we need and by adding the config
|
:file:`flaskr/flaskr` folder. The first several lines of code in the
|
||||||
section. For small applications, it is possible to drop the configuration
|
application module are the needed import statements. After that there will be a
|
||||||
directly into the module, and this is what we will be doing here. However,
|
few lines of configuration code. For small applications like ``flaskr``, it is
|
||||||
a cleaner solution would be to create a separate ``.ini`` or ``.py`` file,
|
possible to drop the configuration directly into the module. However, a cleaner
|
||||||
load that, and import the values from there.
|
solution is to create a separate ``.ini`` or ``.py`` file, load that, and
|
||||||
|
import the values from there.
|
||||||
|
|
||||||
First, we add the imports in :file:`flaskr.py`::
|
Here are the import statements (in :file:`flaskr.py`)::
|
||||||
|
|
||||||
# all the imports
|
# all the imports
|
||||||
import os
|
import os
|
||||||
|
|
@ -19,12 +20,13 @@ First, we add the imports in :file:`flaskr.py`::
|
||||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||||
render_template, flash
|
render_template, flash
|
||||||
|
|
||||||
Next, we can create our actual application and initialize it with the
|
The next couple lines will create the actual application instance and
|
||||||
config from the same file in :file:`flaskr.py`::
|
initialize it with the config from the same file in :file:`flaskr.py`:
|
||||||
|
|
||||||
# create our little application :)
|
.. sourcecode:: python
|
||||||
app = Flask(__name__)
|
|
||||||
app.config.from_object(__name__)
|
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
|
# Load default config and override config from an environment variable
|
||||||
app.config.update(dict(
|
app.config.update(dict(
|
||||||
|
|
@ -35,8 +37,8 @@ config from the same file in :file:`flaskr.py`::
|
||||||
))
|
))
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
The :class:`~flask.Config` object works similarly to a dictionary so we
|
The :class:`~flask.Config` object works similarly to a dictionary, so it can be
|
||||||
can update it with new values.
|
updated with new values.
|
||||||
|
|
||||||
.. admonition:: Database Path
|
.. admonition:: Database Path
|
||||||
|
|
||||||
|
|
@ -55,10 +57,10 @@ can update it with new values.
|
||||||
|
|
||||||
Usually, it is a good idea to load a separate, environment-specific
|
Usually, it is a good idea to load a separate, environment-specific
|
||||||
configuration file. Flask allows you to import multiple configurations and it
|
configuration file. Flask allows you to import multiple configurations and it
|
||||||
will use the setting defined in the last import. This enables robust
|
will use the setting defined in the last import. This enables robust
|
||||||
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this.
|
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this.
|
||||||
|
|
||||||
.. code-block:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
|
|
@ -74,15 +76,15 @@ that in all cases, only variable names that are uppercase are considered.
|
||||||
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
||||||
Choose that key wisely and as hard to guess and complex as possible.
|
Choose that key wisely and as hard to guess and complex as possible.
|
||||||
|
|
||||||
We will also add a method that allows for easy connections to the
|
Lastly, you will add a method that allows for easy connections to the
|
||||||
specified database. This can be used to open a connection on request and
|
specified database. This can be used to open a connection on request and
|
||||||
also from the interactive Python shell or a script. This will come in
|
also from the interactive Python shell or a script. This will come in
|
||||||
handy later. We create a simple database connection through SQLite and
|
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.
|
then tell it to use the :class:`sqlite3.Row` object to represent rows.
|
||||||
This allows us to treat the rows as if they were dictionaries instead of
|
This allows the rows to be treated as if they were dictionaries instead of
|
||||||
tuples.
|
tuples.
|
||||||
|
|
||||||
::
|
.. sourcecode:: python
|
||||||
|
|
||||||
def connect_db():
|
def connect_db():
|
||||||
"""Connects to the specific database."""
|
"""Connects to the specific database."""
|
||||||
|
|
@ -90,29 +92,6 @@ tuples.
|
||||||
rv.row_factory = sqlite3.Row
|
rv.row_factory = sqlite3.Row
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
With that out of the way, you should be able to start up the application
|
In the next section you will see how to run the application.
|
||||||
without problems. Do this with the following commands::
|
|
||||||
|
|
||||||
export FLASK_APP=flaskr
|
Continue with :ref:`tutorial-setuptools`.
|
||||||
export FLASK_DEBUG=1
|
|
||||||
flask run
|
|
||||||
|
|
||||||
(In case you are on Windows you need to use `set` instead of `export`).
|
|
||||||
The :envvar:`FLASK_DEBUG` flag enables or disables 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.
|
|
||||||
|
|
||||||
When you head over to the server in your browser, you will get a 404 error
|
|
||||||
because we don't have any views yet. We will focus on that a little later,
|
|
||||||
but first, we 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`.
|
|
||||||
|
|
|
||||||
89
docs/tutorial/setuptools.rst
Normal file
89
docs/tutorial/setuptools.rst
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
.. _tutorial-setuptools:
|
||||||
|
|
||||||
|
Step 3: Installing flaskr with setuptools
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
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
|
||||||
|
using `setuptools`_. This involves creating a :file:`setup.py`
|
||||||
|
in the projects root directory. You also need to add an empty
|
||||||
|
:file:`__init__.py` file to make the :file:`flaskr/flaskr` directory
|
||||||
|
a package. The code structure at this point should be::
|
||||||
|
|
||||||
|
/flaskr
|
||||||
|
/flaskr
|
||||||
|
__init__.py
|
||||||
|
/static
|
||||||
|
/templates
|
||||||
|
setup.py
|
||||||
|
|
||||||
|
The content of the ``setup.py`` file for ``flaskr`` is:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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, go ahead and install the application with::
|
||||||
|
|
||||||
|
pip install --editable .
|
||||||
|
|
||||||
|
.. note:: The above installation command assumes that it is run within the
|
||||||
|
projects root directory, `flaskr/`. Also, the `editable` flag allows
|
||||||
|
editing source code without having to reinstall the Flask app each time
|
||||||
|
you make changes.
|
||||||
|
|
||||||
|
With that out of the way, you should be able to start up the application.
|
||||||
|
Do this with the following commands::
|
||||||
|
|
||||||
|
export FLASK_APP=flaskr.flaskr
|
||||||
|
export FLASK_DEBUG=1
|
||||||
|
flask run
|
||||||
|
|
||||||
|
(In case you are on Windows you need to use `set` instead of `export`).
|
||||||
|
The :envvar:`FLASK_DEBUG` flag enables or disables 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
.. _setuptools: https://setuptools.readthedocs.io
|
||||||
|
.. _virtualenv: https://virtualenv.pypa.io
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
.. _tutorial-templates:
|
.. _tutorial-templates:
|
||||||
|
|
||||||
Step 6: The Templates
|
Step 8: The Templates
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Now we should start working on the templates. If we were to request the URLs
|
Now it is time to start working on the templates. As you may have
|
||||||
now, we would only get an exception that Flask cannot find the templates. The
|
noticed, if you make requests with the app running, you will get
|
||||||
templates are using `Jinja2`_ syntax and have autoescaping enabled by
|
an exception that Flask cannot find the templates. The templates
|
||||||
|
are using `Jinja2`_ syntax and have autoescaping enabled by
|
||||||
default. This means that unless you mark a value in the code with
|
default. This means that unless you mark a value in the code with
|
||||||
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
||||||
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
||||||
|
|
@ -57,9 +58,9 @@ show_entries.html
|
||||||
|
|
||||||
This template extends the :file:`layout.html` template from above to display the
|
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
|
messages. Note that the ``for`` loop iterates over the messages we passed
|
||||||
in with the :func:`~flask.render_template` function. We also tell the
|
in with the :func:`~flask.render_template` function. Notice that the form is
|
||||||
form to submit to your `add_entry` function and use ``POST`` as HTTP
|
configured to to submit to the `add_entry` view function and use ``POST`` as
|
||||||
method:
|
HTTP method:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,81 @@ 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
|
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
|
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.
|
documentation. Go there to see how easy it is to test Flask applications.
|
||||||
|
|
||||||
|
Adding Tests to flaskr
|
||||||
|
======================
|
||||||
|
|
||||||
|
Assuming you have seen the testing section above 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/
|
||||||
|
context.py
|
||||||
|
test_flaskr.py
|
||||||
|
setup.py
|
||||||
|
MANIFEST.in
|
||||||
|
|
||||||
|
For now go ahead a create the :file:`tests/` directory as well as the
|
||||||
|
:file:`context.py` and :file:`test_flaskr.py` files, if you haven't
|
||||||
|
already. The context file is used as an import helper. The contents
|
||||||
|
of that file are::
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
basedir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, basedir + '/../')
|
||||||
|
|
||||||
|
from flaskr import flaskr
|
||||||
|
|
||||||
|
Testing + Setuptools
|
||||||
|
====================
|
||||||
|
|
||||||
|
One way to handle testing is to integrate it with ``setuptools``. All it
|
||||||
|
requires is adding a couple of lines to the :file:`setup.py` file and
|
||||||
|
creating a new file :file:`setup.cfg`. Go ahead and update the
|
||||||
|
:file:`setup.py` 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.
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
.. _tutorial-views:
|
.. _tutorial-views:
|
||||||
|
|
||||||
Step 5: The View Functions
|
Step 7: The View Functions
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Now that the database connections are working, we can start writing the
|
Now that the database connections are working, you can start writing the
|
||||||
view functions. We will need four of them:
|
view functions. You will need four of them:
|
||||||
|
|
||||||
Show Entries
|
Show Entries
|
||||||
------------
|
------------
|
||||||
|
|
@ -30,7 +30,7 @@ Add New Entry
|
||||||
|
|
||||||
This view lets the user add new entries if they are logged in. This only
|
This view lets the user add new entries if they are logged in. This only
|
||||||
responds to ``POST`` requests; the actual form is shown on the
|
responds to ``POST`` requests; the actual form is shown on the
|
||||||
`show_entries` page. If everything worked out well, we will
|
`show_entries` page. If everything worked out well, it will
|
||||||
:func:`~flask.flash` an information message to the next request and
|
:func:`~flask.flash` an information message to the next request and
|
||||||
redirect back to the `show_entries` page::
|
redirect back to the `show_entries` page::
|
||||||
|
|
||||||
|
|
@ -45,8 +45,8 @@ redirect back to the `show_entries` page::
|
||||||
flash('New entry was successfully posted')
|
flash('New entry was successfully posted')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('show_entries'))
|
||||||
|
|
||||||
Note that we check that the user is logged in here (the `logged_in` key is
|
Note that this view checks that the user is logged in (that is, if the
|
||||||
present in the session and ``True``).
|
`logged_in` key is present in the session and ``True``).
|
||||||
|
|
||||||
.. admonition:: Security Note
|
.. admonition:: Security Note
|
||||||
|
|
||||||
|
|
@ -81,11 +81,11 @@ notified about that, and the user is asked again::
|
||||||
return render_template('login.html', error=error)
|
return render_template('login.html', error=error)
|
||||||
|
|
||||||
The `logout` function, on the other hand, removes that key from the session
|
The `logout` function, on the other hand, removes that key from the session
|
||||||
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
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
|
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
|
will delete the key from the dictionary if present or do nothing when that
|
||||||
key is not in there. This is helpful because now we don't have to check
|
key is not in there. This is helpful because now it is not necessary to
|
||||||
if the user was logged in.
|
check if the user was logged in.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
@ -94,11 +94,24 @@ if the user was logged in.
|
||||||
session.pop('logged_in', None)
|
session.pop('logged_in', None)
|
||||||
flash('You were logged out')
|
flash('You were logged out')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('show_entries'))
|
||||||
|
|
||||||
Note that it is not a good idea to store passwords in plain text. You want to
|
.. admonition:: Security Note
|
||||||
protect login credentials if someone happens to have access to your database.
|
|
||||||
One way to do this is to use Security Helpers from Werkzeug to hash the
|
Passwords should never be stored in plain text in a production
|
||||||
password. However, the emphasis of this tutorial is to demonstrate the basics
|
system. This tutorial uses plain text passwords for simplicity. If you
|
||||||
of Flask and plain text passwords are used for simplicity.
|
plan to release a project based off this tutorial out into the world,
|
||||||
|
passwords should be both `hashed and salted`_ before being stored in a
|
||||||
|
database or file.
|
||||||
|
|
||||||
|
Fortunately, there are Flask extensions for the purpose of
|
||||||
|
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
|
||||||
|
`here <http://flask.pocoo.org/extensions/>`_
|
||||||
|
|
||||||
|
|
||||||
Continue with :ref:`tutorial-templates`.
|
Continue with :ref:`tutorial-templates`.
|
||||||
|
|
||||||
|
.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/
|
||||||
|
|
|
||||||
1
examples/flaskr/.gitignore
vendored
1
examples/flaskr/.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
flaskr.db
|
flaskr.db
|
||||||
|
.eggs/
|
||||||
|
|
|
||||||
3
examples/flaskr/MANIFEST.in
Normal file
3
examples/flaskr/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
graft flaskr/templates
|
||||||
|
graft flaskr/static
|
||||||
|
include flaskr/schema.sql
|
||||||
|
|
@ -13,15 +13,19 @@
|
||||||
export an FLASKR_SETTINGS environment variable
|
export an FLASKR_SETTINGS environment variable
|
||||||
pointing to a configuration file.
|
pointing to a configuration file.
|
||||||
|
|
||||||
2. Instruct flask to use the right application
|
2. install the app from the root of the project directory
|
||||||
|
|
||||||
export FLASK_APP=flaskr
|
pip install --editable .
|
||||||
|
|
||||||
3. initialize the database with this command:
|
3. Instruct flask to use the right application
|
||||||
|
|
||||||
|
export FLASK_APP=flaskr.flaskr
|
||||||
|
|
||||||
|
4. initialize the database with this command:
|
||||||
|
|
||||||
flask initdb
|
flask initdb
|
||||||
|
|
||||||
4. now you can run flaskr:
|
5. now you can run flaskr:
|
||||||
|
|
||||||
flask run
|
flask run
|
||||||
|
|
||||||
|
|
@ -30,5 +34,5 @@
|
||||||
|
|
||||||
~ Is it tested?
|
~ Is it tested?
|
||||||
|
|
||||||
You betcha. Run the `test_flaskr.py` file to see
|
You betcha. Run `python setup.py test` to see
|
||||||
the tests pass.
|
the tests pass.
|
||||||
|
|
|
||||||
0
examples/flaskr/flaskr/__init__.py
Normal file
0
examples/flaskr/flaskr/__init__.py
Normal file
2
examples/flaskr/setup.cfg
Normal file
2
examples/flaskr/setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[aliases]
|
||||||
|
test=pytest
|
||||||
16
examples/flaskr/setup.py
Normal file
16
examples/flaskr/setup.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='flaskr',
|
||||||
|
packages=['flaskr'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
setup_requires=[
|
||||||
|
'pytest-runner',
|
||||||
|
],
|
||||||
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
)
|
||||||
6
examples/flaskr/tests/context.py
Normal file
6
examples/flaskr/tests/context.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
basedir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, basedir + '/../')
|
||||||
|
|
||||||
|
from flaskr import flaskr
|
||||||
|
|
@ -10,11 +10,10 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import flaskr
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
from context import flaskr
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client(request):
|
def client(request):
|
||||||
Loading…
Add table
Add a link
Reference in a new issue