diff --git a/app/models.py b/app/models.py index 3ba4588..7f2dc2d 100644 --- a/app/models.py +++ b/app/models.py @@ -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 '' % (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 '' % (self.body) whooshalchemy.whoosh_index(app, Post) diff --git a/app/templates/post.html b/app/templates/post.html index 3010746..2602860 100644 --- a/app/templates/post.html +++ b/app/templates/post.html @@ -14,6 +14,9 @@ {% endif %} + {% if post.author.id == g.user.id %} +
{{ _('Delete') }}
+ {% endif %} diff --git a/app/translations/es/LC_MESSAGES/messages.po b/app/translations/es/LC_MESSAGES/messages.po index 8865547..8f67137 100644 --- a/app/translations/es/LC_MESSAGES/messages.po +++ b/app/translations/es/LC_MESSAGES/messages.po @@ -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 \n" "Language-Team: es \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\":" diff --git a/app/views.py b/app/views.py index 1e405d2..895d6d4 100644 --- a/app/views.py +++ b/app/views.py @@ -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/') +@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(): diff --git a/config.py b/config.py index ec41171..7b90d5f 100644 --- a/config.py +++ b/config.py @@ -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 diff --git a/profile.py b/profile.py new file mode 100755 index 0000000..2a3203a --- /dev/null +++ b/profile.py @@ -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) + diff --git a/setup.py b/setup.py index 9827752..33a69f2 100755 --- a/setup.py +++ b/setup.py @@ -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']) diff --git a/tests.py b/tests.py index 2e60762..8db870e 100755 --- a/tests.py +++ b/tests.py @@ -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() \ No newline at end of file + 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()