diff options
Diffstat (limited to 'app/static/doc/flask-docs/_sources/tutorial')
11 files changed, 577 insertions, 0 deletions
diff --git a/app/static/doc/flask-docs/_sources/tutorial/css.txt b/app/static/doc/flask-docs/_sources/tutorial/css.txt new file mode 100644 index 0000000..03f62ed --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/css.txt @@ -0,0 +1,31 @@ +.. _tutorial-css: + +Step 7: Adding Style +==================== + +Now that everything else works, it's time to add some style to the +application. Just create a stylesheet called `style.css` in the `static` +folder we created before: + +.. 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`. diff --git a/app/static/doc/flask-docs/_sources/tutorial/dbcon.txt b/app/static/doc/flask-docs/_sources/tutorial/dbcon.txt new file mode 100644 index 0000000..99391a2 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/dbcon.txt @@ -0,0 +1,57 @@ +.. _tutorial-dbcon: + +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.before_request`, +:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request` +decorators:: + + @app.before_request + def before_request(): + g.db = connect_db() + + @app.teardown_request + def teardown_request(exception): + g.db.close() + +Functions marked with :meth:`~flask.Flask.before_request` are called before +a request and passed no arguments. Functions marked with +:meth:`~flask.Flask.after_request` 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. They are however not guaranteed +to be executed if an exception is raised, this is where functions marked with +:meth:`~flask.Flask.teardown_request` come in. They get called after the +response has been constructed. They are not allowed to modify the request, and +their return values are ignored. If an exception occurred while the request was +being processed, it is passed to each function; otherwise, `None` is passed in. + +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. + +Continue to :ref:`tutorial-views`. + +.. 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 + ``before_request`` and ``teardown_request`` functions below your existing + ``init_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: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ diff --git a/app/static/doc/flask-docs/_sources/tutorial/dbinit.txt b/app/static/doc/flask-docs/_sources/tutorial/dbinit.txt new file mode 100644 index 0000000..b546a1a --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/dbinit.txt @@ -0,0 +1,67 @@ +.. _tutorial-dbinit: + +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. For this we can use the `connect_db` function we defined +earlier. Just add that function below the `connect_db` function:: + + def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + +The :func:`~contextlib.closing` helper function allows us to keep a +connection open for the duration of the `with` block. The +:func:`~flask.Flask.open_resource` method of the application object +supports that functionality out of the box, so it can be used in the +`with` block directly. This 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. +SQLite 3 and other transactional databases will not commit unless you +explicitly tell it to. + +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() + +.. admonition:: Troubleshooting + + If you get an exception later that a table cannot be found check that + you did call the `init_db` function and that your table names are + correct (singular vs. plural for example). + +Continue with :ref:`tutorial-dbcon` diff --git a/app/static/doc/flask-docs/_sources/tutorial/folders.txt b/app/static/doc/flask-docs/_sources/tutorial/folders.txt new file mode 100644 index 0000000..6108093 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/folders.txt @@ -0,0 +1,23 @@ +.. _tutorial-folders: + +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. The files inside +the `static` folder are available to users of the application via `HTTP`. +This is the place where css and javascript files go. Inside the +`templates` folder Flask will look for `Jinja2`_ templates. The +templates you create later in the tutorial will go in this directory. + +Continue with :ref:`tutorial-schema`. + +.. _Jinja2: http://jinja.pocoo.org/2/ diff --git a/app/static/doc/flask-docs/_sources/tutorial/index.txt b/app/static/doc/flask-docs/_sources/tutorial/index.txt new file mode 100644 index 0000000..3f2d659 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/index.txt @@ -0,0 +1,32 @@ +.. _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/ + +.. toctree:: + :maxdepth: 2 + + introduction + folders + schema + setup + dbinit + dbcon + views + templates + css + testing diff --git a/app/static/doc/flask-docs/_sources/tutorial/introduction.txt b/app/static/doc/flask-docs/_sources/tutorial/introduction.txt new file mode 100644 index 0000000..c72bbd7 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/introduction.txt @@ -0,0 +1,33 @@ +.. _tutorial-introduction: + +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 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 page shows all entries so far in reverse order (newest on top) and + the user can add new ones from there if logged in. + +We will be using SQLite3 directly for that application because it's good +enough for an application of that size. For larger applications however +it makes a lot of sense to use `SQLAlchemy`_ that handles database +connections in a more intelligent way, allows 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. + +Here a screenshot from the final application: + +.. image:: ../_static/flaskr.png + :align: center + :class: screenshot + :alt: screenshot of the final application + +Continue with :ref:`tutorial-folders`. + +.. _SQLAlchemy: http://www.sqlalchemy.org/ diff --git a/app/static/doc/flask-docs/_sources/tutorial/schema.txt b/app/static/doc/flask-docs/_sources/tutorial/schema.txt new file mode 100644 index 0000000..c078667 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/schema.txt @@ -0,0 +1,25 @@ +.. _tutorial-schema: + +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. + +Continue with :ref:`tutorial-setup`. diff --git a/app/static/doc/flask-docs/_sources/tutorial/setup.txt b/app/static/doc/flask-docs/_sources/tutorial/setup.txt new file mode 100644 index 0000000..e9e4d67 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/setup.txt @@ -0,0 +1,90 @@ +.. _tutorial-setup: + +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. For +small applications it's a possibility to drop the configuration directly +into the module which we will be doing here. However a cleaner solution +would be to create a separate `.ini` or `.py` file and load that or import +the values from there. + +:: + + # 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' + +Next we can create our actual application and initialize it with the +config from the same file:: + + # create our little application :) + app = Flask(__name__) + app.config.from_object(__name__) + +:meth:`~flask.Config.from_object` will look at the given object (if it's a +string it will import it) and then look for all uppercase variables +defined there. In our case, the configuration we just wrote a few lines +of code above. You can also move that into a separate file. + +It is also a good idea to be able to load a configuration from a +configurable file. This is what :meth:`~flask.Config.from_envvar` can +do:: + + app.config.from_envvar('FLASKR_SETTINGS', silent=True) + +That way someone can set an environment variable called +:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will +then override the default values. The silent switch just tells Flask to +not complain if no such environment key is set. + +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. The +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! + +We also add a method to easily connect to the database specified. That +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. + +:: + + def connect_db(): + return sqlite3.connect(app.config['DATABASE']) + +Finally we just add a line to the bottom of the file that fires up the +server if we want to run that file as a standalone application:: + + if __name__ == '__main__': + app.run() + +With that out of the way you should be able to start up the application +without problems. Do this with the following command:: + + python flaskr.py + +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 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. + +.. 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-dbinit`. diff --git a/app/static/doc/flask-docs/_sources/tutorial/templates.txt b/app/static/doc/flask-docs/_sources/tutorial/templates.txt new file mode 100644 index 0000000..5ec5584 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/templates.txt @@ -0,0 +1,111 @@ +.. _tutorial-templates: + +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. The +templates are using `Jinja2`_ syntax and have autoescaping enabled by +default. This means that unless you mark a value in the code with +:class:`~flask.Markup` or with the ``|safe`` filter in the template, +Jinja2 will ensure that special characters such as ``<`` or ``>`` are +escaped with their XML equivalents. + +We are also using template inheritance which makes it possible to reuse +the layout of the website in all pages. + +Put the following templates into the `templates` folder: + +.. _Jinja2: http://jinja.pocoo.org/2/documentation/templates + +layout.html +----------- + +This template contains the HTML skeleton, the header and a link to log in +(or log out if the user was already logged in). It also displays the +flashed messages if there are any. The ``{% block body %}`` block can be +replaced by a block of the same name (``body``) in a child template. + +The :class:`~flask.session` dict is available in the template as well and +you can use that to check if the user is logged in or not. Note that in +Jinja you can access missing attributes and items of objects / dicts which +makes the following code work, even if there is no ``'logged_in'`` key in +the session: + +.. 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 +----------------- + +This template extends the `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. We also tell the +form to submit to your `add_entry` function and use `POST` as `HTTP` +method: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} + {% if session.logged_in %} + <form action="{{ url_for('add_entry') }}" method=post class=add-entry> + <dl> + <dt>Title: + <dd><input type=text size=30 name=title> + <dt>Text: + <dd><textarea name=text rows=5 cols=40></textarea> + <dd><input type=submit value=Share> + </dl> + </form> + {% endif %} + <ul class=entries> + {% for entry in entries %} + <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }} + {% else %} + <li><em>Unbelievable. No entries here so far</em> + {% endfor %} + </ul> + {% endblock %} + +login.html +---------- + +Finally the login template which basically just displays a form to allow +the user to login: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} + <h2>Login</h2> + {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} + <form action="{{ url_for('login') }}" method=post> + <dl> + <dt>Username: + <dd><input type=text name=username> + <dt>Password: + <dd><input type=password name=password> + <dd><input type=submit value=Login> + </dl> + </form> + {% endblock %} + +Continue with :ref:`tutorial-css`. diff --git a/app/static/doc/flask-docs/_sources/tutorial/testing.txt b/app/static/doc/flask-docs/_sources/tutorial/testing.txt new file mode 100644 index 0000000..34edd79 --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/testing.txt @@ -0,0 +1,10 @@ +.. _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 unittesting in the :ref:`testing` section of the +documentation. Go there to see how easy it is to test Flask applications. diff --git a/app/static/doc/flask-docs/_sources/tutorial/views.txt b/app/static/doc/flask-docs/_sources/tutorial/views.txt new file mode 100644 index 0000000..93bec3b --- /dev/null +++ b/app/static/doc/flask-docs/_sources/tutorial/views.txt @@ -0,0 +1,98 @@ +.. _tutorial-views: + +Step 5: The View Functions +========================== + +Now that the database connections are working we can start writing the +view functions. We will need four of them: + +Show Entries +------------ + +This view shows all the entries stored in the database. It listens on the +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 are tuples with the columns ordered like specified +in the select statement. This is good enough for small applications like +here, but you might want to convert them into a dict. If you are +interested in how to do that, check out the :ref:`easy-querying` example. + +The view function will pass the entries as dicts to the +`show_entries.html` template and return the rendered one:: + + @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 they are logged in. This only +responds to `POST` requests, the actual form is shown on the +`show_entries` page. If everything worked out well we will +:func:`~flask.flash` an information message to the next request and +redirect back to 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')) + +Note that we check that the user is logged in here (the `logged_in` key is +present in the session and `True`). + +.. admonition:: Security Note + + Be sure to use question marks when building SQL statements, as done in the + example above. Otherwise, your app will be vulnerable to SQL injection when + you use string formatting to build SQL statements. + See :ref:`sqlite3` for more. + +Login and Logout +---------------- + +These functions are used to sign the user in and out. Login checks the +username and password against the ones from the configuration and sets the +`logged_in` key in the session. If the user logged in successfully, that +key is set to `True`, and the user is redirected back to the `show_entries` +page. In addition, a message is flashed that informs the user that he or +she was logged in successfully. If an error occurred, the template is +notified about that, and the user is asked again:: + + @app.route('/login', methods=['GET', 'POST']) + def login(): + error = None + if request.method == 'POST': + if request.form['username'] != app.config['USERNAME']: + error = 'Invalid username' + elif request.form['password'] != app.config['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) + +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 +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 we don't have to check +if the user was logged in. + +:: + + @app.route('/logout') + def logout(): + session.pop('logged_in', None) + flash('You were logged out') + return redirect(url_for('show_entries')) + +Continue with :ref:`tutorial-templates`. |