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

View File

@ -14,6 +14,9 @@
<img id="loading{{post.id}}" style="display: none" src="/static/img/loading.gif">
</div>
{% endif %}
{% if post.author.id == g.user.id %}
<div><a href="{{ url_for('delete', id = post.id) }}">{{ _('Delete') }}</a></div>
{% endif %}
</td>
</tr>
</table>

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-02-19 22:11-0800\n"
"PO-Revision-Date: 2013-02-19 22:12-0800\n"
"POT-Creation-Date: 2013-03-09 20:29-0800\n"
"PO-Revision-Date: 2013-03-09 20:29-0800\n"
"Last-Translator: Miguel Grinberg <miguel.grinberg@gmail.com>\n"
"Language-Team: es <LL@li.org>\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."
msgstr "Error: el servicio de traducción no está configurado."
#: app/translate.py:35 app/translate.py:55
msgid "Error: Unexpected error."
msgstr "Error: Un error inesperado ha ocurrido."
#: app/translate.py:39
#: app/translate.py:40
msgid "Error: translation service not available."
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!"
msgstr "¡Tu artículo ha sido publicado!"
#: app/views.py:81
#: app/views.py:89
msgid "Invalid login. Please try again."
msgstr "Credenciales inválidas. Por favor intenta de nuevo."
#: app/views.py:114
#: app/views.py:122
#, python-format
msgid "User %(nickname)s not found."
msgstr "El usuario %(nickname)s no existe."
#: app/views.py:130
#: app/views.py:138
msgid "Your changes have been saved."
msgstr "Tus cambios han sido guardados."
#: app/views.py:146
#: app/views.py:154
msgid "You can't follow yourself!"
msgstr "¡No te puedes seguir a tí mismo!"
#: app/views.py:150
#: app/views.py:158
#, python-format
msgid "Cannot follow %(nickname)s."
msgstr "No se pudo seguir a %(nickname)s."
#: app/views.py:154
#: app/views.py:162
#, python-format
msgid "You are now following %(nickname)s!"
msgstr "¡Ya estás siguiendo a %(nickname)s!"
#: app/views.py:166
#: app/views.py:174
msgid "You can't unfollow yourself!"
msgstr "¡No te puedes dejar de seguir a tí mismo!"
#: app/views.py:170
#: app/views.py:178
#, python-format
msgid "Cannot unfollow %(nickname)s."
msgstr "No se pudo dejar de seguir a %(nickname)s."
#: app/views.py:174
#: app/views.py:182
#, python-format
msgid "You have stopped following %(nickname)s."
msgstr "Ya no sigues más a %(nickname)s."
@ -208,6 +208,10 @@ msgstr "%(nickname)s dijo %(when)s:"
msgid "Translate"
msgstr "Traducir"
#: app/templates/post.html:18
msgid "Delete"
msgstr "Borrar"
#: app/templates/search_results.html:5
#, python-format
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.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
@ -8,7 +9,7 @@ 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
from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS, LANGUAGES, DATABASE_QUERY_TIMEOUT
@lm.user_loader
def load_user(id):
@ -28,6 +29,13 @@ def before_request():
g.search_form = SearchForm()
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)
def internal_error(error):
return render_template('404.html'), 404
@ -174,6 +182,21 @@ def unfollow(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:
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'])
@login_required
def search():

View File

@ -14,8 +14,12 @@ OPENID_PROVIDERS = [
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
SQLALCHEMY_RECORD_QUERIES = True
WHOOSH_BASE = os.path.join(basedir, 'search.db')
# slow database query threshold (in seconds)
DATABASE_QUERY_TIMEOUT = 0.5
# email server
MAIL_SERVER = 'your.mailserver.com'
MAIL_PORT = 25
@ -38,5 +42,5 @@ MS_TRANSLATOR_CLIENT_SECRET = '' # enter your MS translator app secret here
ADMINS = ['you@example.com']
# pagination
POSTS_PER_PAGE = 3
POSTS_PER_PAGE = 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', 'guess-language'])
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
# -*- coding: utf8 -*-
from coverage import coverage
cov = coverage(branch = True, omit = ['flask/*', 'tests.py'])
cov.start()
import os
import unittest
from datetime import datetime, timedelta
@ -21,6 +25,21 @@ class TestCase(unittest.TestCase):
db.session.remove()
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):
# create a user
u = User(nickname = 'john', email = 'john@example.com')
@ -33,6 +52,8 @@ class TestCase(unittest.TestCase):
u = User(nickname = 'john', email = 'john@example.com')
db.session.add(u)
db.session.commit()
nickname = User.make_unique_nickname('susan')
assert nickname == 'susan'
nickname = User.make_unique_nickname('john')
assert nickname != 'john'
# make another user with the new nickname
@ -111,14 +132,43 @@ class TestCase(unittest.TestCase):
assert len(f2) == 2
assert len(f3) == 2
assert len(f4) == 1
assert f1 == [p4, p2, p1]
assert f2 == [p3, p2]
assert f3 == [p4, p3]
assert f4 == [p4]
assert f1[0].id == p4.id
assert f1[1].id == p2.id
assert f1[2].id == p1.id
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):
assert microsoft_translate(u'English', 'en', 'es') == u'Inglés'
assert microsoft_translate(u'Español', 'es', 'en') == u'Spanish'
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()