Permalink
Browse files

Added mini blogging application as Flask example.

This should become the tutorial.
  • Loading branch information...
1 parent 2d9bb69 commit c33675f0251071ea47ce166b81ce8c637842f091 @mitsuhiko mitsuhiko committed Apr 14, 2010
@@ -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()
@@ -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='<Hello>',
+ text='<strong>HTML</strong> allowed here'
+ ), follow_redirects=True)
+ assert 'No entries here so far' not in rv.data
+ self.login(flaskr.USERNAME, flaskr.PASSWORD)
+ assert '&lt;Hello&gt' in rv.data
+ assert '<strong>HTML</strong> allowed here' in rv.data
+
+
+if __name__ == '__main__':
+ unittest.main()
@@ -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
+);
@@ -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; }
@@ -0,0 +1,17 @@
+<!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 g.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>
@@ -0,0 +1,14 @@
+{% 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=passowrd name=password>
+ <dd><input type=submit value=Login>
+ </dl>
+ </form>
+{% endblock %}
@@ -0,0 +1,21 @@
+{% 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 %}

0 comments on commit c33675f

Please sign in to comment.