mirror of https://github.com/pallets/flask.git
First part of the tutorial. Many explanations missing but it's a start.
This commit is contained in:
parent
c4f5c2fb9a
commit
1246f4088a
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
|
@ -181,6 +181,13 @@ a.headerlink:hover {
|
|||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
-webkit-box-shadow: 2px 2px 1px #d8d8d8;
|
||||
-moz-box-shadow: 2px 2px 1px #d8d8d8;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title + p {
|
||||
display: inline;
|
||||
|
@ -222,6 +229,11 @@ pre, tt {
|
|||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-webkit-box-shadow: 4px 4px 3px #cdcdcd;
|
||||
-moz-box-shadow: 4px 4px 3px #cdcdcd;
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
-webkit-box-shadow: none;
|
||||
|
|
|
@ -7,9 +7,11 @@ Welcome to Flask
|
|||
|
||||
Welcome to Flask's documentation. This documentation is divided in
|
||||
different parts. I would suggest to get started with the
|
||||
:ref:`installation` and then heading over to the :ref:`quickstart`. If
|
||||
you want to dive into all the internal parts of Flask, check out the
|
||||
:ref:`api` documentation. Common patterns are described in the
|
||||
:ref:`installation` and then heading over to the :ref:`quickstart`.
|
||||
Besides the quickstart there is also a more detailed :ref:`tutorial` that
|
||||
shows how to create a complete (albeit small) application with Flask. If
|
||||
you rather want to dive into all the internal parts of Flask, check out
|
||||
the :ref:`api` documentation. Common patterns are described in the
|
||||
:ref:`patterns` section.
|
||||
|
||||
.. toctree::
|
||||
|
@ -18,6 +20,7 @@ you want to dive into all the internal parts of Flask, check out the
|
|||
foreword
|
||||
installation
|
||||
quickstart
|
||||
tutorial
|
||||
patterns
|
||||
api
|
||||
deploying
|
||||
|
|
|
@ -54,6 +54,19 @@ So what did that code do?
|
|||
|
||||
To stop the server, hit control-C.
|
||||
|
||||
.. admonition:: Troubleshooting
|
||||
|
||||
The browser is unable to access the server? Sometimes this is
|
||||
unfortunately caused by broken IPv6 support in your operating system,
|
||||
browser or a combination. For example on Snow Leopard Google Chrome is
|
||||
known to exhibit this behaviour.
|
||||
|
||||
If the browser does not load up the page, you can change the `app.run`
|
||||
call to force IPv4 usage::
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='127.0.0.1')
|
||||
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
|
|
|
@ -0,0 +1,354 @@
|
|||
.. _tutorial:
|
||||
|
||||
Tutorial
|
||||
========
|
||||
|
||||
You want to develop an application with Python and Flask? Here you have
|
||||
the chance to learn that by example. In this tutorial we will create a
|
||||
simple microblog application. It only supports one user that can create
|
||||
text-only entries and there are no feeds or comments, but it still
|
||||
features everything you need to get started. We will use Flask and SQLite
|
||||
as database which comes out of the box with Python, so there is nothing
|
||||
else you need.
|
||||
|
||||
If you want the full sourcecode in advance or for comparison, check out
|
||||
the `example source`_.
|
||||
|
||||
.. _example source:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
|
||||
|
||||
Introducing Flaskr
|
||||
------------------
|
||||
|
||||
We will call our blogging application flaskr here, feel free to chose a
|
||||
less web-2.0-ish name ;) Basically we want it to 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 he or she 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 page shows all entries so far in reverse order (newest on top) and
|
||||
the user can add new ones from there if logged in.
|
||||
|
||||
Here a screenshot from the final application:
|
||||
|
||||
.. image:: _static/flaskr.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of the final application
|
||||
|
||||
Step 0: Creating The Folders
|
||||
----------------------------
|
||||
|
||||
Before we get started, let's create the folders needed for this
|
||||
application::
|
||||
|
||||
/flaskr
|
||||
/static
|
||||
/templates
|
||||
|
||||
The `flaskr` folder is not a python package, but just something where we
|
||||
drop our files. Directly into this folder we will then put our database
|
||||
schema as well as main module in the following steps.
|
||||
|
||||
Step 1: Database Schema
|
||||
-----------------------
|
||||
|
||||
First we want to create the database schema. For this application only a
|
||||
single table is needed and we only want to support SQLite so that is quite
|
||||
easy. Just put the following contents into a file named `schema.sql` in
|
||||
the just created `flaskr` folder:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
drop table if exists entries;
|
||||
create table entries (
|
||||
id integer primary key autoincrement,
|
||||
title string not null,
|
||||
text string not null
|
||||
);
|
||||
|
||||
This schema consists of a single table called `entries` and 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.
|
||||
|
||||
Step 2: Application Setup Code
|
||||
------------------------------
|
||||
|
||||
Now that we have the schema in place we can create the application module.
|
||||
Let's call it `flaskr.py` inside the `flaskr` folder. For starters we
|
||||
will add the imports we will need as well as the config section::
|
||||
|
||||
# all the imports
|
||||
import sqlite3
|
||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||
render_template, flash
|
||||
|
||||
# configuration
|
||||
DATABASE = '/tmp/flaskr.db'
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'development key'
|
||||
USERNAME = 'admin'
|
||||
PASSWORD = 'default'
|
||||
|
||||
The `with_statement` and :func:`~contextlib.closing` function are used to
|
||||
make dealing with the database connection easier later on for setting up
|
||||
the initial database. Next we can create our actual application and
|
||||
initialize it with the config::
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET_KEY
|
||||
app.debug = DEBUG
|
||||
|
||||
We can also add a method to easily connect to the database sepcified::
|
||||
|
||||
def connect_db():
|
||||
return sqlite3.connect(DATABASE)
|
||||
|
||||
Finally we just add a line to the bottom of the file that fires up the
|
||||
server if we run that file as standalone application::
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
||||
.. admonition:: Troubleshooting
|
||||
|
||||
If you notice later that the browser cannot connect to the server
|
||||
during development, you might want to try this line instead::
|
||||
|
||||
app.run(host='127.0.0.1')
|
||||
|
||||
In a nutshell: Werkzeug starts up as IPv6 on many operating systems by
|
||||
default and not every browser is happy with that. This forces IPv4
|
||||
usage.
|
||||
|
||||
With that out of the way you should be able to start up the application
|
||||
without problems. When you head over to the server you will get an 404
|
||||
page not found error because we don't have any views yet. But we will
|
||||
focus on that a little later. First we should get the database working.
|
||||
|
||||
Step 3: Creating The Database
|
||||
-----------------------------
|
||||
|
||||
Flaskr is a database powered application as outlined earlier, and more
|
||||
precisely, an application powered by a relational database system. Such
|
||||
systems need a schema that tells them how to store that information. So
|
||||
before starting the server for the first time it's important to create
|
||||
that schema.
|
||||
|
||||
Such a schema can be created by piping the `schema.sql` file into the
|
||||
`sqlite3` command as follows::
|
||||
|
||||
sqlite3 /tmp/flaskr.db < schema.sql
|
||||
|
||||
The downside of this is that it requires the sqlite3 command to be
|
||||
installed which is not necessarily the case on every system. Also one has
|
||||
to provide the path to the database there which leaves some place for
|
||||
errors. It's a good idea to add a function that initializes the database
|
||||
for you to the application.
|
||||
|
||||
If you want to do that, you first have to import the
|
||||
:func:`contextlib.closing` function from the contextlib package. If you
|
||||
want to use Python 2.5 it's also necessary to enable the `with` statement
|
||||
first (`__future__` imports must be the very first import)::
|
||||
|
||||
from __future__ import with_statement
|
||||
from contextlib import closing
|
||||
|
||||
Next we can create a function called `init_db` that initializes the
|
||||
database::
|
||||
|
||||
def init_db():
|
||||
with closing(connect_db()) as db:
|
||||
with app.open_resource('schema.sql') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
Now it is possible to create a database by starting up a Python shell and
|
||||
importing and calling that function::
|
||||
|
||||
>>> from flaskr import init_db
|
||||
>>> init_db()
|
||||
|
||||
The :meth:`~flask.Flask.open_resource` function opens a file from the
|
||||
resource location (your flaskr folder) and allows you to read from it. We
|
||||
are using this here to execute a script on the database connection.
|
||||
|
||||
When we connect to a database we get a connection object (here called
|
||||
`db`) that can give us a cursor. On that cursor there is a method to
|
||||
execute a complete script. Finally we only have to commit the changes and
|
||||
close the transaction.
|
||||
|
||||
Step 4: Request Database Connections
|
||||
------------------------------------
|
||||
|
||||
Now we know how we can open database connections and use them for scripts,
|
||||
but how can we elegantly do that for requests? We will need the database
|
||||
connection in all our functions so it makes sense to initialize them
|
||||
before each request and shut them down afterwards.
|
||||
|
||||
Flask allows us to do that with the :meth:`~flask.Flask.request_init` and
|
||||
:meth:`~flask.Flask.request_shutdown` decorators::
|
||||
|
||||
@app.request_init
|
||||
def before_request():
|
||||
g.db = connect_db()
|
||||
|
||||
@app.request_shutdown
|
||||
def after_request(response):
|
||||
g.db.close()
|
||||
return response
|
||||
|
||||
Functions marked with :meth:`~flask.Flask.request_init` are called before
|
||||
a request and passed no arguments, functions marked with
|
||||
:meth:`~flask.Flask.request_shutdown` are called after a request and
|
||||
passed the response that will be sent to the client. They have to return
|
||||
that response object or a different one. In this case we just return it
|
||||
unchanged.
|
||||
|
||||
We store our current database connection on the special :data:`~flask.g`
|
||||
object that flask provides for us. This object stores information for one
|
||||
request only and is available from within each function. Never store such
|
||||
things on other objects because this would not work with threaded
|
||||
environments. That special :data:`~flask.g` object does some magic behind
|
||||
the scenes to ensure it does the right thing.
|
||||
|
||||
Step 5: The View Functions
|
||||
--------------------------
|
||||
|
||||
Now that the database connections are working we can start writing the
|
||||
view functions. We will need for of them:
|
||||
|
||||
Show Entries
|
||||
````````````
|
||||
|
||||
This view shows all the entries stored in the database::
|
||||
|
||||
@app.route('/')
|
||||
def show_entries():
|
||||
cur = g.db.execute('select title, text from entries order by id desc')
|
||||
entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
|
||||
return render_template('show_entries.html', entries=entries)
|
||||
|
||||
Add New Entry
|
||||
`````````````
|
||||
|
||||
This view lets the user add new entries if he's logged in. This only
|
||||
responds to `POST` requests, the actual form is shown on the
|
||||
`show_entries` page::
|
||||
|
||||
@app.route('/add', methods=['POST'])
|
||||
def add_entry():
|
||||
if not session.get('logged_in'):
|
||||
abort(401)
|
||||
g.db.execute('insert into entries (title, text) values (?, ?)',
|
||||
[request.form['title'], request.form['text']])
|
||||
g.db.commit()
|
||||
flash('New entry was successfully posted')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
Login and Logout
|
||||
````````````````
|
||||
|
||||
These functions are used to sign the user in and out::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if request.form['username'] != USERNAME:
|
||||
error = 'Invalid username'
|
||||
elif request.form['password'] != PASSWORD:
|
||||
error = 'Invalid password'
|
||||
else:
|
||||
session['logged_in'] = True
|
||||
flash('You were logged in')
|
||||
return redirect(url_for('show_entries'))
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('logged_in', None)
|
||||
flash('You were logged out')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
Step 6: The Templates
|
||||
---------------------
|
||||
|
||||
Now we should start working on the templates. If we request the URLs now
|
||||
we would only get an exception that Flask cannot find the templates.
|
||||
|
||||
Put the following templates into the `templates` folder:
|
||||
|
||||
layout.html
|
||||
```````````
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!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('login') }}">log in</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('logout') }}">log out</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class=flash>{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
||||
show_entries.html
|
||||
`````````````````
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% if g.logged_in %}
|
||||
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
|
||||
<dl>
|
||||
<dt>Title:
|
||||
<dd><input type=text size=30 name=title>
|
||||
<dt>Text:
|
||||
<dd><textarea name=text rows=5 cols=40></textarea>
|
||||
<dd><input type=submit value=Share>
|
||||
</dl>
|
||||
</form>
|
||||
{% endif %}
|
||||
<ul class=entries>
|
||||
{% for entry in entries %}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
||||
{% else %}
|
||||
<li><em>Unbelievable. No entries here so far</em>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
login.html
|
||||
``````````
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<h2>Login</h2>
|
||||
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
|
||||
<form action="{{ url_for('login') }}" method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username>
|
||||
<dt>Password:
|
||||
<dd><input type=password name=password>
|
||||
<dd><input type=submit value=Login>
|
||||
</dl>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -43,11 +43,8 @@ def init_db():
|
|||
|
||||
@app.request_init
|
||||
def before_request():
|
||||
"""Make sure we are connected to the database each request. Also
|
||||
set `g.logged_in` to `True` if we are logged in.
|
||||
"""
|
||||
"""Make sure we are connected to the database each request."""
|
||||
g.db = connect_db()
|
||||
g.logged_in = session.get('logged_in', False)
|
||||
|
||||
|
||||
@app.request_shutdown
|
||||
|
@ -66,7 +63,7 @@ def show_entries():
|
|||
|
||||
@app.route('/add', methods=['POST'])
|
||||
def add_entry():
|
||||
if not g.logged_in:
|
||||
if not session.get('logged_in'):
|
||||
abort(401)
|
||||
g.db.execute('insert into entries (title, text) values (?, ?)',
|
||||
[request.form['title'], request.form['text']])
|
||||
|
|
|
@ -3,14 +3,15 @@ a, h1, h2 { color: #377BA8; }
|
|||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
div.metanav { text-align: right; font-size: 0.8em; background: #fafafa;
|
||||
padding: 0.3em; margin-bottom: 1em; }
|
||||
|
||||
div.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
ul.entries { list-style: none; margin: 0; padding: 0; }
|
||||
ul.entries li { margin: 0.8em 1.2em; }
|
||||
ul.entries li h2 { margin-left: -1em; }
|
||||
div.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
form.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
|
||||
form.add-entry dl { font-weight: bold; }
|
||||
div.metanav { text-align: right; font-size: 0.8em; background: #fafafa;
|
||||
padding: 0.3em; margin-bottom: 1em; }
|
||||
div.flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; }
|
||||
p.error { background: #F0D6D6; padding: 0.5em; }
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class=page>
|
||||
<h1>Flaskr</h1>
|
||||
<div class=metanav>
|
||||
{% if not g.logged_in %}
|
||||
{% if not session.logged_in %}
|
||||
<a href="{{ url_for('login') }}">log in</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('logout') }}">log out</a>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<dt>Username:
|
||||
<dd><input type=text name=username>
|
||||
<dt>Password:
|
||||
<dd><input type=passowrd name=password>
|
||||
<dd><input type=password name=password>
|
||||
<dd><input type=submit value=Login>
|
||||
</dl>
|
||||
</form>
|
||||
|
|
Loading…
Reference in New Issue