| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | # -*- coding: utf-8 -*- | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  | """
 | 
					
						
							|  |  |  |     MiniTwit | 
					
						
							|  |  |  |     ~~~~~~~~ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     A microblogging application written with Flask and sqlite3. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-01-03 02:21:07 +08:00
										 |  |  |     :copyright: (c) 2014 by Armin Ronacher. | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     :license: BSD, see LICENSE for more details. | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2013-05-22 07:33:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2010-12-02 00:22:55 +08:00
										 |  |  | from sqlite3 import dbapi2 as sqlite3 | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | from hashlib import md5 | 
					
						
							|  |  |  | from datetime import datetime | 
					
						
							|  |  |  | from flask import Flask, request, session, url_for, redirect, \ | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |      render_template, abort, g, flash, _app_ctx_stack | 
					
						
							| 
									
										
										
										
											2010-04-09 01:03:15 +08:00
										 |  |  | from werkzeug import check_password_hash, generate_password_hash | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # configuration | 
					
						
							|  |  |  | DATABASE = '/tmp/minitwit.db' | 
					
						
							|  |  |  | PER_PAGE = 30 | 
					
						
							|  |  |  | DEBUG = True | 
					
						
							|  |  |  | SECRET_KEY = 'development key' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # create our little application :) | 
					
						
							|  |  |  | app = Flask(__name__) | 
					
						
							| 
									
										
										
										
											2010-05-28 03:17:25 +08:00
										 |  |  | app.config.from_object(__name__) | 
					
						
							|  |  |  | app.config.from_envvar('MINITWIT_SETTINGS', silent=True) | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  | def get_db(): | 
					
						
							|  |  |  |     """Opens a new database connection if there is none yet for the
 | 
					
						
							|  |  |  |     current application context. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     top = _app_ctx_stack.top | 
					
						
							|  |  |  |     if not hasattr(top, 'sqlite_db'): | 
					
						
							|  |  |  |         top.sqlite_db = sqlite3.connect(app.config['DATABASE']) | 
					
						
							|  |  |  |         top.sqlite_db.row_factory = sqlite3.Row | 
					
						
							|  |  |  |     return top.sqlite_db | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @app.teardown_appcontext | 
					
						
							|  |  |  | def close_database(exception): | 
					
						
							|  |  |  |     """Closes the database again at the end of the request.""" | 
					
						
							|  |  |  |     top = _app_ctx_stack.top | 
					
						
							|  |  |  |     if hasattr(top, 'sqlite_db'): | 
					
						
							|  |  |  |         top.sqlite_db.close() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-02 18:46:04 +08:00
										 |  |  | def init_db(): | 
					
						
							|  |  |  |     """Initializes the database.""" | 
					
						
							| 
									
										
										
										
											2014-04-29 07:48:31 +08:00
										 |  |  |     db = get_db() | 
					
						
							|  |  |  |     with app.open_resource('schema.sql', mode='r') as f: | 
					
						
							|  |  |  |         db.cursor().executescript(f.read()) | 
					
						
							|  |  |  |     db.commit() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-02 18:46:04 +08:00
										 |  |  | @app.cli.command('initdb') | 
					
						
							|  |  |  | def initdb_command(): | 
					
						
							|  |  |  |     """Creates the database tables.""" | 
					
						
							|  |  |  |     init_db() | 
					
						
							|  |  |  |     print('Initialized the database.') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | def query_db(query, args=(), one=False): | 
					
						
							|  |  |  |     """Queries the database and returns a list of dictionaries.""" | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |     cur = get_db().execute(query, args) | 
					
						
							|  |  |  |     rv = cur.fetchall() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     return (rv[0] if rv else None) if one else rv | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_user_id(username): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Convenience method to look up the id for a username.""" | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |     rv = query_db('select user_id from user where username = ?', | 
					
						
							|  |  |  |                   [username], one=True) | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     return rv[0] if rv else None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def format_datetime(timestamp): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Format a timestamp for display.""" | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def gravatar_url(email, size=80): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Return the gravatar image for the given email address.""" | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ | 
					
						
							| 
									
										
										
										
											2010-04-06 22:02:14 +08:00
										 |  |  |         (md5(email.strip().lower().encode('utf-8')).hexdigest(), size) | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-16 17:03:16 +08:00
										 |  |  | @app.before_request | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | def before_request(): | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     g.user = None | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     if 'user_id' in session: | 
					
						
							|  |  |  |         g.user = query_db('select * from user where user_id = ?', | 
					
						
							|  |  |  |                           [session['user_id']], one=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @app.route('/') | 
					
						
							|  |  |  | def timeline(): | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     """Shows a users timeline or if no user is logged in it will
 | 
					
						
							|  |  |  |     redirect to the public timeline.  This timeline shows the user's | 
					
						
							|  |  |  |     messages as well as all the messages of followed users. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if not g.user: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         return redirect(url_for('public_timeline')) | 
					
						
							|  |  |  |     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(): | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     """Displays the latest messages of all users.""" | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     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): | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     """Display's a users tweets.""" | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     profile_user = query_db('select * from user where username = ?', | 
					
						
							|  |  |  |                             [username], one=True) | 
					
						
							|  |  |  |     if profile_user is None: | 
					
						
							|  |  |  |         abort(404) | 
					
						
							| 
									
										
										
										
											2010-04-19 05:42:11 +08:00
										 |  |  |     followed = False | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     if g.user: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         followed = query_db('''select 1 from follower where
 | 
					
						
							|  |  |  |             follower.who_id = ? and follower.whom_id = ?''',
 | 
					
						
							| 
									
										
										
										
											2010-04-21 00:40:58 +08:00
										 |  |  |             [session['user_id'], profile_user['user_id']], | 
					
						
							|  |  |  |             one=True) is not None | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     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): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Adds the current user as follower of the given user.""" | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     if not g.user: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         abort(401) | 
					
						
							|  |  |  |     whom_id = get_user_id(username) | 
					
						
							|  |  |  |     if whom_id is None: | 
					
						
							|  |  |  |         abort(404) | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |     db = get_db() | 
					
						
							|  |  |  |     db.execute('insert into follower (who_id, whom_id) values (?, ?)', | 
					
						
							|  |  |  |               [session['user_id'], whom_id]) | 
					
						
							|  |  |  |     db.commit() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     flash('You are now following "%s"' % username) | 
					
						
							|  |  |  |     return redirect(url_for('user_timeline', username=username)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @app.route('/<username>/unfollow') | 
					
						
							|  |  |  | def unfollow_user(username): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Removes the current user as follower of the given user.""" | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     if not g.user: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         abort(401) | 
					
						
							|  |  |  |     whom_id = get_user_id(username) | 
					
						
							|  |  |  |     if whom_id is None: | 
					
						
							|  |  |  |         abort(404) | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |     db = get_db() | 
					
						
							|  |  |  |     db.execute('delete from follower where who_id=? and whom_id=?', | 
					
						
							|  |  |  |               [session['user_id'], whom_id]) | 
					
						
							|  |  |  |     db.commit() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     flash('You are no longer following "%s"' % username) | 
					
						
							|  |  |  |     return redirect(url_for('user_timeline', username=username)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-09 07:32:39 +08:00
										 |  |  | @app.route('/add_message', methods=['POST']) | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | def add_message(): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Registers a new message for the user.""" | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     if 'user_id' not in session: | 
					
						
							|  |  |  |         abort(401) | 
					
						
							|  |  |  |     if request.form['text']: | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |         db = get_db() | 
					
						
							|  |  |  |         db.execute('''insert into message (author_id, text, pub_date)
 | 
					
						
							|  |  |  |           values (?, ?, ?)''', (session['user_id'], request.form['text'],
 | 
					
						
							|  |  |  |                                 int(time.time()))) | 
					
						
							|  |  |  |         db.commit() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         flash('Your message was recorded') | 
					
						
							|  |  |  |     return redirect(url_for('timeline')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-09 07:32:39 +08:00
										 |  |  | @app.route('/login', methods=['GET', 'POST']) | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | def login(): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Logs the user in.""" | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     if g.user: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-09 07:32:39 +08:00
										 |  |  | @app.route('/register', methods=['GET', 'POST']) | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | def register(): | 
					
						
							| 
									
										
										
										
											2010-04-12 06:14:59 +08:00
										 |  |  |     """Registers the user.""" | 
					
						
							| 
									
										
										
										
											2010-04-11 23:58:45 +08:00
										 |  |  |     if g.user: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         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 \ | 
					
						
							| 
									
										
										
										
											2014-04-29 07:48:31 +08:00
										 |  |  |                 '@' not in request.form['email']: | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |             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']: | 
					
						
							| 
									
										
										
										
											2010-04-12 00:45:06 +08:00
										 |  |  |             error = 'The two passwords do not match' | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |         elif get_user_id(request.form['username']) is not None: | 
					
						
							|  |  |  |             error = 'The username is already taken' | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2012-10-10 03:02:32 +08:00
										 |  |  |             db = get_db() | 
					
						
							|  |  |  |             db.execute('''insert into user (
 | 
					
						
							|  |  |  |               username, email, pw_hash) values (?, ?, ?)''',
 | 
					
						
							|  |  |  |               [request.form['username'], request.form['email'], | 
					
						
							|  |  |  |                generate_password_hash(request.form['password'])]) | 
					
						
							|  |  |  |             db.commit() | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |             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(): | 
					
						
							| 
									
										
										
										
											2010-04-21 00:40:58 +08:00
										 |  |  |     """Logs the user out.""" | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  |     flash('You were logged out') | 
					
						
							|  |  |  |     session.pop('user_id', None) | 
					
						
							|  |  |  |     return redirect(url_for('public_timeline')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-28 03:17:25 +08:00
										 |  |  | # add some filters to jinja | 
					
						
							| 
									
										
										
										
											2010-04-06 19:12:57 +08:00
										 |  |  | app.jinja_env.filters['datetimeformat'] = format_datetime | 
					
						
							|  |  |  | app.jinja_env.filters['gravatar'] = gravatar_url |