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