coverage and profiling

This commit is contained in:
Miguel Grinberg 2013-03-09 20:17:06 -08:00
parent c99c1d52b5
commit f43818315e
8 changed files with 119 additions and 26 deletions

View File

@ -74,7 +74,7 @@ class User(db.Model):
def followed_posts(self): 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): def __repr__(self): # pragma: no cover
return '<User %r>' % (self.nickname) return '<User %r>' % (self.nickname)
class Post(db.Model): class Post(db.Model):
@ -86,7 +86,7 @@ class Post(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
language = db.Column(db.String(5)) language = db.Column(db.String(5))
def __repr__(self): def __repr__(self): # pragma: no cover
return '<Post %r>' % (self.body) return '<Post %r>' % (self.body)
whooshalchemy.whoosh_index(app, Post) whooshalchemy.whoosh_index(app, Post)

View File

@ -14,6 +14,9 @@
<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> </div>
{% endif %} {% endif %}
{% if post.author.id == g.user.id %}
<div><a href="{{ url_for('delete', id = post.id) }}">{{ _('Delete') }}</a></div>
{% endif %}
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-02-19 22:11-0800\n" "POT-Creation-Date: 2013-03-09 20:29-0800\n"
"PO-Revision-Date: 2013-02-19 22:12-0800\n" "PO-Revision-Date: 2013-03-09 20:29-0800\n"
"Last-Translator: Miguel Grinberg <miguel.grinberg@gmail.com>\n" "Last-Translator: Miguel Grinberg <miguel.grinberg@gmail.com>\n"
"Language-Team: es <LL@li.org>\n" "Language-Team: es <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -38,55 +38,55 @@ msgstr "Este nombre de usuario ya esta usado. Por favor elije otro."
msgid "Error: translation service not configured." msgid "Error: translation service not configured."
msgstr "Error: el servicio de traducción no está configurado." msgstr "Error: el servicio de traducción no está configurado."
#: app/translate.py:35 app/translate.py:55 #: app/translate.py:40
msgid "Error: Unexpected error."
msgstr "Error: Un error inesperado ha ocurrido."
#: app/translate.py:39
msgid "Error: translation service not available." msgid "Error: translation service not available."
msgstr "Error: servicio de traducción no disponible." msgstr "Error: servicio de traducción no disponible."
#: app/views.py:56 #: app/translate.py:56
msgid "Error: Unexpected error."
msgstr "Error: Un error inesperado ha ocurrido."
#: app/views.py:64
msgid "Your post is now live!" msgid "Your post is now live!"
msgstr "¡Tu artículo ha sido publicado!" msgstr "¡Tu artículo ha sido publicado!"
#: app/views.py:81 #: app/views.py:89
msgid "Invalid login. Please try again." msgid "Invalid login. Please try again."
msgstr "Credenciales inválidas. Por favor intenta de nuevo." msgstr "Credenciales inválidas. Por favor intenta de nuevo."
#: app/views.py:114 #: app/views.py:122
#, python-format #, python-format
msgid "User %(nickname)s not found." msgid "User %(nickname)s not found."
msgstr "El usuario %(nickname)s no existe." msgstr "El usuario %(nickname)s no existe."
#: app/views.py:130 #: app/views.py:138
msgid "Your changes have been saved." msgid "Your changes have been saved."
msgstr "Tus cambios han sido guardados." msgstr "Tus cambios han sido guardados."
#: app/views.py:146 #: app/views.py:154
msgid "You can't follow yourself!" msgid "You can't follow yourself!"
msgstr "¡No te puedes seguir a tí mismo!" msgstr "¡No te puedes seguir a tí mismo!"
#: app/views.py:150 #: app/views.py:158
#, python-format #, python-format
msgid "Cannot follow %(nickname)s." msgid "Cannot follow %(nickname)s."
msgstr "No se pudo seguir a %(nickname)s." msgstr "No se pudo seguir a %(nickname)s."
#: app/views.py:154 #: app/views.py:162
#, python-format #, python-format
msgid "You are now following %(nickname)s!" msgid "You are now following %(nickname)s!"
msgstr "¡Ya estás siguiendo a %(nickname)s!" msgstr "¡Ya estás siguiendo a %(nickname)s!"
#: app/views.py:166 #: app/views.py:174
msgid "You can't unfollow yourself!" msgid "You can't unfollow yourself!"
msgstr "¡No te puedes dejar de seguir a tí mismo!" msgstr "¡No te puedes dejar de seguir a tí mismo!"
#: app/views.py:170 #: app/views.py:178
#, python-format #, python-format
msgid "Cannot unfollow %(nickname)s." msgid "Cannot unfollow %(nickname)s."
msgstr "No se pudo dejar de seguir a %(nickname)s." msgstr "No se pudo dejar de seguir a %(nickname)s."
#: app/views.py:174 #: app/views.py:182
#, python-format #, python-format
msgid "You have stopped following %(nickname)s." msgid "You have stopped following %(nickname)s."
msgstr "Ya no sigues más a %(nickname)s." msgstr "Ya no sigues más a %(nickname)s."
@ -208,6 +208,10 @@ msgstr "%(nickname)s dijo %(when)s:"
msgid "Translate" msgid "Translate"
msgstr "Traducir" msgstr "Traducir"
#: app/templates/post.html:18
msgid "Delete"
msgstr "Borrar"
#: app/templates/search_results.html:5 #: app/templates/search_results.html:5
#, python-format #, python-format
msgid "Search results for \"%(query)s\":" msgid "Search results for \"%(query)s\":"

View File

@ -1,5 +1,6 @@
from flask import render_template, flash, redirect, session, url_for, request, g, jsonify 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.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 flask.ext.babel import gettext
from app import app, db, lm, oid, babel from app import app, db, lm, oid, babel
from forms import LoginForm, EditForm, PostForm, SearchForm from forms import LoginForm, EditForm, PostForm, SearchForm
@ -8,7 +9,7 @@ from datetime import datetime
from emails import follower_notification from emails import follower_notification
from guess_language import guessLanguage from guess_language import guessLanguage
from translate import microsoft_translate from translate import microsoft_translate
from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS, LANGUAGES from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS, LANGUAGES, DATABASE_QUERY_TIMEOUT
@lm.user_loader @lm.user_loader
def load_user(id): def load_user(id):
@ -28,6 +29,13 @@ def before_request():
g.search_form = SearchForm() g.search_form = SearchForm()
g.locale = get_locale() g.locale = get_locale()
@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))
return response
@app.errorhandler(404) @app.errorhandler(404)
def internal_error(error): def internal_error(error):
return render_template('404.html'), 404 return render_template('404.html'), 404
@ -174,6 +182,21 @@ def unfollow(nickname):
flash(gettext('You have stopped following %(nickname)s.', nickname = nickname)) flash(gettext('You have stopped following %(nickname)s.', nickname = nickname))
return redirect(url_for('user', 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:
flash('Post not found.')
return redirect(url_for('index'))
if post.author.id != g.user.id:
flash('You cannot delete this post.')
return redirect(url_for('index'))
db.session.delete(post)
db.session.commit()
flash('Your post has been deleted.')
return redirect(url_for('index'))
@app.route('/search', methods = ['POST']) @app.route('/search', methods = ['POST'])
@login_required @login_required
def search(): def search():

View File

@ -14,8 +14,12 @@ OPENID_PROVIDERS = [
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository') SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
SQLALCHEMY_RECORD_QUERIES = True
WHOOSH_BASE = os.path.join(basedir, 'search.db') WHOOSH_BASE = os.path.join(basedir, 'search.db')
# slow database query threshold (in seconds)
DATABASE_QUERY_TIMEOUT = 0.5
# email server # email server
MAIL_SERVER = 'your.mailserver.com' MAIL_SERVER = 'your.mailserver.com'
MAIL_PORT = 25 MAIL_PORT = 25
@ -38,5 +42,5 @@ MS_TRANSLATOR_CLIENT_SECRET = '' # enter your MS translator app secret here
ADMINS = ['you@example.com'] ADMINS = ['you@example.com']
# pagination # pagination
POSTS_PER_PAGE = 3 POSTS_PER_PAGE = 50
MAX_SEARCH_RESULTS = 50 MAX_SEARCH_RESULTS = 50

8
profile.py Executable file
View File

@ -0,0 +1,8 @@
#!flask/bin/python
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)

View File

@ -20,3 +20,4 @@ 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', 'flask-babel'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'guess-language']) 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', 'flup'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'coverage'])

View File

@ -1,6 +1,10 @@
#!flask/bin/python #!flask/bin/python
# -*- coding: utf8 -*- # -*- coding: utf8 -*-
from coverage import coverage
cov = coverage(branch = True, omit = ['flask/*', 'tests.py'])
cov.start()
import os import os
import unittest import unittest
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -21,6 +25,21 @@ class TestCase(unittest.TestCase):
db.session.remove() db.session.remove()
db.drop_all() db.drop_all()
def test_user(self):
# make valid nicknames
n = User.make_valid_nickname('John_123')
assert n == 'John_123'
n = User.make_valid_nickname('John_[123]\n')
assert n == 'John_123'
# create a user
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.id == int(u.get_id())
def test_avatar(self): def test_avatar(self):
# create a user # create a user
u = User(nickname = 'john', email = 'john@example.com') u = User(nickname = 'john', email = 'john@example.com')
@ -33,6 +52,8 @@ class TestCase(unittest.TestCase):
u = User(nickname = 'john', email = 'john@example.com') u = User(nickname = 'john', email = 'john@example.com')
db.session.add(u) db.session.add(u)
db.session.commit() db.session.commit()
nickname = User.make_unique_nickname('susan')
assert nickname == 'susan'
nickname = User.make_unique_nickname('john') nickname = User.make_unique_nickname('john')
assert nickname != 'john' assert nickname != 'john'
# make another user with the new nickname # make another user with the new nickname
@ -111,14 +132,43 @@ class TestCase(unittest.TestCase):
assert len(f2) == 2 assert len(f2) == 2
assert len(f3) == 2 assert len(f3) == 2
assert len(f4) == 1 assert len(f4) == 1
assert f1 == [p4, p2, p1] assert f1[0].id == p4.id
assert f2 == [p3, p2] assert f1[1].id == p2.id
assert f3 == [p4, p3] assert f1[2].id == p1.id
assert f4 == [p4] assert f2[0].id == p3.id
assert f2[1].id == p2.id
assert f3[0].id == p4.id
assert f3[1].id == p3.id
assert f4[0].id == p4.id
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())
db.session.add(u)
db.session.add(p)
db.session.commit()
# query the post and destroy the session
p = Post.query.get(1)
db.session.remove()
# delete the post using a new session
db.session = db.create_scoped_session()
db.session.delete(p)
db.session.commit()
def test_translation(self): def test_translation(self):
assert microsoft_translate(u'English', 'en', 'es') == u'Inglés' assert microsoft_translate(u'English', 'en', 'es') == u'Inglés'
assert microsoft_translate(u'Español', 'es', 'en') == u'Spanish' assert microsoft_translate(u'Español', 'es', 'en') == u'Spanish'
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() try:
unittest.main()
except:
pass
cov.stop()
cov.save()
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.erase()