Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 249 lines (205 sloc) 8.424 kB
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
1 # -*- coding: utf-8 -*-
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
2 """
3 MiniTwit
4 ~~~~~~~~
5
6 A microblogging application written with Flask and sqlite3.
7
8 :copyright: (c) 2010 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10 """
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
11 from __future__ import with_statement
12 import time
1640679 @mitsuhiko Updated examples to work with pypy which has a incomplete sqlite3 in …
authored
13 from sqlite3 import dbapi2 as sqlite3
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
14 from hashlib import md5
15 from datetime import datetime
16 from contextlib import closing
17 from flask import Flask, request, session, url_for, redirect, \
3b36bef Improved documentation, added a contextmanager for request binding
Armin Ronacher authored
18 render_template, abort, g, flash
19 from werkzeug import check_password_hash, generate_password_hash
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
20
21
22 # configuration
23 DATABASE = '/tmp/minitwit.db'
24 PER_PAGE = 30
25 DEBUG = True
26 SECRET_KEY = 'development key'
27
28 # create our little application :)
29 app = Flask(__name__)
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
30 app.config.from_object(__name__)
31 app.config.from_envvar('MINITWIT_SETTINGS', silent=True)
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
32
33
34 def connect_db():
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
35 """Returns a new connection to the database."""
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
36 return sqlite3.connect(app.config['DATABASE'])
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
37
38
39 def init_db():
40 """Creates the database tables."""
41 with closing(connect_db()) as db:
42 with app.open_resource('schema.sql') as f:
43 db.cursor().executescript(f.read())
44 db.commit()
45
46
47 def query_db(query, args=(), one=False):
48 """Queries the database and returns a list of dictionaries."""
49 cur = g.db.execute(query, args)
50 rv = [dict((cur.description[idx][0], value)
51 for idx, value in enumerate(row)) for row in cur.fetchall()]
52 return (rv[0] if rv else None) if one else rv
53
54
55 def get_user_id(username):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
56 """Convenience method to look up the id for a username."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
57 rv = g.db.execute('select user_id from user where username = ?',
58 [username]).fetchone()
59 return rv[0] if rv else None
60
61
62 def format_datetime(timestamp):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
63 """Format a timestamp for display."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
64 return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M')
65
66
67 def gravatar_url(email, size=80):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
68 """Return the gravatar image for the given email address."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
69 return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
4ec7d2a @mitsuhiko Started working on documentation.
authored
70 (md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
71
72
fb2d2e4 @mitsuhiko request_init -> before_request and request_shutdown -> after_request
authored
73 @app.before_request
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
74 def before_request():
75 """Make sure we are connected to the database each request and look
76 up the current user so that we know he's there.
77 """
727c701 @mitsuhiko And finished documentation for most parts.
authored
78 g.db = connect_db()
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
79 g.user = None
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
80 if 'user_id' in session:
81 g.user = query_db('select * from user where user_id = ?',
82 [session['user_id']], one=True)
83
84
b51ecd7 @mitsuhiko Updated examples
authored
85 @app.teardown_request
86 def teardown_request(exception):
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
87 """Closes the database again at the end of the request."""
d3ca551 @mitsuhiko Updated the docs and examples to non-failing teardown handlers
authored
88 if hasattr(g, 'db'):
89 g.db.close()
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
90
91
92 @app.route('/')
93 def timeline():
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
94 """Shows a users timeline or if no user is logged in it will
95 redirect to the public timeline. This timeline shows the user's
96 messages as well as all the messages of followed users.
97 """
98 if not g.user:
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
99 return redirect(url_for('public_timeline'))
100 return render_template('timeline.html', messages=query_db('''
101 select message.*, user.* from message, user
102 where message.author_id = user.user_id and (
103 user.user_id = ? or
104 user.user_id in (select whom_id from follower
105 where who_id = ?))
106 order by message.pub_date desc limit ?''',
107 [session['user_id'], session['user_id'], PER_PAGE]))
108
109
110 @app.route('/public')
111 def public_timeline():
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
112 """Displays the latest messages of all users."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
113 return render_template('timeline.html', messages=query_db('''
114 select message.*, user.* from message, user
115 where message.author_id = user.user_id
116 order by message.pub_date desc limit ?''', [PER_PAGE]))
117
118
119 @app.route('/<username>')
120 def user_timeline(username):
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
121 """Display's a users tweets."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
122 profile_user = query_db('select * from user where username = ?',
123 [username], one=True)
124 if profile_user is None:
125 abort(404)
73a47a0 @mitsuhiko Removed unused stuff from minitwit and fixed a bug.
authored
126 followed = False
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
127 if g.user:
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
128 followed = query_db('''select 1 from follower where
129 follower.who_id = ? and follower.whom_id = ?''',
3c821a0 @florentx Fix typos and remove unused import.
florentx authored
130 [session['user_id'], profile_user['user_id']],
131 one=True) is not None
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
132 return render_template('timeline.html', messages=query_db('''
133 select message.*, user.* from message, user where
134 user.user_id = message.author_id and user.user_id = ?
135 order by message.pub_date desc limit ?''',
136 [profile_user['user_id'], PER_PAGE]), followed=followed,
137 profile_user=profile_user)
138
139
140 @app.route('/<username>/follow')
141 def follow_user(username):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
142 """Adds the current user as follower of the given user."""
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
143 if not g.user:
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
144 abort(401)
145 whom_id = get_user_id(username)
146 if whom_id is None:
147 abort(404)
148 g.db.execute('insert into follower (who_id, whom_id) values (?, ?)',
149 [session['user_id'], whom_id])
150 g.db.commit()
151 flash('You are now following "%s"' % username)
152 return redirect(url_for('user_timeline', username=username))
153
154
155 @app.route('/<username>/unfollow')
156 def unfollow_user(username):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
157 """Removes the current user as follower of the given user."""
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
158 if not g.user:
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
159 abort(401)
160 whom_id = get_user_id(username)
161 if whom_id is None:
162 abort(404)
163 g.db.execute('delete from follower where who_id=? and whom_id=?',
164 [session['user_id'], whom_id])
165 g.db.commit()
166 flash('You are no longer following "%s"' % username)
167 return redirect(url_for('user_timeline', username=username))
168
169
4aa7621 Updated documentation. Starting to look pretty good
Armin Ronacher authored
170 @app.route('/add_message', methods=['POST'])
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
171 def add_message():
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
172 """Registers a new message for the user."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
173 if 'user_id' not in session:
174 abort(401)
175 if request.form['text']:
176 g.db.execute('''insert into message (author_id, text, pub_date)
177 values (?, ?, ?)''', (session['user_id'], request.form['text'],
178 int(time.time())))
179 g.db.commit()
180 flash('Your message was recorded')
181 return redirect(url_for('timeline'))
182
183
4aa7621 Updated documentation. Starting to look pretty good
Armin Ronacher authored
184 @app.route('/login', methods=['GET', 'POST'])
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
185 def login():
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
186 """Logs the user in."""
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
187 if g.user:
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
188 return redirect(url_for('timeline'))
189 error = None
190 if request.method == 'POST':
191 user = query_db('''select * from user where
192 username = ?''', [request.form['username']], one=True)
193 if user is None:
194 error = 'Invalid username'
195 elif not check_password_hash(user['pw_hash'],
196 request.form['password']):
197 error = 'Invalid password'
198 else:
199 flash('You were logged in')
200 session['user_id'] = user['user_id']
201 return redirect(url_for('timeline'))
202 return render_template('login.html', error=error)
203
204
4aa7621 Updated documentation. Starting to look pretty good
Armin Ronacher authored
205 @app.route('/register', methods=['GET', 'POST'])
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
206 def register():
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
207 """Registers the user."""
03168a5 @mitsuhiko Removed the useless apishowcase example
authored
208 if g.user:
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
209 return redirect(url_for('timeline'))
210 error = None
211 if request.method == 'POST':
212 if not request.form['username']:
213 error = 'You have to enter a username'
214 elif not request.form['email'] or \
215 '@' not in request.form['email']:
216 error = 'You have to enter a valid email address'
217 elif not request.form['password']:
218 error = 'You have to enter a password'
219 elif request.form['password'] != request.form['password2']:
f2dc38c @mitsuhiko Added tests for minitwit. Testing with Flask is awesome
authored
220 error = 'The two passwords do not match'
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
221 elif get_user_id(request.form['username']) is not None:
222 error = 'The username is already taken'
223 else:
224 g.db.execute('''insert into user (
225 username, email, pw_hash) values (?, ?, ?)''',
226 [request.form['username'], request.form['email'],
227 generate_password_hash(request.form['password'])])
228 g.db.commit()
229 flash('You were successfully registered and can login now')
230 return redirect(url_for('login'))
231 return render_template('register.html', error=error)
232
233
234 @app.route('/logout')
235 def logout():
3c821a0 @florentx Fix typos and remove unused import.
florentx authored
236 """Logs the user out."""
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
237 flash('You were logged out')
238 session.pop('user_id', None)
239 return redirect(url_for('public_timeline'))
240
241
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
242 # add some filters to jinja
33850c0 @mitsuhiko Initial checkin of stuff that exists so far.
authored
243 app.jinja_env.filters['datetimeformat'] = format_datetime
244 app.jinja_env.filters['gravatar'] = gravatar_url
245
246
247 if __name__ == '__main__':
248 app.run()
Something went wrong with that request. Please try again.