From c33675f0251071ea47ce166b81ce8c637842f091 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 14 Apr 2010 16:44:29 +0200 Subject: [PATCH] Added mini blogging application as Flask example. This should become the tutorial. --- examples/flaskr/flaskr.py | 103 ++++++++++++++++++++ examples/flaskr/flaskr_tests.py | 64 ++++++++++++ examples/flaskr/schema.sql | 6 ++ examples/flaskr/static/style.css | 16 +++ examples/flaskr/templates/layout.html | 17 ++++ examples/flaskr/templates/login.html | 14 +++ examples/flaskr/templates/show_entries.html | 21 ++++ 7 files changed, 241 insertions(+) create mode 100644 examples/flaskr/flaskr.py create mode 100644 examples/flaskr/flaskr_tests.py create mode 100644 examples/flaskr/schema.sql create mode 100644 examples/flaskr/static/style.css create mode 100644 examples/flaskr/templates/layout.html create mode 100644 examples/flaskr/templates/login.html create mode 100644 examples/flaskr/templates/show_entries.html diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py new file mode 100644 index 00000000..10a6ff8b --- /dev/null +++ b/examples/flaskr/flaskr.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" + Flaskr + ~~~~~~ + + A microblog example application written as Flask tutorial with + Flask and sqlite3. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from __future__ import with_statement +import time +import sqlite3 +from contextlib import closing +from flask import Flask, request, session, g, redirect, url_for, abort, \ + render_template, flash +from werkzeug import secure_filename + +# configuration +DATABASE = '/tmp/flaskr.db' +DEBUG = True +SECRET_KEY = 'development key' +USERNAME = 'admin' +PASSWORD = 'default' + +# create our little application :) +app = Flask(__name__) +app.secret_key = SECRET_KEY +app.debug = DEBUG + + +def connect_db(): + """Returns a new connection to the database.""" + return sqlite3.connect(DATABASE) + + +def init_db(): + """Creates the database tables.""" + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + + +@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. + """ + g.db = connect_db() + g.logged_in = session.get('logged_in', False) + + +@app.request_shutdown +def after_request(response): + """Closes the database again at the end of the request.""" + g.db.close() + return response + + +@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) + + +@app.route('/add', methods=['POST']) +def add_entry(): + if not g.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')) + + +@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')) + + +if __name__ == '__main__': + app.run() diff --git a/examples/flaskr/flaskr_tests.py b/examples/flaskr/flaskr_tests.py new file mode 100644 index 00000000..f8a05976 --- /dev/null +++ b/examples/flaskr/flaskr_tests.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" + Flaskr Tests + ~~~~~~~~~~~~ + + Tests the Flaskr application. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flaskr +import unittest +import tempfile + + +class FlaskrTestCase(unittest.TestCase): + + def setUp(self): + """Before each test, set up a blank database""" + self.db = tempfile.NamedTemporaryFile() + self.app = flaskr.app.test_client() + flaskr.DATABASE = self.db.name + flaskr.init_db() + + def login(self, username, password): + return self.app.post('/login', data=dict( + username=username, + password=password + ), follow_redirects=True) + + def logout(self): + return self.app.get('/logout', follow_redirects=True) + + # testing functions + + def test_login_logout(self): + """Make sure login and logout works""" + rv = self.login(flaskr.USERNAME, flaskr.PASSWORD) + assert 'You were logged in' in rv.data + rv = self.logout() + assert 'You were logged out' in rv.data + rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD) + assert 'Invalid username' in rv.data + rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x') + assert 'Invalid password' in rv.data + + def test_messages(self): + """Test that messages work""" + # start with a blank state + rv = self.app.get('/') + assert 'No entries here so far' in rv.data + self.login(flaskr.USERNAME, flaskr.PASSWORD) + rv = self.app.post('/add', data=dict( + title='', + text='HTML allowed here' + ), follow_redirects=True) + assert 'No entries here so far' not in rv.data + self.login(flaskr.USERNAME, flaskr.PASSWORD) + assert '<Hello>' in rv.data + assert 'HTML allowed here' in rv.data + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/flaskr/schema.sql b/examples/flaskr/schema.sql new file mode 100644 index 00000000..970cca77 --- /dev/null +++ b/examples/flaskr/schema.sql @@ -0,0 +1,6 @@ +drop table if exists entries; +create table entries ( + id integer primary key autoincrement, + title string not null, + text string not null +); diff --git a/examples/flaskr/static/style.css b/examples/flaskr/static/style.css new file mode 100644 index 00000000..b0f38774 --- /dev/null +++ b/examples/flaskr/static/style.css @@ -0,0 +1,16 @@ +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; } +div.metanav { text-align: right; font-size: 0.8em; background: #fafafa; + padding: 0.3em; margin-bottom: 1em; } +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.flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; } +p.error { background: #F0D6D6; padding: 0.5em; } diff --git a/examples/flaskr/templates/layout.html b/examples/flaskr/templates/layout.html new file mode 100644 index 00000000..eafcefa9 --- /dev/null +++ b/examples/flaskr/templates/layout.html @@ -0,0 +1,17 @@ + +Flaskr + +
+

Flaskr

+
+ {% if not g.logged_in %} + log in + {% else %} + log out + {% endif %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block body %}{% endblock %} +
diff --git a/examples/flaskr/templates/login.html b/examples/flaskr/templates/login.html new file mode 100644 index 00000000..568e9e84 --- /dev/null +++ b/examples/flaskr/templates/login.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} +{% block body %} +

Login

+ {% if error %}

Error: {{ error }}{% endif %} +

+
+
Username: +
+
Password: +
+
+
+
+{% endblock %} diff --git a/examples/flaskr/templates/show_entries.html b/examples/flaskr/templates/show_entries.html new file mode 100644 index 00000000..55940ee7 --- /dev/null +++ b/examples/flaskr/templates/show_entries.html @@ -0,0 +1,21 @@ +{% extends "layout.html" %} +{% block body %} + {% if g.logged_in %} +
+
+
Title: +
+
Text: +
+
+
+
+ {% endif %} + +{% endblock %}