mirror of https://github.com/mitsuhiko/flask.git
Armin Ronacher
15 years ago
commit
33850c0ebd
15 changed files with 984 additions and 0 deletions
@ -0,0 +1,30 @@
|
||||
from flask import Flask, abort, redirect, request, session, \ |
||||
render_template, url_for |
||||
|
||||
#: create a new flask applications. We pass it the name of our module |
||||
#: so that flask knows where to look for templates and static files. |
||||
app = Flask(__name__) |
||||
|
||||
|
||||
@app.route('/', methods=['GET']) |
||||
def index(): |
||||
"""Show an overview page""" |
||||
return render_template('index.html') |
||||
|
||||
|
||||
@app.route('/hello/', methods=['GET', 'POST']) |
||||
def hello_user(): |
||||
"""Ask the user for a name and redirect to :func:`hello`""" |
||||
if request.method == 'POST': |
||||
return redirect(url_for('hello', name=request.form['name'])) |
||||
return render_template('hello.html', name=None) |
||||
|
||||
|
||||
@app.route('/hello/<name>', methods=['GET']) |
||||
def hello(name): |
||||
"""Greet name friendly""" |
||||
return render_template('hello.html', name=name) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
app.run(debug=True) |
@ -0,0 +1,7 @@
|
||||
body { |
||||
font-family: 'Trebuchet MS', sans-serif; |
||||
} |
||||
|
||||
a { |
||||
color: #44AD80; |
||||
} |
@ -0,0 +1,12 @@
|
||||
{% extends "layout.html" %} |
||||
{% block body %} |
||||
<p> |
||||
This is an example application that shows how |
||||
the Werkzeug powered Flask microframework works. |
||||
<p> |
||||
The various parts of the example application: |
||||
<ul> |
||||
<li><a href="{{ url_for('hello_user') }}">Hello World</a> |
||||
<li><a href="{{ url_for('counter') }}">Counter</a> |
||||
</ul> |
||||
{% endblock %} |
@ -0,0 +1,13 @@
|
||||
{% extends "layout.html" %} |
||||
{% block body %} |
||||
{% if name %} |
||||
<h2>Hello {{ name }}!</h2> |
||||
{% else %} |
||||
<h3>Hello Stranger …</h3> |
||||
<form action="{{ url_for('hello_user') }}" method="post"> |
||||
<p>… What's your name? |
||||
<p><input type=text name=name size=30> |
||||
<input type=submit value="That's me"> |
||||
</form> |
||||
{% endif %} |
||||
{% endblock %} |
@ -0,0 +1,11 @@
|
||||
{% extends "layout.html" %} |
||||
{% block body %} |
||||
<p> |
||||
This is an example application that shows how |
||||
the Werkzeug powered Flask microframework works. |
||||
<p> |
||||
The various parts of the example application: |
||||
<ul> |
||||
<li><a href="{{ url_for('hello_user') }}">Hello World</a> |
||||
</ul> |
||||
{% endblock %} |
@ -0,0 +1,8 @@
|
||||
<!doctype html> |
||||
<title>Flask API Showcase</title> |
||||
<link rel=stylesheet href="{{ url_for('static', filename='style.css') }}" type=text/css> |
||||
<h1>Flask API Showcase</h1> |
||||
{% if request.endpoint != 'index' %} |
||||
<div class=backlink><a href="{{ url_for('index') }}">« back to index</a></div> |
||||
{% endif %} |
||||
{% block body %}{% endblock %} |
@ -0,0 +1,228 @@
|
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import with_statement |
||||
import re |
||||
import time |
||||
import sqlite3 |
||||
from hashlib import md5 |
||||
from datetime import datetime |
||||
from contextlib import closing |
||||
from flask import Flask, request, session, url_for, redirect, \ |
||||
render_template, abort, g, flash, generate_password_hash, \ |
||||
check_password_hash |
||||
|
||||
|
||||
# configuration |
||||
DATABASE = '/tmp/minitwit.db' |
||||
PER_PAGE = 30 |
||||
DEBUG = True |
||||
SECRET_KEY = 'development key' |
||||
|
||||
# create our little application :) |
||||
app = Flask(__name__) |
||||
|
||||
|
||||
def connect_db(): |
||||
"""Returns a new database 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() |
||||
|
||||
|
||||
def query_db(query, args=(), one=False): |
||||
"""Queries the database and returns a list of dictionaries.""" |
||||
cur = g.db.execute(query, args) |
||||
rv = [dict((cur.description[idx][0], value) |
||||
for idx, value in enumerate(row)) for row in cur.fetchall()] |
||||
return (rv[0] if rv else None) if one else rv |
||||
|
||||
|
||||
def get_user_id(username): |
||||
"""Convenience method to look up the id for a username""" |
||||
rv = g.db.execute('select user_id from user where username = ?', |
||||
[username]).fetchone() |
||||
return rv[0] if rv else None |
||||
|
||||
|
||||
def format_datetime(timestamp): |
||||
"""Format a timestamp for display""" |
||||
return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M') |
||||
|
||||
|
||||
def gravatar_url(email, size=80): |
||||
"""Return the gravatar image for the given email address""" |
||||
return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ |
||||
(md5(email.lower().encode('utf-8')).hexdigest(), size) |
||||
|
||||
|
||||
@app.request_init |
||||
def before_request(): |
||||
"""Make sure we are connected to the database each request and look |
||||
up the current user so that we know he's there. |
||||
""" |
||||
g.db = sqlite3.connect(DATABASE) |
||||
if 'user_id' in session: |
||||
g.user = query_db('select * from user where user_id = ?', |
||||
[session['user_id']], one=True) |
||||
|
||||
|
||||
@app.request_shutdown |
||||
def after_request(request): |
||||
"""Closes the database again at the end of the request.""" |
||||
g.db.close() |
||||
return request |
||||
|
||||
|
||||
@app.route('/') |
||||
def timeline(): |
||||
if not 'user_id' in session: |
||||
return redirect(url_for('public_timeline')) |
||||
offset = request.args.get('offset', type=int) |
||||
return render_template('timeline.html', messages=query_db(''' |
||||
select message.*, user.* from message, user |
||||
where message.author_id = user.user_id and ( |
||||
user.user_id = ? or |
||||
user.user_id in (select whom_id from follower |
||||
where who_id = ?)) |
||||
order by message.pub_date desc limit ?''', |
||||
[session['user_id'], session['user_id'], PER_PAGE])) |
||||
|
||||
|
||||
@app.route('/public') |
||||
def public_timeline(): |
||||
return render_template('timeline.html', messages=query_db(''' |
||||
select message.*, user.* from message, user |
||||
where message.author_id = user.user_id |
||||
order by message.pub_date desc limit ?''', [PER_PAGE])) |
||||
|
||||
|
||||
@app.route('/<username>') |
||||
def user_timeline(username): |
||||
profile_user = query_db('select * from user where username = ?', |
||||
[username], one=True) |
||||
if profile_user is None: |
||||
abort(404) |
||||
followd = False |
||||
if 'user_id' in session: |
||||
followed = query_db('''select 1 from follower where |
||||
follower.who_id = ? and follower.whom_id = ?''', |
||||
[session['user_id'], profile_user['user_id']], one=True) is not None |
||||
return render_template('timeline.html', messages=query_db(''' |
||||
select message.*, user.* from message, user where |
||||
user.user_id = message.author_id and user.user_id = ? |
||||
order by message.pub_date desc limit ?''', |
||||
[profile_user['user_id'], PER_PAGE]), followed=followed, |
||||
profile_user=profile_user) |
||||
|
||||
|
||||
@app.route('/<username>/follow') |
||||
def follow_user(username): |
||||
if not 'user_id' in session: |
||||
abort(401) |
||||
whom_id = get_user_id(username) |
||||
if whom_id is None: |
||||
abort(404) |
||||
g.db.execute('insert into follower (who_id, whom_id) values (?, ?)', |
||||
[session['user_id'], whom_id]) |
||||
g.db.commit() |
||||
flash('You are now following "%s"' % username) |
||||
return redirect(url_for('user_timeline', username=username)) |
||||
|
||||
|
||||
@app.route('/<username>/unfollow') |
||||
def unfollow_user(username): |
||||
if not 'user_id' in session: |
||||
abort(401) |
||||
whom_id = get_user_id(username) |
||||
if whom_id is None: |
||||
abort(404) |
||||
g.db.execute('delete from follower where who_id=? and whom_id=?', |
||||
[session['user_id'], whom_id]) |
||||
g.db.commit() |
||||
flash('You are no longer following "%s"' % username) |
||||
return redirect(url_for('user_timeline', username=username)) |
||||
|
||||
|
||||
@app.route('/add_message') |
||||
def add_message(): |
||||
if 'user_id' not in session: |
||||
abort(401) |
||||
if request.form['text']: |
||||
g.db.execute('''insert into message (author_id, text, pub_date) |
||||
values (?, ?, ?)''', (session['user_id'], request.form['text'], |
||||
int(time.time()))) |
||||
g.db.commit() |
||||
flash('Your message was recorded') |
||||
return redirect(url_for('timeline')) |
||||
|
||||
|
||||
@app.route('/login') |
||||
def login(): |
||||
if 'user_id' in session: |
||||
return redirect(url_for('timeline')) |
||||
error = None |
||||
if request.method == 'POST': |
||||
user = query_db('''select * from user where |
||||
username = ?''', [request.form['username']], one=True) |
||||
if user is None: |
||||
error = 'Invalid username' |
||||
elif not check_password_hash(user['pw_hash'], |
||||
request.form['password']): |
||||
error = 'Invalid password' |
||||
else: |
||||
flash('You were logged in') |
||||
session['user_id'] = user['user_id'] |
||||
return redirect(url_for('timeline')) |
||||
return render_template('login.html', error=error) |
||||
|
||||
|
||||
@app.route('/register') |
||||
def register(): |
||||
if 'user_id' in session: |
||||
return redirect(url_for('timeline')) |
||||
error = None |
||||
if request.method == 'POST': |
||||
if not request.form['username']: |
||||
error = 'You have to enter a username' |
||||
elif not request.form['email'] or \ |
||||
'@' not in request.form['email']: |
||||
error = 'You have to enter a valid email address' |
||||
elif not request.form['password']: |
||||
error = 'You have to enter a password' |
||||
elif request.form['password'] != request.form['password2']: |
||||
error = 'The two passwords to not match' |
||||
elif get_user_id(request.form['username']) is not None: |
||||
error = 'The username is already taken' |
||||
else: |
||||
g.db.execute('''insert into user ( |
||||
username, email, pw_hash) values (?, ?, ?)''', |
||||
[request.form['username'], request.form['email'], |
||||
generate_password_hash(request.form['password'])]) |
||||
g.db.commit() |
||||
flash('You were successfully registered and can login now') |
||||
return redirect(url_for('login')) |
||||
return render_template('register.html', error=error) |
||||
|
||||
|
||||
@app.route('/logout') |
||||
def logout(): |
||||
flash('You were logged out') |
||||
session.pop('user_id', None) |
||||
return redirect(url_for('public_timeline')) |
||||
|
||||
|
||||
# add some filters to jinja and set the secret key and debug mode |
||||
# from the configuration. |
||||
app.jinja_env.filters['datetimeformat'] = format_datetime |
||||
app.jinja_env.filters['gravatar'] = gravatar_url |
||||
app.secret_key = SECRET_KEY |
||||
app.debug = DEBUG |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
app.run() |
@ -0,0 +1,21 @@
|
||||
drop table if exists user; |
||||
create table user ( |
||||
user_id integer primary key autoincrement, |
||||
username string not null, |
||||
email string not null, |
||||
pw_hash string not null |
||||
); |
||||
|
||||
drop table if exists follower; |
||||
create table follower ( |
||||
who_id integer, |
||||
whom_id integer |
||||
); |
||||
|
||||
drop table if exists message; |
||||
create table message ( |
||||
message_id integer primary key autoincrement, |
||||
author_id integer not null, |
||||
text string not null, |
||||
pub_date integer |
||||
); |
@ -0,0 +1,178 @@
|
||||
body { |
||||
background: #CAECE9; |
||||
font-family: 'Trebuchet MS', sans-serif; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
a { |
||||
color: #26776F; |
||||
} |
||||
|
||||
a:hover { |
||||
color: #333; |
||||
} |
||||
|
||||
input[type="text"], |
||||
input[type="password"] { |
||||
background: white; |
||||
border: 1px solid #BFE6E2; |
||||
padding: 2px; |
||||
font-family: 'Trebuchet MS', sans-serif; |
||||
font-size: 14px; |
||||
-moz-border-radius: 2px; |
||||
-webkit-border-radius: 2px; |
||||
color: #105751; |
||||
} |
||||
|
||||
input[type="submit"] { |
||||
background: #105751; |
||||
border: 1px solid #073B36; |
||||
padding: 1px 3px; |
||||
font-family: 'Trebuchet MS', sans-serif; |
||||
font-size: 14px; |
||||
font-weight: bold; |
||||
-moz-border-radius: 2px; |
||||
-webkit-border-radius: 2px; |
||||
color: white; |
||||
} |
||||
|
||||
div.page { |
||||
background: white; |
||||
border: 1px solid #6ECCC4; |
||||
width: 700px; |
||||
margin: 30px auto; |
||||
} |
||||
|
||||
div.page h1 { |
||||
background: #6ECCC4; |
||||
margin: 0; |
||||
padding: 10px 14px; |
||||
color: white; |
||||
letter-spacing: 1px; |
||||
text-shadow: 0 0 3px #24776F; |
||||
font-weight: normal; |
||||
} |
||||
|
||||
div.page div.navigation { |
||||
background: #DEE9E8; |
||||
padding: 4px 10px; |
||||
border-top: 1px solid #ccc; |
||||
border-bottom: 1px solid #eee; |
||||
color: #888; |
||||
font-size: 12px; |
||||
letter-spacing: 0.5px; |
||||
} |
||||
|
||||
div.page div.navigation a { |
||||
color: #444; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
div.page h2 { |
||||
margin: 0 0 15px 0; |
||||
color: #105751; |
||||
text-shadow: 0 1px 2px #ccc; |
||||
} |
||||
|
||||
div.page div.body { |
||||
padding: 10px; |
||||
} |
||||
|
||||
div.page div.footer { |
||||
background: #eee; |
||||
color: #888; |
||||
padding: 5px 10px; |
||||
font-size: 12px; |
||||
} |
||||
|
||||
div.page div.followstatus { |
||||
border: 1px solid #ccc; |
||||
background: #E3EBEA; |
||||
-moz-border-radius: 2px; |
||||
-webkit-border-radius: 2px; |
||||
padding: 3px; |
||||
font-size: 13px; |
||||
} |
||||
|
||||
div.page ul.messages { |
||||
list-style: none; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
div.page ul.messages li { |
||||
margin: 10px 0; |
||||
padding: 5px; |
||||
background: #F0FAF9; |
||||
border: 1px solid #DBF3F1; |
||||
-moz-border-radius: 5px; |
||||
-webkit-border-radius: 5px; |
||||
min-height: 48px; |
||||
} |
||||
|
||||
div.page ul.messages p { |
||||
margin: 0; |
||||
} |
||||
|
||||
div.page ul.messages li img { |
||||
float: left; |
||||
padding: 0 10px 0 0; |
||||
} |
||||
|
||||
div.page ul.messages li small { |
||||
font-size: 0.9em; |
||||
color: #888; |
||||
} |
||||
|
||||
div.page div.twitbox { |
||||
margin: 10px 0; |
||||
padding: 5px; |
||||
background: #F0FAF9; |
||||
border: 1px solid #94E2DA; |
||||
-moz-border-radius: 5px; |
||||
-webkit-border-radius: 5px; |
||||
} |
||||
|
||||
div.page div.twitbox h3 { |
||||
margin: 0; |
||||
font-size: 1em; |
||||
color: #2C7E76; |
||||
} |
||||
|
||||
div.page div.twitbox p { |
||||
margin: 0; |
||||
} |
||||
|
||||
div.page div.twitbox input[type="text"] { |
||||
width: 585px; |
||||
} |
||||
|
||||
div.page div.twitbox input[type="submit"] { |
||||
width: 70px; |
||||
margin-left: 5px; |
||||
} |
||||
|
||||
ul.flashes { |
||||
list-style: none; |
||||
margin: 10px 10px 0 10px; |
||||
padding: 0; |
||||
} |
||||
|
||||
ul.flashes li { |
||||
background: #B9F3ED; |
||||
border: 1px solid #81CEC6; |
||||
-moz-border-radius: 2px; |
||||
-webkit-border-radius: 2px; |
||||
padding: 4px; |
||||
font-size: 13px; |
||||
} |
||||
|
||||
div.error { |
||||
margin: 10px 0; |
||||
background: #FAE4E4; |
||||
border: 1px solid #DD6F6F; |
||||
-moz-border-radius: 2px; |
||||
-webkit-border-radius: 2px; |
||||
padding: 4px; |
||||
font-size: 13px; |
||||
} |
@ -0,0 +1,32 @@
|
||||
<!doctype html> |
||||
<title>{% block title %}Welcome{% endblock %} | MiniTwit</title> |
||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> |
||||
<div class=page> |
||||
<h1>MiniTwit</h1> |
||||
<div class=navigation> |
||||
{% if g.user %} |
||||
<a href="{{ url_for('timeline') }}">my timeline</a> | |
||||
<a href="{{ url_for('public_timeline') }}">public timeline</a> | |
||||
<a href="{{ url_for('logout') }}">sign out [{{ g.user.username }}]</a> |
||||
{% else %} |
||||
<a href="{{ url_for('public_timeline') }}">public timeline</a> | |
||||
<a href="{{ url_for('register') }}">sign up</a> | |
||||
<a href="{{ url_for('login') }}">sign in</a> |
||||
{% endif %} |
||||
</div> |
||||
{% with flashes = get_flashed_messages() %} |
||||
{% if flashes %} |
||||
<ul class=flashes> |
||||
{% for message in flashes %} |
||||
<li>{{ message }} |
||||
{% endfor %} |
||||
</ul> |
||||
{% endif %} |
||||
{% endwith %} |
||||
<div class=body> |
||||
{% block body %}{% endblock %} |
||||
</div> |
||||
<div class=footer> |
||||
MiniTwit — A Flask Application |
||||
</div> |
||||
</div> |
@ -0,0 +1,16 @@
|
||||
{% extends "layout.html" %} |
||||
{% block title %}Sign In{% endblock %} |
||||
{% block body %} |
||||
<h2>Sign In</h2> |
||||
{% if error %}<div class=error><strong>Error:</strong> {{ error }}</div>{% endif %} |
||||
<form action="" method=post> |
||||
<dl> |
||||
<dt>Username: |
||||
<dd><input type=text name=username size=30 value="{{ request.form.username }}"> |
||||
<dt>Password: |
||||
<dd><input type=password name=password size=30> |
||||
</dl> |
||||
<div class=actions><input type=submit value="Sign In"></div> |
||||
</form> |
||||
{% endblock %} |
||||
|
@ -0,0 +1,19 @@
|
||||
{% extends "layout.html" %} |
||||
{% block title %}Sign Up{% endblock %} |
||||
{% block body %} |
||||
<h2>Sign Up</h2> |
||||
{% if error %}<div class=error><strong>Error:</strong> {{ error }}</div>{% endif %} |
||||
<form action="" method=post> |
||||
<dl> |
||||
<dt>Username: |
||||
<dd><input type=text name=username size=30 value="{{ request.form.username }}"> |
||||
<dt>E-Mail: |
||||
<dd><input type=text name=email size=30 value="{{ request.form.email }}"> |
||||
<dt>Password: |
||||
<dd><input type=password name=password size=30> |
||||
<dt>Password <small>(repeat)</small>: |
||||
<dd><input type=password name=password2 size=30> |
||||
</dl> |
||||
<div class=actions><input type=submit value="Sign Up"></div> |
||||
</form> |
||||
{% endblock %} |
@ -0,0 +1,49 @@
|
||||
{% extends "layout.html" %} |
||||
{% block title %} |
||||
{% if request.endpoint == 'public_timeline' %} |
||||
Public Timeline |
||||
{% elif request.endpoint == 'user_timeline' %} |
||||
{{ profile_user.username }}'s Timeline |
||||
{% else %} |
||||
My Timeline |
||||
{% endif %} |
||||
{% endblock %} |
||||
{% block body %} |
||||
<h2>{{ self.title() }}</h2> |
||||
{% if g.user %} |
||||
{% if request.endpoint == 'user_timeline' %} |
||||
<div class=followstatus> |
||||
{% if g.user.user_id == profile_user.user_id %} |
||||
This is you! |
||||
{% elif followed %} |
||||
You are currently following this user. |
||||
<a class=unfollow href="{{ url_for('unfollow_user', username=profile_user.username) |
||||
}}">Unfollow user</a>. |
||||
{% else %} |
||||
You are not yet following this user. |
||||
<a class=follow href="{{ url_for('follow_user', username=profile_user.username) |
||||
}}">Follow user</a>. |
||||
{% endif %} |
||||
</div> |
||||
{% elif request.endpoint == 'timeline' %} |
||||
<div class=twitbox> |
||||
<h3>What's on your mind {{ g.user.username }}?</h3> |
||||
<form action="{{ url_for('add_message') }}" method=post> |
||||
<p><input type=text name=text size=60><!-- |
||||
--><input type=submit value="Share"> |
||||
</form> |
||||
</div> |
||||
{% endif %} |
||||
{% endif %} |
||||
<ul class=messages> |
||||
{% for message in messages %} |
||||
<li><img src="{{ message.email|gravatar(size=48) }}"><p> |
||||
<strong><a href="{{ url_for('user_timeline', username=message.username) |
||||
}}">{{ message.username }}</a></strong> |
||||
{{ message.text }} |
||||
<small>— {{ message.pub_date|datetimeformat }}</small> |
||||
{% else %} |
||||
<li><em>There are no messages so far.</em> |
||||
{% endfor %} |
||||
</ul> |
||||
{% endblock %} |
@ -0,0 +1,356 @@
|
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
flask |
||||
~~~~~ |
||||
|
||||
A microframework based on Werkzeug. It's extensively documented |
||||
and follows best practice patterns. |
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher. |
||||
:license: BSD, see LICENSE for more details. |
||||
""" |
||||
import os |
||||
import sys |
||||
import pkg_resources |
||||
from threading import local |
||||
from jinja2 import Environment, PackageLoader |
||||
from werkzeug import Request, Response, LocalStack, LocalProxy |
||||
from werkzeug.routing import Map, Rule |
||||
from werkzeug.exceptions import HTTPException, InternalServerError |
||||
from werkzeug.contrib.securecookie import SecureCookie |
||||
|
||||
# try to import the json helpers |
||||
try: |
||||
from simplejson import loads as load_json, dumps as dump_json |
||||
except ImportError: |
||||
try: |
||||
from json import loads as load_json, dumps as dump_json |
||||
except ImportError: |
||||
pass |
||||
|
||||
# utilities we import from Werkzeug and Jinja2 that are unused |
||||
# in the module but are exported as public interface. |
||||
from werkzeug import abort, redirect, secure_filename, cached_property, \ |
||||
html, import_string, generate_password_hash, check_password_hash |
||||
from jinja2 import Markup, escape |
||||
|
||||
|
||||
class FlaskRequest(Request): |
||||
"""The request object used by default in flask. Remembers the |
||||
matched endpoint and view arguments. |
||||
""" |
||||
|
||||
def __init__(self, environ): |
||||
Request.__init__(self, environ) |
||||
self.endpoint = None |
||||
self.view_args = None |
||||
|
||||
|
||||
class FlaskResponse(Response): |
||||
"""The response object that is used by default in flask. Works like the |
||||
response object from Werkzeug but is set to have a HTML mimetype by |
||||
default. |
||||
""" |
||||
default_mimetype = 'text/html' |
||||
|
||||
|
||||
class _RequestGlobals(object): |
||||
pass |
||||
|
||||
|
||||
class _RequestContext(object): |
||||
"""The request context contains all request relevant information. It is |
||||
created at the beginning of the request and pushed to the |
||||
`_request_ctx_stack` and removed at the end of it. It will create the |
||||
URL adapter and request object for the WSGI environment provided. |
||||
""" |
||||
|
||||
def __init__(self, app, environ): |
||||
self.app = app |
||||
self.url_adapter = app.url_map.bind_to_environ(environ) |
||||
self.request = app.request_class(environ) |
||||
self.session = app.open_session(self.request) |
||||
self.g = _RequestGlobals() |
||||
self.flashes = None |
||||
|
||||
|
||||
def url_for(endpoint, **values): |
||||
"""Generates a URL to the given endpoint with the method provided. |
||||
|
||||
:param endpoint: the endpoint of the URL (name of the function) |
||||
:param values: the variable arguments of the URL rule |
||||
""" |
||||
return _request_ctx_stack.top.url_adapter.build(endpoint, values) |
||||
|
||||
|
||||
def jsonified(**values): |
||||
"""Returns a json response""" |
||||
return current_app.response_class(dump_json(values), |
||||
mimetype='application/json') |
||||
|
||||
|
||||
def flash(message): |
||||
"""Flashes a message to the next request. In order to remove the |
||||
flashed message from the session and to display it to the user, |
||||
the template has to call :func:`get_flashed_messages`. |
||||
""" |
||||
session['_flashes'] = (session.get('_flashes', [])) + [message] |
||||
|
||||
|
||||
def get_flashed_messages(): |
||||
"""Pulls all flashed messages from the session and returns them. |
||||
Further calls in the same request to the function will return |
||||
the same messages. |
||||
""" |
||||
flashes = _request_ctx_stack.top.flashes |
||||
if flashes is None: |
||||
_request_ctx_stack.top.flashes = flashes = \ |
||||
session.pop('_flashes', []) |
||||
return flashes |
||||
|
||||
|
||||
def render_template(template_name, **context): |
||||
"""Renders a template from the template folder with the given |
||||
context. |
||||
""" |
||||
return current_app.jinja_env.get_template(template_name).render(context) |
||||
|
||||
|
||||
def render_template_string(source, **context): |
||||
"""Renders a template from the given template source string |
||||
with the given context. |
||||
""" |
||||
return current_app.jinja_env.from_string(source).render(context) |
||||
|
||||
|
||||
class Flask(object): |
||||
"""The flask object implements a WSGI application and acts as the central |
||||
object. It is passed the name of the module or package of the application |
||||
and optionally a configuration. When it's created it sets up the |
||||
template engine and provides ways to register view functions. |
||||
""" |
||||
|
||||
#: the class that is used for request objects |
||||
request_class = FlaskRequest |
||||
|
||||
#: the class that is used for response objects |
||||
response_class = FlaskResponse |
||||
|
||||
#: path for the static files. If you don't want to use static files |
||||
#: you can set this value to `None` in which case no URL rule is added |
||||
#: and the development server will no longer serve any static files. |
||||
static_path = '/static' |
||||
|
||||
#: if a secret key is set, cryptographic components can use this to |
||||
#: sign cookies and other things. Set this to a complex random value |
||||
#: when you want to use the secure cookie for instance. |
||||
secret_key = None |
||||
|
||||
#: The secure cookie uses this for the name of the session cookie |
||||
session_cookie_name = 'session' |
||||
|
||||
#: options that are passed directly to the Jinja2 environment |
||||
jinja_options = dict( |
||||
autoescape=True, |
||||
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] |
||||
) |
||||
|
||||
def __init__(self, package_name): |
||||
self.debug = False |
||||
self.package_name = package_name |
||||
self.view_functions = {} |
||||
self.error_handlers = {} |
||||
self.request_init_funcs = [] |
||||
self.request_shutdown_funcs = [] |
||||
self.url_map = Map() |
||||
|
||||
if self.static_path is not None: |
||||
self.url_map.add(Rule(self.static_path + '/<filename>', |
||||
build_only=True, endpoint='static')) |
||||
|
||||
self.jinja_env = Environment(loader=self.create_jinja_loader(), |
||||
**self.jinja_options) |
||||
self.jinja_env.globals.update( |
||||
url_for=url_for, |
||||
request=request, |
||||
session=session, |
||||
g=g, |
||||
get_flashed_messages=get_flashed_messages |
||||
) |
||||
|
||||
def create_jinja_loader(self): |
||||
"""Creates the Jinja loader. By default just a package loader for |
||||
the configured package is returned that looks up templates in the |
||||
`templates` folder. To add other loaders it's possible to |
||||
override this method. |
||||
""" |
||||
return PackageLoader(self.package_name) |
||||
|
||||
def run(self, host='localhost', port=5000, **options): |
||||
"""Runs the application on a local development server""" |
||||
from werkzeug import run_simple |
||||
if 'debug' in options: |
||||
self.debug = options.pop('debug') |
||||
if self.static_path is not None: |
||||
options['static_files'] = { |
||||
self.static_path: (self.package_name, 'static') |
||||
} |
||||
options.setdefault('use_reloader', self.debug) |
||||
options.setdefault('use_debugger', self.debug) |
||||
return run_simple(host, port, self, **options) |
||||
|
||||
@cached_property |
||||
def test(self): |
||||
"""A test client for this application""" |
||||
from werkzeug import Client |
||||
return Client(self, self.response_class, use_cookies=True) |
||||
|
||||
def open_resource(self, resource): |
||||
"""Opens a resource from the application's resource folder""" |
||||
return pkg_resources.resource_stream(self.package_name, resource) |
||||
|
||||
def open_session(self, request): |
||||
"""Creates or opens a new session. Default implementation requires |
||||
that `securecookie.secret_key` is set. |
||||
""" |
||||
key = self.secret_key |
||||
if key is not None: |
||||
return SecureCookie.load_cookie(request, self.session_cookie_name, |
||||
secret_key=key) |
||||
|
||||
def save_session(self, session, response): |
||||
"""Saves the session if it needs updates.""" |
||||
if session is not None: |
||||
session.save_cookie(response, self.session_cookie_name) |
||||
|
||||
def route(self, rule, **options): |
||||
"""A decorator that is used to register a view function for a |
||||
given URL rule. Example:: |
||||
|
||||
@app.route('/') |
||||
def index(): |
||||
return 'Hello World' |
||||
""" |
||||
def decorator(f): |
||||
if 'endpoint' not in options: |
||||
options['endpoint'] = f.__name__ |
||||
self.url_map.add(Rule(rule, **options)) |
||||
self.view_functions[options['endpoint']] = f |
||||
return f |
||||
return decorator |
||||
|
||||
def errorhandler(self, code): |
||||
"""A decorator that is used to register a function give a given |
||||
error code. Example:: |
||||
|
||||
@app.errorhandler(404) |
||||
def page_not_found(): |
||||
return 'This page does not exist', 404 |
||||
""" |
||||
def decorator(f): |
||||
self.error_handlers[code] = f |
||||
return f |
||||
return decorator |
||||
|
||||
def request_init(self, f): |
||||
"""Registers a function to run before each request.""" |
||||
self.request_init_funcs.append(f) |
||||
return f |
||||
|
||||
def request_shutdown(self, f): |
||||
"""Register a function to be run after each request.""" |
||||
self.request_shutdown_funcs.append(f) |
||||
return f |
||||
|
||||
def match_request(self): |
||||
"""Matches the current request against the URL map and also |
||||
stores the endpoint and view arguments on the request object |
||||
is successful, otherwise the exception is stored. |
||||
""" |
||||
rv = _request_ctx_stack.top.url_adapter.match() |
||||
request.endpoint, request.view_args = rv |
||||
return rv |
||||
|
||||
def dispatch_request(self): |
||||
"""Does the request dispatching. Matches the URL and returns the |
||||
return value of the view or error handler. This does not have to |
||||
be a response object. In order to convert the return value to a |
||||
proper response object, call :func:`make_response`. |
||||
""" |
||||
try: |
||||
endpoint, values = self.match_request() |
||||
return self.view_functions[endpoint](**values) |
||||
except HTTPException, e: |
||||
handler = self.error_handlers.get(e.code) |
||||
if handler is None: |
||||
return e |
||||
return handler(e) |
||||
except Exception, e: |
||||
handler = self.error_handlers.get(500) |
||||
if self.debug or handler is None: |
||||
raise |
||||
return handler(e) |
||||
|
||||
def make_response(self, rv): |
||||
"""Converts the return value from a view function to a real |
||||
response object that is an instance of :attr:`response_class`. |
||||
""" |
||||
if isinstance(rv, self.response_class): |
||||
return rv |
||||
if isinstance(rv, basestring): |
||||
return self.response_class(rv) |
||||
if isinstance(rv, tuple): |
||||
return self.response_class(*rv) |
||||
return self.response_class.force_type(rv, request.environ) |
||||
|
||||
def preprocess_request(self): |
||||
"""Called before the actual request dispatching and will |
||||
call every as :func:`request_init` decorated function. |
||||
If any of these function returns a value it's handled as |
||||
if it was the return value from the view and further |
||||
request handling is stopped. |
||||
""" |
||||
for func in self.request_init_funcs: |
||||
rv = func() |
||||
if rv is not None: |
||||
return rv |
||||
|
||||
def process_response(self, response): |
||||
"""Can be overridden in order to modify the response object |
||||
before it's sent to the WSGI server. |
||||
""" |
||||
session = _request_ctx_stack.top.session |
||||
if session is not None: |
||||
self.save_session(session, response) |
||||
for handler in self.request_shutdown_funcs: |
||||
response = handler(response) |
||||
return response |
||||
|
||||
def wsgi_app(self, environ, start_response): |
||||
"""The actual WSGI application. This is not implemented in |
||||
`__call__` so that middlewares can be applied: |
||||
|
||||
app.wsgi_app = MyMiddleware(app.wsgi_app) |
||||
""" |
||||
_request_ctx_stack.push(_RequestContext(self, environ)) |
||||
try: |
||||
rv = self.preprocess_request() |
||||
if rv is None: |
||||
rv = self.dispatch_request() |
||||
response = self.make_response(rv) |
||||
response = self.process_response(response) |
||||
return response(environ, start_response) |
||||
finally: |
||||
_request_ctx_stack.pop() |
||||
|
||||
def __call__(self, environ, start_response): |
||||
"""Shortcut for :attr:`wsgi_app`""" |
||||
return self.wsgi_app(environ, start_response) |
||||
|
||||
|
||||
# context locals |
||||
_request_ctx_stack = LocalStack() |
||||
current_app = LocalProxy(lambda: _request_ctx_stack.top.app) |
||||
request = LocalProxy(lambda: _request_ctx_stack.top.request) |
||||
session = LocalProxy(lambda: _request_ctx_stack.top.session) |
||||
g = LocalProxy(lambda: _request_ctx_stack.top.g) |
Loading…
Reference in new issue