python 3, pep8 and other various updates

This commit is contained in:
Miguel Grinberg 2014-09-19 07:47:13 -07:00
parent 6b193afe47
commit afa6c33e9e
41 changed files with 569 additions and 2984 deletions

View File

@ -1,4 +1,3 @@
web: gunicorn runp-heroku:app
init: python db_create.py && pybabel compile -d app/translations
upgrade: python db_upgrade.py && pybabel compile -d app/translations
init: python db_create.py
upgrade: python db_upgrade.py

View File

@ -6,11 +6,7 @@ A decently featured microblogging web application written in Python and Flask th
Installation
------------
The tutorial referenced above explains how to setup a virtual environment with all the required modules. As a convenience, the `setup.py` script will create this virtual environment for you. You can run this script again to refresh any missing modules. Note that to be able to run this script you have to have the following packages installed:
- Python 2.7
- Python development package (`python-dev` for most Linux distributions)
- git
The tutorial referenced above explains how to setup a virtual environment with all the required modules.
The sqlite database must also be created before the application can run, and the `db_create.py` script takes care of that. See the [Database tutorial](http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-database) for the details.

View File

@ -1,12 +1,14 @@
import os
from flask import Flask
from flask.json import JSONEncoder
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.login import LoginManager
from flask.ext.openid import OpenID
from flask.ext.mail import Mail
from flask.ext.babel import Babel, lazy_gettext
from config import basedir, ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD
from momentjs import momentjs
from config import basedir, ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, \
MAIL_PASSWORD
from .momentjs import momentjs
app = Flask(__name__)
app.config.from_object('config')
@ -19,22 +21,41 @@ oid = OpenID(app, os.path.join(basedir, 'tmp'))
mail = Mail(app)
babel = Babel(app)
class CustomJSONEncoder(JSONEncoder):
"""This class adds support for lazy translation texts to Flask's
JSON encoder. This is necessary when flashing translated texts."""
def default(self, obj):
from speaklater import is_lazy_string
if is_lazy_string(obj):
try:
return unicode(obj) # python 2
except NameError:
return str(obj) # python 3
return super(CustomJSONEncoder, self).default(obj)
app.json_encoder = CustomJSONEncoder
if not app.debug and MAIL_SERVER != '':
import logging
from logging.handlers import SMTPHandler
credentials = None
if MAIL_USERNAME or MAIL_PASSWORD:
credentials = (MAIL_USERNAME, MAIL_PASSWORD)
mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT), 'no-reply@' + MAIL_SERVER, ADMINS, 'microblog failure', credentials)
mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT),
'no-reply@' + MAIL_SERVER, ADMINS,
'microblog failure', credentials)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
if not app.debug and os.environ.get('HEROKU') is None:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024 * 1024, 10)
file_handler = RotatingFileHandler('tmp/microblog.log', 'a',
1 * 1024 * 1024, 10)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('microblog startup')
@ -49,4 +70,3 @@ if os.environ.get('HEROKU') is not None:
app.jinja_env.globals['momentjs'] = momentjs
from app import views, models

View File

@ -1,8 +1,8 @@
from threading import Thread
def async(f):
def wrapper(*args, **kwargs):
thr = Thread(target = f, args = args, kwargs = kwargs)
thr = Thread(target=f, args=args, kwargs=kwargs)
thr.start()
return wrapper

View File

@ -1,28 +1,28 @@
from flask import render_template
from flask import render_template, current_app
from flask.ext.mail import Message
from app import mail
from decorators import async
from .decorators import async
from config import ADMINS
@async
def send_async_email(msg):
mail.send(msg)
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender = sender, recipients = recipients)
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
send_async_email(msg)
#thr = threading.Thread(target = send_async_email, args = [msg])
#thr.start()
send_async_email(current_app, msg)
def follower_notification(followed, follower):
send_email("[microblog] %s is now following you!" % follower.nickname,
ADMINS[0],
[followed.email],
render_template("follower_email.txt",
user = followed, follower = follower),
render_template("follower_email.html",
user = followed, follower = follower))
ADMINS[0],
[followed.email],
render_template("follower_email.txt",
user=followed, follower=follower),
render_template("follower_email.html",
user=followed, follower=follower))

View File

@ -1,16 +1,18 @@
from flask.ext.wtf import Form
from wtforms import TextField, BooleanField, TextAreaField
from wtforms.validators import Required, Length
from flask.ext.babel import gettext
from app.models import User
from wtforms import StringField, BooleanField, TextAreaField
from wtforms.validators import DataRequired, Length
from .models import User
class LoginForm(Form):
openid = TextField('openid', validators = [Required()])
remember_me = BooleanField('remember_me', default = False)
openid = StringField('openid', validators=[DataRequired()])
remember_me = BooleanField('remember_me', default=False)
class EditForm(Form):
nickname = TextField('nickname', validators = [Required()])
about_me = TextAreaField('about_me', validators = [Length(min = 0, max = 140)])
nickname = StringField('nickname', validators=[DataRequired()])
about_me = TextAreaField('about_me', validators=[Length(min=0, max=140)])
def __init__(self, original_nickname, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
@ -22,16 +24,22 @@ class EditForm(Form):
if self.nickname.data == self.original_nickname:
return True
if self.nickname.data != User.make_valid_nickname(self.nickname.data):
self.nickname.errors.append(gettext('This nickname has invalid characters. Please use letters, numbers, dots and underscores only.'))
self.nickname.errors.append(gettext(
'This nickname has invalid characters. '
'Please use letters, numbers, dots and underscores only.'))
return False
user = User.query.filter_by(nickname = self.nickname.data).first()
if user != None:
self.nickname.errors.append(gettext('This nickname is already in use. Please choose another one.'))
user = User.query.filter_by(nickname=self.nickname.data).first()
if user is not None:
self.nickname.errors.append(gettext(
'This nickname is already in use. '
'Please choose another one.'))
return False
return True
class PostForm(Form):
post = TextField('post', validators = [Required()])
post = StringField('post', validators=[DataRequired()])
class SearchForm(Form):
search = TextField('search', validators = [Required()])
search = StringField('search', validators=[DataRequired()])

View File

@ -1,31 +1,38 @@
from hashlib import md5
import re
from app import db
from app import app
from config import WHOOSH_ENABLED
import re
ROLE_USER = 0
ROLE_ADMIN = 1
import sys
if sys.version_info >= (3, 0):
enable_search = False
else:
enable_search = WHOOSH_ENABLED
if enable_search:
import flask.ext.whooshalchemy as whooshalchemy
followers = db.Table('followers',
followers = db.Table(
'followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
nickname = db.Column(db.String(64), unique = True)
email = db.Column(db.String(120), index = True, unique = True)
role = db.Column(db.SmallInteger, default = ROLE_USER)
posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
followed = db.relationship('User',
secondary = followers,
primaryjoin = (followers.c.follower_id == id),
secondaryjoin = (followers.c.followed_id == id),
backref = db.backref('followers', lazy = 'dynamic'),
lazy = 'dynamic')
secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic')
@staticmethod
def make_valid_nickname(nickname):
@ -33,12 +40,12 @@ class User(db.Model):
@staticmethod
def make_unique_nickname(nickname):
if User.query.filter_by(nickname = nickname).first() == None:
if User.query.filter_by(nickname=nickname).first() is None:
return nickname
version = 2
while True:
new_nickname = nickname + str(version)
if User.query.filter_by(nickname = new_nickname).first() == None:
if User.query.filter_by(nickname=new_nickname).first() is None:
break
version += 1
return new_nickname
@ -53,10 +60,14 @@ class User(db.Model):
return False
def get_id(self):
return unicode(self.id)
try:
return unicode(self.id) # python 2
except NameError:
return str(self.id) # python 3
def avatar(self, size):
return 'http://www.gravatar.com/avatar/' + md5(self.email).hexdigest() + '?d=mm&s=' + str(size)
return 'http://www.gravatar.com/avatar/%s?d=mm&s=%d' % \
(md5(self.email.encode('utf-8')).hexdigest(), size)
def follow(self, user):
if not self.is_following(user):
@ -69,26 +80,31 @@ class User(db.Model):
return self
def is_following(self, user):
return self.followed.filter(followers.c.followed_id == user.id).count() > 0
return self.followed.filter(
followers.c.followed_id == user.id).count() > 0
def followed_posts(self):
return Post.query.join(followers, (followers.c.followed_id == Post.user_id)).filter(followers.c.follower_id == self.id).order_by(Post.timestamp.desc())
return Post.query.join(
followers, (followers.c.followed_id == Post.user_id)).filter(
followers.c.follower_id == self.id).order_by(
Post.timestamp.desc())
def __repr__(self): # pragma: no cover
def __repr__(self): # pragma: no cover
return '<User %r>' % (self.nickname)
class Post(db.Model):
__searchable__ = ['body']
id = db.Column(db.Integer, primary_key = True)
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
language = db.Column(db.String(5))
def __repr__(self): # pragma: no cover
def __repr__(self): # pragma: no cover
return '<Post %r>' % (self.body)
if WHOOSH_ENABLED:
import flask.ext.whooshalchemy as whooshalchemy
if enable_search:
whooshalchemy.whoosh_index(app, Post)

View File

@ -1,11 +1,14 @@
from jinja2 import Markup
class momentjs(object):
def __init__(self, timestamp):
self.timestamp = timestamp
def render(self, format):
return Markup("<script>\ndocument.write(moment(\"%s\").%s);\n</script>" % (self.timestamp.strftime("%Y-%m-%dT%H:%M:%S Z"), format))
return Markup(
"<script>\ndocument.write(moment(\"%s\").%s);\n</script>" %
(self.timestamp.strftime("%Y-%m-%dT%H:%M:%S Z"), format))
def format(self, fmt):
return self.render("format(\"%s\")" % fmt)
@ -15,4 +18,3 @@ class momentjs(object):
def fromNow(self):
return self.render("fromNow()")

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,6 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ _('File Not Found') }}</h1>
<p><a href="{{url_for('index')}}">{{ _('Back') }}</a></p>
<h1>{{ _('File Not Found') }}</h1>
<p><a href="{{ url_for('index') }}">{{ _('Back') }}</a></p>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ _('An unexpected error has occurred') }}</h1>
<p>{{ _('The administrator has been notified. Sorry for the inconvenience!') }}</p>
<p><a href="{{url_for('index')}}">{{ _('Back') }}</a></p>
<h1>{{ _('An unexpected error has occurred') }}</h1>
<p>{{ _('The administrator has been notified. Sorry for the inconvenience!') }}</p>
<p><a href="{{ url_for('index') }}">{{ _('Back') }}</a></p>
{% endblock %}

View File

@ -2,17 +2,17 @@
<html>
<head>
{% if title %}
<title>{{title}} - microblog</title>
<title>{{ title }} - microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
<link href="{{ url_for('.static', filename = 'css/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('.static', filename = 'css/bootstrap-responsive.min.css') }}" rel="stylesheet">
<link href="{{ url_for('.static', filename='css/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('.static', filename='css/bootstrap-responsive.min.css') }}" rel="stylesheet">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ url_for('.static', filename = 'js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('.static', filename = 'js/moment.min.js') }}"></script>
<script src="{{ url_for('.static', filename='js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('.static', filename='js/moment.min.js') }}"></script>
{% if g.locale != 'en' %}
<script src="{{ url_for('.static', filename = 'js/moment-' + g.locale + '.min.js') }}"></script>
<script src="{{ url_for('.static', filename='js/moment-' + g.locale + '.min.js') }}"></script>
{% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
@ -48,13 +48,13 @@
<ul class="nav">
<li><a href="{{ url_for('index') }}">{{ _('Home') }}</a></li>
{% if g.user.is_authenticated() %}
<li><a href="{{ url_for('user', nickname = g.user.nickname) }}">{{ _('Your Profile') }}</a></li>
<li><a href="{{ url_for('user', nickname=g.user.nickname) }}">{{ _('Your Profile') }}</a></li>
<li><a href="{{ url_for('logout') }}">{{ _('Logout') }}</a></li>
{% endif %}
</ul>
<div class="nav-collapse collapse">
{% if g.user.is_authenticated() and g.search_enabled %}
<form class="navbar-search pull-right" action="{{url_for('search')}}" method="post" name="search">{{g.search_form.hidden_tag()}}{{g.search_form.search(size=20,placeholder=_('Search'),class="search-query")}}</form>
{% if g.user.is_authenticated() %}
<form class="navbar-search pull-right" action="{{ url_for('search') }}" method="post" name="search">{{ g.search_form.hidden_tag() }}{{ g.search_form.search(size=20, placeholder=_('Search'), class="search-query")}}</form>
{% endif %}
</div>
</div>

View File

@ -2,35 +2,34 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ _('Edit Your Profile') }}</h1>
{% include 'flash.html' %}
<div class="well">
<form class="form-horizontal" action="" method="post" name="edit">
{{form.hidden_tag()}}
<div class="control-group{% if form.errors.post %} error{% endif %}">
<label class="control-label" for="nickname">{{ _('Your nickname:') }}</label>
<div class="controls">
{{ form.nickname(maxlength = 64, class = "span4") }}
{% for error in form.errors.nickname %}
<span class="help-inline">[{{error}}]</span><br>
{% endfor %}
<h1>{{ _('Edit Your Profile') }}</h1>
{% include 'flash.html' %}
<div class="well">
<form class="form-horizontal" action="" method="post" name="edit">
{{ form.hidden_tag() }}
<div class="control-group{% if form.nickname.errors %} error{% endif %}">
<label class="control-label" for="nickname">{{ _('Your nickname:') }}</label>
<div class="controls">
{{ form.nickname(maxlength=64, class="span4") }}
{% for error in form.nickname.errors %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
</div>
</div>
<div class="control-group{% if form.errors.post %} error{% endif %}">
<label class="control-label" for="about_me">{{ _('About yourself:') }}</label>
<div class="controls">
{{ form.about_me(cols = 64, rows = 4, class = "span4") }}
{% for error in form.errors.about_me %}
<span class="help-inline">[{{error}}]</span><br>
{% endfor %}
<div class="control-group{% if form.about_me.errors %} error{% endif %}">
<label class="control-label" for="about_me">{{ _('About yourself:') }}</label>
<div class="controls">
{{ form.about_me(cols=64, rows=4, class="span4") }}
{% for error in form.about_me.errors %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="{{ _('Save Changes') }}">
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="{{ _('Save Changes') }}">
</div>
</div>
</div>
</form>
</div>
</form>
</form>
</div>
{% endblock %}

View File

@ -1,11 +1,11 @@
<p>Dear {{user.nickname}},</p>
<p>{{ _('%(nickname)s is now a follower.', nickname = '<a href="' + url_for("user", nickname = follower.nickname, _external = True) + '">' + follower.nickname + '</a>') }}</p>
<p>Dear {{ user.nickname }},</p>
<p>{{ _('%(nickname)s is now a follower.', nickname='<a href="' + url_for("user", nickname=follower.nickname, _external=True) + '">' + follower.nickname + '</a>') }}</p>
<table>
<tr valign="top">
<td><img src="{{follower.avatar(50)}}"></td>
<td><img src="{{ follower.avatar(50) }}"></td>
<td>
<a href="{{url_for('user', nickname = follower.nickname, _external = True)}}">{{follower.nickname}}</a><br />
{{follower.about_me}}
<a href="{{ url_for('user', nickname=follower.nickname, _external=True)}}">{{ follower.nickname }}</a><br />
{{ follower.about_me }}
</td>
</tr>
</table>

View File

@ -1,8 +1,8 @@
{{ _('Dear %(nickname)s,', nickname = user.nickname) }}
{{ _('Dear %(nickname)s,', nickname=user.nickname) }}
{{ _('%(nickname)s is now a follower. Click on the following link to visit %(nickname)s\'s profile page:', nickname = follower.nickname) }}
{{ _('%(nickname)s is now a follower. Click on the following link to visit %(nickname)s\'s profile page:', nickname=follower.nickname) }}
{{url_for("user", nickname = follower.nickname, _external = True)}}
{{ url_for("user", nickname=follower.nickname, _external=True) }}
{{ _('Regards,') }}

View File

@ -2,40 +2,40 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ _('Hi, %(nickname)s!', nickname = g.user.nickname) }}</h1>
{% include 'flash.html' %}
<div class="well">
<form class="form-horizontal" action="" method="post" name="post">
{{form.hidden_tag()}}
<div class="control-group{% if form.errors.post %} error{% endif %}">
<label class="control-label" for="post">{{ _('Say something:') }}</label>
<div class="controls">
{{ form.post(size = 30, maxlength = 140) }}
{% for error in form.errors.post %}
<span class="help-inline">[{{error}}]</span><br>
{% endfor %}
<h1>{{ _('Hi, %(nickname)s!', nickname=g.user.nickname) }}</h1>
{% include 'flash.html' %}
<div class="well">
<form class="form-horizontal" action="" method="post" name="post">
{{ form.hidden_tag() }}
<div class="control-group{% if form.post.errors %} error{% endif %}">
<label class="control-label" for="post">{{ _('Say something:') }}</label>
<div class="controls">
{{ form.post(size=30, maxlength=140) }}
{% for error in form.post.errors %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="{{ _('Post!') }}">
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="{{ _('Post!') }}">
</div>
</div>
</div>
</form>
</div>
{% for post in posts.items %}
{% include 'post.html' %}
{% endfor %}
<ul class="pager">
{% if posts.has_prev %}
<li class="previous"><a href="{{ url_for('index', page = posts.prev_num) }}">{{ _('Newer posts') }}</a></li>
{% else %}
<li class="previous disabled"><a href="#">{{ _('Newer posts') }}</a></li>
{% endif %}
{% if posts.has_next %}
<li class="next"><a href="{{ url_for('index', page = posts.next_num) }}">{{ _('Older posts') }}</a></li>
{% else %}
<li class="next disabled"><a href="#">{{ _('Older posts') }}</a></li>
{% endif %}
</ul>
</form>
</div>
{% for post in posts.items %}
{% include 'post.html' %}
{% endfor %}
<ul class="pager">
{% if posts.has_prev %}
<li class="previous"><a href="{{ url_for('index', page=posts.prev_num) }}">{{ _('Newer posts') }}</a></li>
{% else %}
<li class="previous disabled"><a href="#">{{ _('Newer posts') }}</a></li>
{% endif %}
{% if posts.has_next %}
<li class="next"><a href="{{ url_for('index', page=posts.next_num) }}">{{ _('Older posts') }}</a></li>
{% else %}
<li class="next disabled"><a href="#">{{ _('Older posts') }}</a></li>
{% endif %}
</ul>
{% endblock %}

View File

@ -2,51 +2,51 @@
{% extends "base.html" %}
{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
u = openid.search('<username>');
if (u != -1) {
// openid requires username
user = prompt('Enter your ' + pr + ' username:');
openid = openid.substr(0, u) + user;
<script type="text/javascript">
function set_openid(openid, pr)
{
u = openid.search('<username>');
if (u != -1) {
// openid requires username
user = prompt('Enter your ' + pr + ' username:');
openid = openid.substr(0, u) + user;
}
form = document.forms['login'];
form.elements['openid'].value = openid;
}
form = document.forms['login'];
form.elements['openid'].value = openid;
}
</script>
{% include 'flash.html' %}
<div class="well">
<h3>{{ _('Please Sign In') }}</h3>
<form class="form" action="" method="post" name="login">
{{form.hidden_tag()}}
<div class="help-block">{{ _('Click on your OpenID provider below:') }}</div>
<div class="control-group">
{% for pr in providers %}
<a href="javascript:set_openid('{{pr.url}}', '{{pr.name}}');"><img src="/static/img/{{pr.name.lower()}}.png" class="img-polaroid" style="margin:2px;" /></a>
{% endfor %}
</div>
<div class="control-group{% if form.errors.openid %} error{% endif %}">
<label class="control-label" for="openid">{{ _('Or enter your OpenID here:') }}</label>
<div class="controls">
{{ form.openid(size = 80, class = "span4") }}
{% for error in form.errors.openid %}
<span class="help-inline">[{{error}}]</span><br>
</script>
{% include 'flash.html' %}
<div class="well">
<h3>{{ _('Please Sign In') }}</h3>
<form class="form" action="" method="post" name="login">
{{ form.hidden_tag() }}
<div class="help-block">{{ _('Click on your OpenID provider below:') }}</div>
<div class="control-group">
{% for pr in providers %}
<a href="javascript:set_openid('{{ pr.url }}', '{{ pr.name }}');"><img src="/static/img/{{ pr.name.lower() }}.png" class="img-polaroid" style="margin:2px;" /></a>
{% endfor %}
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox" for="remember_me">
{{ form.remember_me }} {{ _('Remember Me') }}
</label>
<div class="control-group{% if form.openid.errors %} error{% endif %}">
<label class="control-label" for="openid">{{ _('Or enter your OpenID here:') }}</label>
<div class="controls">
{{ form.openid(size=80, class="span4") }}
{% for error in form.openid.errors %}
<span class="help-inline">[{{ error }}]</span><br>
{% endfor %}
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="{{ _('Sign In') }}">
<div class="control-group">
<div class="controls">
<label class="checkbox" for="remember_me">
{{ form.remember_me }} {{ _('Remember Me') }}
</label>
</div>
</div>
</div>
</form>
</div>
<div class="control-group">
<div class="controls">
<input class="btn btn-primary" type="submit" value="{{ _('Sign In') }}">
</div>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,17 +1,17 @@
<table class="table table-hover">
<tr>
<td width="70px"><a href="{{url_for('user', nickname = post.author.nickname)}}"><img src="{{post.author.avatar(70)}}" /></a></td>
<td width="70px"><a href="{{ url_for('user', nickname=post.author.nickname) }}"><img src="{{ post.author.avatar(70) }}" /></a></td>
<td>
{% autoescape false %}
<p>{{ _('%(nickname)s said %(when)s:', nickname = '<a href="%s">%s</a>' % (url_for('user', nickname = post.author.nickname), post.author.nickname), when = momentjs(post.timestamp).fromNow()) }}</p>
<p>{{ _('%(nickname)s said %(when)s:', nickname='<a href="%s">%s</a>' % (url_for('user', nickname=post.author.nickname), post.author.nickname), when=momentjs(post.timestamp).fromNow()) }}</p>
{% endautoescape %}
<p><strong><span id="post{{post.id}}">{{post.body}}</span></strong></p>
<p><strong><span id="post{{ post.id }}">{{ post.body }}</span></strong></p>
{% if post.language != None and post.language != '' and post.language != g.locale %}
<div>
<span id="translation{{post.id}}">
<a href="javascript:translate('{{post.language}}', '{{g.locale}}', '#post{{post.id}}', '#translation{{post.id}}', '#loading{{post.id}}');">{{ _('Translate') }}</a>
<span id="translation{{ post.id }}">
<a href="javascript:translate('{{ post.language }}', '{{ g.locale }}', '#post{{ post.id }}', '#translation{{ post.id }}', '#loading{{ post.id }}');">{{ _('Translate') }}</a>
</span>
<img id="loading{{post.id}}" style="display: none" src="/static/img/loading.gif">
<img id="loading{{ post.id }}" style="display: none" src="/static/img/loading.gif">
</div>
{% endif %}
{% if post.author.id == g.user.id %}

View File

@ -2,39 +2,39 @@
{% extends "base.html" %}
{% block content %}
{% include 'flash.html' %}
<div class="well well-large" style="height: 140px;">
<div class="pull-right">
<img src="{{user.avatar(128)}}" class="img-polaroid">
{% include 'flash.html' %}
<div class="well well-large" style="height: 140px;">
<div class="pull-right">
<img src="{{ user.avatar(128) }}" class="img-polaroid">
</div>
<h1>{{ user.nickname }}</h1>
{% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
{% if user.last_seen %}
<p><em>{{ _('Last seen:') }} {{ momentjs(user.last_seen).calendar() }}</em></p>
{% endif %}
<p>{{ _('Followers:') }} {{ user.followers.count() - 1 }} | {{ _('Following:') }} {{ user.followed.count() - 1 }} |
{% if user.id == g.user.id %}
<a href="{{ url_for('edit') }}">{{ _('Edit your profile') }}</a>
{% elif not g.user.is_following(user) %}
<a href="{{ url_for('follow', nickname=user.nickname) }}">{{ _('Follow') }}</a>
{% else %}
<a href="{{ url_for('unfollow', nickname=user.nickname) }}">{{ _('Unfollow') }}</a>
{% endif %}
</p>
</div>
<h1>{{user.nickname}}</h1>
{% if user.about_me %}<p>{{user.about_me}}</p>{% endif %}
{% if user.last_seen %}
<p><em>{{ _('Last seen:') }} {{ momentjs(user.last_seen).calendar() }}</em></p>
{% endif %}
<p>{{ _('Followers:') }} {{user.followers.count() - 1}} | {{ _('Following:') }} {{user.followed.count() - 1}} |
{% if user.id == g.user.id %}
<a href="{{url_for('edit')}}">{{ _('Edit your profile') }}</a>
{% elif not g.user.is_following(user) %}
<a href="{{url_for('follow', nickname = user.nickname)}}">{{ _('Follow') }}</a>
{% else %}
<a href="{{url_for('unfollow', nickname = user.nickname)}}">{{ _('Unfollow') }}</a>
{% endif %}
</p>
</div>
{% for post in posts.items %}
{% include 'post.html' %}
{% endfor %}
<ul class="pager">
{% if posts.has_prev %}
<li class="previous"><a href="{{ url_for('user', nickname = user.nickname, page = posts.prev_num) }}">{{ _('Newer posts') }}</a></li>
{% else %}
<li class="previous disabled"><a href="#">{{ _('Newer posts') }}</a></li>
{% endif %}
{% if posts.has_next %}
<li class="next"><a href="{{ url_for('user', nickname = user.nickname, page = posts.next_num) }}">{{ _('Older posts') }}</a></li>
{% else %}
<li class="next disabled"><a href="#">{{ _('Older posts') }}</a></li>
{% endif %}
</ul>
{% for post in posts.items %}
{% include 'post.html' %}
{% endfor %}
<ul class="pager">
{% if posts.has_prev %}
<li class="previous"><a href="{{ url_for('user', nickname=user.nickname, page=posts.prev_num) }}">{{ _('Newer posts') }}</a></li>
{% else %}
<li class="previous disabled"><a href="#">{{ _('Newer posts') }}</a></li>
{% endif %}
{% if posts.has_next %}
<li class="next"><a href="{{ url_for('user', nickname=user.nickname, page=posts.next_num) }}">{{ _('Older posts') }}</a></li>
{% else %}
<li class="next disabled"><a href="#">{{ _('Older posts') }}</a></li>
{% endif %}
</ul>
{% endblock %}

View File

@ -1,15 +1,23 @@
import urllib, httplib
try:
import httplib # Python 2
except ImportError:
import http.client as httplib # Python 3
try:
from urllib import urlencode # Python 2
except ImportError:
from urllib.parse import urlencode # Python 3
import json
from app import app
from flask.ext.babel import gettext
from config import MS_TRANSLATOR_CLIENT_ID, MS_TRANSLATOR_CLIENT_SECRET
def microsoft_translate(text, sourceLang, destLang):
if MS_TRANSLATOR_CLIENT_ID == "" or MS_TRANSLATOR_CLIENT_SECRET == "":
return gettext('Error: translation service not configured.')
try:
# get access token
params = urllib.urlencode({
params = urlencode({
'client_id': MS_TRANSLATOR_CLIENT_ID,
'client_secret': MS_TRANSLATOR_CLIENT_SECRET,
'scope': 'http://api.microsofttranslator.com',
@ -17,39 +25,40 @@ def microsoft_translate(text, sourceLang, destLang):
})
conn = httplib.HTTPSConnection("datamarket.accesscontrol.windows.net")
conn.request("POST", "/v2/OAuth2-13", params)
response = json.loads (conn.getresponse().read())
response = json.loads(conn.getresponse().read())
token = response[u'access_token']
# translate
conn = httplib.HTTPConnection('api.microsofttranslator.com')
params = {
'appId': 'Bearer ' + token,
'from': sourceLang,
'to': destLang,
'text': text.encode("utf-8")
}
conn.request("GET", '/V2/Ajax.svc/Translate?' + urllib.urlencode(params))
response = json.loads("{\"response\":" + conn.getresponse().read().decode('utf-8-sig') + "}")
params = {'appId': 'Bearer ' + token,
'from': sourceLang,
'to': destLang,
'text': text.encode("utf-8")}
conn.request("GET",
'/V2/Ajax.svc/Translate?' + urlencode(params))
response = json.loads('{"response":' +
conn.getresponse().read().decode('utf-8') + '}')
return response["response"]
except:
#return gettext('Error: Unexpected error.')
raise
def google_translate(text, sourceLang, destLang):
if not app.debug:
return gettext('Error: translation service not available.')
try:
params = urllib.urlencode({
params = urlencode({
'client': 't',
'text': text.encode("utf-8"),
'sl': sourceLang,
'tl': destLang,
'ie': 'UTF-8',
'oe': 'UTF-8'
})
'oe': 'UTF-8'})
conn = httplib.HTTPSConnection("translate.google.com")
conn.request("GET", "/translate_a/t?" + params, headers = { 'User-Agent': 'Mozilla/5.0' })
httpresponse = conn.getresponse().read().replace(",,,", ",\"\",\"\",").replace(",,", ",\"\",")
conn.request("GET", "/translate_a/t?" + params,
headers={'User-Agent': 'Mozilla/5.0'})
httpresponse = conn.getresponse().read().replace(
",,,", ",\"\",\"\",").replace(",,", ",\"\",")
response = json.loads("{\"response\":" + httpresponse + "}")
return response["response"][0][0][0]
except:

Binary file not shown.

View File

@ -1,24 +1,30 @@
from flask import render_template, flash, redirect, session, url_for, request, g, jsonify
from flask.ext.login import login_user, logout_user, current_user, login_required
from flask import render_template, flash, redirect, session, url_for, request, \
g, jsonify
from flask.ext.login import login_user, logout_user, current_user, \
login_required
from flask.ext.sqlalchemy import get_debug_queries
from flask.ext.babel import gettext
from app import app, db, lm, oid, babel
from forms import LoginForm, EditForm, PostForm, SearchForm
from models import User, ROLE_USER, ROLE_ADMIN, Post
from datetime import datetime
from emails import follower_notification
from guess_language import guessLanguage
from translate import microsoft_translate
from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS, LANGUAGES, DATABASE_QUERY_TIMEOUT, WHOOSH_ENABLED
from app import app, db, lm, oid, babel
from .forms import LoginForm, EditForm, PostForm, SearchForm
from .models import User, Post
from .emails import follower_notification
from .translate import microsoft_translate
from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS, LANGUAGES, \
DATABASE_QUERY_TIMEOUT
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@babel.localeselector
def get_locale():
return request.accept_languages.best_match(LANGUAGES.keys())
@app.before_request
def before_request():
g.user = current_user
@ -28,49 +34,54 @@ def before_request():
db.session.commit()
g.search_form = SearchForm()
g.locale = get_locale()
g.search_enabled = WHOOSH_ENABLED
@app.after_request
def after_request(response):
for query in get_debug_queries():
if query.duration >= DATABASE_QUERY_TIMEOUT:
app.logger.warning("SLOW QUERY: %s\nParameters: %s\nDuration: %fs\nContext: %s\n" % (query.statement, query.parameters, query.duration, query.context))
app.logger.warning(
"SLOW QUERY: %s\nParameters: %s\nDuration: %fs\nContext: %s\n" %
(query.statement, query.parameters, query.duration,
query.context))
return response
@app.errorhandler(404)
def internal_error(error):
def not_found_error(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500
@app.route('/', methods = ['GET', 'POST'])
@app.route('/index', methods = ['GET', 'POST'])
@app.route('/index/<int:page>', methods = ['GET', 'POST'])
@app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
@app.route('/index/<int:page>', methods=['GET', 'POST'])
@login_required
def index(page = 1):
def index(page=1):
form = PostForm()
if form.validate_on_submit():
language = guessLanguage(form.post.data)
if language == 'UNKNOWN' or len(language) > 5:
language = ''
post = Post(body = form.post.data,
timestamp = datetime.utcnow(),
author = g.user,
language = language)
post = Post(body=form.post.data, timestamp=datetime.utcnow(),
author=g.user, language=language)
db.session.add(post)
db.session.commit()
flash(gettext('Your post is now live!'))
return redirect(url_for('index'))
posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False)
return render_template('index.html',
title = 'Home',
form = form,
posts = posts)
title='Home',
form=form,
posts=posts)
@app.route('/login', methods = ['GET', 'POST'])
@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
if g.user is not None and g.user.is_authenticated():
@ -78,25 +89,26 @@ def login():
form = LoginForm()
if form.validate_on_submit():
session['remember_me'] = form.remember_me.data
return oid.try_login(form.openid.data, ask_for = ['nickname', 'email'])
return oid.try_login(form.openid.data, ask_for=['nickname', 'email'])
return render_template('login.html',
title = 'Sign In',
form = form,
providers = app.config['OPENID_PROVIDERS'])
title='Sign In',
form=form,
providers=app.config['OPENID_PROVIDERS'])
@oid.after_login
def after_login(resp):
if resp.email is None or resp.email == "":
flash(gettext('Invalid login. Please try again.'))
return redirect(url_for('login'))
user = User.query.filter_by(email = resp.email).first()
user = User.query.filter_by(email=resp.email).first()
if user is None:
nickname = resp.nickname
if nickname is None or nickname == "":
nickname = resp.email.split('@')[0]
nickname = User.make_valid_nickname(nickname)
nickname = User.make_unique_nickname(nickname)
user = User(nickname = nickname, email = resp.email, role = ROLE_USER)
user = User(nickname=nickname, email=resp.email)
db.session.add(user)
db.session.commit()
# make the user follow him/herself
@ -106,28 +118,31 @@ def after_login(resp):
if 'remember_me' in session:
remember_me = session['remember_me']
session.pop('remember_me', None)
login_user(user, remember = remember_me)
login_user(user, remember=remember_me)
return redirect(request.args.get('next') or url_for('index'))
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/user/<nickname>')
@app.route('/user/<nickname>/<int:page>')
@login_required
def user(nickname, page = 1):
user = User.query.filter_by(nickname = nickname).first()
if user == None:
flash(gettext('User %(nickname)s not found.', nickname = nickname))
def user(nickname, page=1):
user = User.query.filter_by(nickname=nickname).first()
if user is None:
flash(gettext('User %(nickname)s not found.', nickname=nickname))
return redirect(url_for('index'))
posts = user.posts.paginate(page, POSTS_PER_PAGE, False)
return render_template('user.html',
user = user,
posts = posts)
user=user,
posts=posts)
@app.route('/edit', methods = ['GET', 'POST'])
@app.route('/edit', methods=['GET', 'POST'])
@login_required
def edit():
form = EditForm(g.user.nickname)
@ -141,53 +156,56 @@ def edit():
elif request.method != "POST":
form.nickname.data = g.user.nickname
form.about_me.data = g.user.about_me
return render_template('edit.html',
form = form)
return render_template('edit.html', form=form)
@app.route('/follow/<nickname>')
@login_required
def follow(nickname):
user = User.query.filter_by(nickname = nickname).first()
if user == None:
flash('User ' + nickname + ' not found.')
user = User.query.filter_by(nickname=nickname).first()
if user is None:
flash('User %s not found.' % nickname)
return redirect(url_for('index'))
if user == g.user:
flash(gettext('You can\'t follow yourself!'))
return redirect(url_for('user', nickname = nickname))
return redirect(url_for('user', nickname=nickname))
u = g.user.follow(user)
if u is None:
flash(gettext('Cannot follow %(nickname)s.', nickname = nickname))
return redirect(url_for('user', nickname = nickname))
flash(gettext('Cannot follow %(nickname)s.', nickname=nickname))
return redirect(url_for('user', nickname=nickname))
db.session.add(u)
db.session.commit()
flash(gettext('You are now following %(nickname)s!', nickname = nickname))
flash(gettext('You are now following %(nickname)s!', nickname=nickname))
follower_notification(user, g.user)
return redirect(url_for('user', nickname = nickname))
return redirect(url_for('user', nickname=nickname))
@app.route('/unfollow/<nickname>')
@login_required
def unfollow(nickname):
user = User.query.filter_by(nickname = nickname).first()
if user == None:
flash('User ' + nickname + ' not found.')
user = User.query.filter_by(nickname=nickname).first()
if user is None:
flash('User %s not found.' % nickname)
return redirect(url_for('index'))
if user == g.user:
flash(gettext('You can\'t unfollow yourself!'))
return redirect(url_for('user', nickname = nickname))
return redirect(url_for('user', nickname=nickname))
u = g.user.unfollow(user)
if u is None:
flash(gettext('Cannot unfollow %(nickname)s.', nickname = nickname))
return redirect(url_for('user', nickname = nickname))
flash(gettext('Cannot unfollow %(nickname)s.', nickname=nickname))
return redirect(url_for('user', nickname=nickname))
db.session.add(u)
db.session.commit()
flash(gettext('You have stopped following %(nickname)s.', nickname = nickname))
return redirect(url_for('user', nickname = nickname))
flash(gettext('You have stopped following %(nickname)s.',
nickname=nickname))
return redirect(url_for('user', nickname=nickname))
@app.route('/delete/<int:id>')
@login_required
def delete(id):
post = Post.query.get(id)
if post == None:
if post is None:
flash('Post not found.')
return redirect(url_for('index'))
if post.author.id != g.user.id:
@ -198,27 +216,29 @@ def delete(id):
flash('Your post has been deleted.')
return redirect(url_for('index'))
@app.route('/search', methods = ['POST'])
@app.route('/search', methods=['POST'])
@login_required
def search():
if not g.search_form.validate_on_submit():
return redirect(url_for('index'))
return redirect(url_for('search_results', query = g.search_form.search.data))
return redirect(url_for('search_results', query=g.search_form.search.data))
@app.route('/search_results/<query>')
@login_required
def search_results(query):
results = Post.query.whoosh_search(query, MAX_SEARCH_RESULTS).all()
return render_template('search_results.html',
query = query,
results = results)
query=query,
results=results)
@app.route('/translate', methods = ['POST'])
@app.route('/translate', methods=['POST'])
@login_required
def translate():
return jsonify({
'text': microsoft_translate(
request.form['text'],
request.form['sourceLang'],
request.form['destLang']) })
request.form['destLang'])})

View File

@ -6,14 +6,15 @@ CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
OPENID_PROVIDERS = [
{ 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' },
{ 'name': 'Yahoo', 'url': 'https://me.yahoo.com' },
{ 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' },
{ 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' },
{ 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]
{'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id'},
{'name': 'Yahoo', 'url': 'https://me.yahoo.com'},
{'name': 'AOL', 'url': 'http://openid.aol.com/<username>'},
{'name': 'Flickr', 'url': 'http://www.flickr.com/<username>'},
{'name': 'MyOpenID', 'url': 'https://www.myopenid.com'}]
if os.environ.get('DATABASE_URL') is None:
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') + '?check_same_thread=False'
SQLALCHEMY_DATABASE_URI = ('sqlite:///' + os.path.join(basedir, 'app.db') +
'?check_same_thread=False')
else:
SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
@ -27,12 +28,12 @@ WHOOSH_ENABLED = os.environ.get('HEROKU') is None
DATABASE_QUERY_TIMEOUT = 0.5
# email server
MAIL_SERVER = '' # your mailserver
MAIL_SERVER = '' # your mailserver
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
MAIL_USERNAME = 'you'
MAIL_PASSWORD = 'your-password'
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
# available languages
LANGUAGES = {
@ -41,8 +42,8 @@ LANGUAGES = {
}
# microsoft translation service
MS_TRANSLATOR_CLIENT_ID = '' # enter your MS translator app id here
MS_TRANSLATOR_CLIENT_SECRET = '' # enter your MS translator app secret here
MS_TRANSLATOR_CLIENT_ID = '' # enter your MS translator app id here
MS_TRANSLATOR_CLIENT_SECRET = '' # enter your MS translator app secret here
# administrator list
ADMINS = ['you@example.com']

View File

@ -9,4 +9,5 @@ if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))
api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO,
api.version(SQLALCHEMY_MIGRATE_REPO))

View File

@ -4,4 +4,5 @@ from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1)
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('Current database version: ' + str(v))

View File

@ -4,12 +4,16 @@ from migrate.versioning import api
from app import db
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
migration = SQLALCHEMY_MIGRATE_REPO + ('/versions/%03d_migration.py' % (v+1))
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
exec old_model in tmp_module.__dict__
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI,
SQLALCHEMY_MIGRATE_REPO,
tmp_module.meta, db.metadata)
open(migration, "wt").write(script)
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'New migration saved as ' + migration
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('New migration saved as ' + migration)
print('Current database version: ' + str(v))

View File

@ -9,7 +9,6 @@ user = Table('user', post_meta,
Column('id', Integer, primary_key=True, nullable=False),
Column('nickname', String(length=64)),
Column('email', String(length=120)),
Column('role', SmallInteger, default=ColumnDefault(0)),
)

View File

@ -9,7 +9,6 @@ user = Table('user', post_meta,
Column('id', Integer, primary_key=True, nullable=False),
Column('nickname', String(length=64)),
Column('email', String(length=120)),
Column('role', SmallInteger, default=ColumnDefault(0)),
Column('about_me', String(length=140)),
Column('last_seen', DateTime),
)

View File

@ -3,4 +3,5 @@ from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('Current database version: ' + str(v))

View File

@ -3,6 +3,5 @@ from werkzeug.contrib.profiler import ProfilerMiddleware
from app import app
app.config['PROFILE'] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions = [30])
app.run(debug = True)
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(debug=True)

View File

@ -1,28 +1,31 @@
Babel==0.9.6
Flask==0.9
Flask-Babel==0.8
Flask-Login==0.1.3
Flask-Mail==0.8.2
Flask-OpenID==1.1.1
Flask-SQLAlchemy==0.16
Flask-WTF==0.8.3
git+git://github.com/miguelgrinberg/Flask-WhooshAlchemy
Jinja2==2.6
MySQL-python==1.2.4
SQLAlchemy==0.7.9
Tempita==0.5.1
WTForms==1.0.3
Werkzeug==0.8.3
Whoosh==2.4.1
blinker==1.2
coverage==3.6
Babel==1.3
Flask==0.10.1
Flask-Babel==0.9
Flask-Login==0.2.11
Flask-Mail==0.9.0
Flask-OpenID==1.2.1
Flask-SQLAlchemy==2.0
Flask-WTF==0.10.2
Flask-WhooshAlchemy==0.56
Jinja2==2.7.3
MarkupSafe==0.23
SQLAlchemy==0.9.7
Tempita==0.5.2
WTForms==2.0.1
Werkzeug==0.9.6
Whoosh==2.6.0
blinker==1.3
coverage==3.7.1
decorator==3.4.0
flup==1.0.3.dev-20110405
flipflop==1.0
guess-language==0.2
gunicorn==0.17.2
psycopg2==2.5
gunicorn==19.1.1
itsdangerous==0.24
pbr==0.10.0
psycopg2==2.5.4
python-openid==2.2.5
pytz==2013b
pytz==2014.7
six==1.8.0
speaklater==1.3
sqlalchemy-migrate==0.7.2
sqlalchemy-migrate==0.9.2
sqlparse==0.1.11

2
run.py
View File

@ -1,3 +1,3 @@
#!flask/bin/python
from app import app
app.run(debug = True)
app.run(debug=True)

View File

@ -1,2 +0,0 @@
#!flask/bin/python
from app import app

View File

@ -1,9 +1,10 @@
#!flask/bin/python
import os
# use mysql
os.environ['DATABASE_URL'] = 'mysql://apps:apps@localhost/apps'
from flup.server.fcgi import WSGIServer
from flipflop import WSGIServer
from app import app
if __name__ == '__main__':

View File

@ -1,5 +1,5 @@
#!flask/bin/python
from flup.server.fcgi import WSGIServer
from flipflop import WSGIServer
from app import app
if __name__ == '__main__':

View File

@ -1,24 +0,0 @@
#!/usr/bin/python
import os, subprocess, sys
subprocess.call(['python', 'virtualenv.py', 'flask'])
if sys.platform == 'win32':
bin = 'Scripts'
else:
bin = 'bin'
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask<0.10'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-login'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-openid'])
if sys.platform == 'win32':
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', '--no-deps', 'lamson', 'chardet', 'flask-mail'])
else:
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-mail'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'sqlalchemy==0.7.9'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-sqlalchemy'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'sqlalchemy-migrate'])
#subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-whooshalchemy'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'git+git://github.com/miguelgrinberg/Flask-WhooshAlchemy'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-wtf'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-babel'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'guess-language'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flup'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'coverage'])

View File

@ -2,7 +2,7 @@
# -*- coding: utf8 -*-
from coverage import coverage
cov = coverage(branch = True, omit = ['flask/*', 'tests.py'])
cov = coverage(branch=True, omit=['flask/*', 'tests.py'])
cov.start()
import os
@ -14,11 +14,13 @@ from app import app, db
from app.models import User, Post
from app.translate import microsoft_translate
class TestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['CSRF_ENABLED'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db')
app.config['WTF_CSRF_ENABLED'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
os.path.join(basedir, 'test.db')
db.create_all()
def tearDown(self):
@ -32,24 +34,25 @@ class TestCase(unittest.TestCase):
n = User.make_valid_nickname('John_[123]\n')
assert n == 'John_123'
# create a user
u = User(nickname = 'john', email = 'john@example.com')
u = User(nickname='john', email='john@example.com')
db.session.add(u)
db.session.commit()
assert u.is_authenticated() == True
assert u.is_active() == True
assert u.is_anonymous() == False
assert u.is_authenticated() is True
assert u.is_active() is True
assert u.is_anonymous() is False
assert u.id == int(u.get_id())
def test_avatar(self):
# create a user
u = User(nickname = 'john', email = 'john@example.com')
u = User(nickname='john', email='john@example.com')
avatar = u.avatar(128)
expected = 'http://www.gravatar.com/avatar/d4c74594d841139328695756648b6bd6'
expected = 'http://www.gravatar.com/avatar/' + \
'd4c74594d841139328695756648b6bd6'
assert avatar[0:len(expected)] == expected
def test_make_unique_nickname(self):
# create a user and write it to the database
u = User(nickname = 'john', email = 'john@example.com')
u = User(nickname='john', email='john@example.com')
db.session.add(u)
db.session.commit()
nickname = User.make_unique_nickname('susan')
@ -57,7 +60,7 @@ class TestCase(unittest.TestCase):
nickname = User.make_unique_nickname('john')
assert nickname != 'john'
# make another user with the new nickname
u = User(nickname = nickname, email = 'susan@example.com')
u = User(nickname=nickname, email='susan@example.com')
db.session.add(u)
db.session.commit()
nickname2 = User.make_unique_nickname('john')
@ -65,59 +68,63 @@ class TestCase(unittest.TestCase):
assert nickname2 != nickname
def test_follow(self):
u1 = User(nickname = 'john', email = 'john@example.com')
u2 = User(nickname = 'susan', email = 'susan@example.com')
u1 = User(nickname='john', email='john@example.com')
u2 = User(nickname='susan', email='susan@example.com')
db.session.add(u1)
db.session.add(u2)
db.session.commit()
assert u1.unfollow(u2) == None
assert u1.unfollow(u2) is None
u = u1.follow(u2)
db.session.add(u)
db.session.commit()
assert u1.follow(u2) == None
assert u1.follow(u2) is None
assert u1.is_following(u2)
assert u1.followed.count() == 1
assert u1.followed.first().nickname == 'susan'
assert u2.followers.count() == 1
assert u2.followers.first().nickname == 'john'
u = u1.unfollow(u2)
assert u != None
assert u is not None
db.session.add(u)
db.session.commit()
assert u1.is_following(u2) == False
assert not u1.is_following(u2)
assert u1.followed.count() == 0
assert u2.followers.count() == 0
def test_follow_posts(self):
# make four users
u1 = User(nickname = 'john', email = 'john@example.com')
u2 = User(nickname = 'susan', email = 'susan@example.com')
u3 = User(nickname = 'mary', email = 'mary@example.com')
u4 = User(nickname = 'david', email = 'david@example.com')
u1 = User(nickname='john', email='john@example.com')
u2 = User(nickname='susan', email='susan@example.com')
u3 = User(nickname='mary', email='mary@example.com')
u4 = User(nickname='david', email='david@example.com')
db.session.add(u1)
db.session.add(u2)
db.session.add(u3)
db.session.add(u4)
# make four posts
utcnow = datetime.utcnow()
p1 = Post(body = "post from john", author = u1, timestamp = utcnow + timedelta(seconds = 1))
p2 = Post(body = "post from susan", author = u2, timestamp = utcnow + timedelta(seconds = 2))
p3 = Post(body = "post from mary", author = u3, timestamp = utcnow + timedelta(seconds = 3))
p4 = Post(body = "post from david", author = u4, timestamp = utcnow + timedelta(seconds = 4))
p1 = Post(body="post from john", author=u1,
timestamp=utcnow + timedelta(seconds=1))
p2 = Post(body="post from susan", author=u2,
timestamp=utcnow + timedelta(seconds=2))
p3 = Post(body="post from mary", author=u3,
timestamp=utcnow + timedelta(seconds=3))
p4 = Post(body="post from david", author=u4,
timestamp=utcnow + timedelta(seconds=4))
db.session.add(p1)
db.session.add(p2)
db.session.add(p3)
db.session.add(p4)
db.session.commit()
# setup the followers
u1.follow(u1) # john follows himself
u1.follow(u2) # john follows susan
u1.follow(u4) # john follows david
u2.follow(u2) # susan follows herself
u2.follow(u3) # susan follows mary
u3.follow(u3) # mary follows herself
u3.follow(u4) # mary follows david
u4.follow(u4) # david follows himself
u1.follow(u1) # john follows himself
u1.follow(u2) # john follows susan
u1.follow(u4) # john follows david
u2.follow(u2) # susan follows herself
u2.follow(u3) # susan follows mary
u3.follow(u3) # mary follows herself
u3.follow(u4) # mary follows david
u4.follow(u4) # david follows himself
db.session.add(u1)
db.session.add(u2)
db.session.add(u3)
@ -143,8 +150,8 @@ class TestCase(unittest.TestCase):
def test_delete_post(self):
# create a user and a post
u = User(nickname = 'john', email = 'john@example.com')
p = Post(body = 'test post', author = u, timestamp = datetime.utcnow())
u = User(nickname='john', email='john@example.com')
p = Post(body='test post', author=u, timestamp=datetime.utcnow())
db.session.add(u)
db.session.add(p)
db.session.commit()
@ -160,6 +167,7 @@ class TestCase(unittest.TestCase):
assert microsoft_translate(u'English', 'en', 'es') == u'Inglés'
assert microsoft_translate(u'Español', 'es', 'en') == u'Spanish'
if __name__ == '__main__':
try:
unittest.main()
@ -170,5 +178,5 @@ if __name__ == '__main__':
print "\n\nCoverage Report:\n"
cov.report()
print "\nHTML version: " + os.path.join(basedir, "tmp/coverage/index.html")
cov.html_report(directory = 'tmp/coverage')
cov.html_report(directory='tmp/coverage')
cov.erase()

View File

@ -6,4 +6,3 @@ if sys.platform == 'win32':
else:
pybabel = 'flask/bin/pybabel'
os.system(pybabel + ' compile -d app/translations')

View File

@ -8,7 +8,8 @@ else:
if len(sys.argv) != 2:
print "usage: tr_init <language-code>"
sys.exit(1)
os.system(pybabel + ' extract -F babel.cfg -k lazy_gettext -o messages.pot app')
os.system(pybabel + ' init -i messages.pot -d app/translations -l ' + sys.argv[1])
os.system(pybabel +
' extract -F babel.cfg -k lazy_gettext -o messages.pot app')
os.system(pybabel +
' init -i messages.pot -d app/translations -l ' + sys.argv[1])
os.unlink('messages.pot')

View File

@ -8,4 +8,3 @@ else:
os.system(pybabel + ' extract -F babel.cfg -k lazy_gettext -o messages.pot app')
os.system(pybabel + ' update -i messages.pot -d app/translations')
os.unlink('messages.pot')

File diff suppressed because it is too large Load Diff