From 8775feef238fe03ff542069ff553ee9a55dbcca4 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Sun, 25 Sep 2011 14:30:30 +0100 Subject: [PATCH] using mongodb now --- app.py | 4 ++ bin/_run_coverage_tests.py | 1 + bin/redis2mongo.py | 32 +++++++++++++ bin/run_tests.sh | 2 + handlers.py | 94 ++++++++++++++++++-------------------- models.py | 19 ++++++++ settings.py | 2 + templates/home.html | 4 +- tests/base.py | 19 ++++---- tests/test_handlers.py | 80 ++++++++++++++++++++++++++------ 10 files changed, 183 insertions(+), 74 deletions(-) create mode 100755 bin/redis2mongo.py create mode 100755 bin/run_tests.sh create mode 100644 models.py diff --git a/app.py b/app.py index b4081b7..8e3c8d5 100755 --- a/app.py +++ b/app.py @@ -33,6 +33,10 @@ def __init__(self, database_name=None): self.redis = redis.client.Redis(settings.REDIS_HOST, settings.REDIS_PORT) + from models import connection + self.db = connection[settings.DATABASE_NAME] + + def main(): # pragma: no cover tornado.options.parse_command_line() diff --git a/bin/_run_coverage_tests.py b/bin/_run_coverage_tests.py index 46a9fa2..0f83ff3 100644 --- a/bin/_run_coverage_tests.py +++ b/bin/_run_coverage_tests.py @@ -7,6 +7,7 @@ COVERAGE_MODULES = [ 'app', 'handlers', + 'models', ] def all(): diff --git a/bin/redis2mongo.py b/bin/redis2mongo.py new file mode 100755 index 0000000..4406ef6 --- /dev/null +++ b/bin/redis2mongo.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +import os +import site + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +path = lambda *a: os.path.join(ROOT,*a) + +site.addsitedir(path('.')) +site.addsitedir(path('vendor')) + +import redis as redis_ + +def run(): + import settings + from models import connection + db = connection[settings.DATABASE_NAME] + redis = redis_.client.Redis(settings.REDIS_HOST, + settings.REDIS_PORT) + + for username in redis.smembers('allusernames'): + key = 'access_tokens:%s' % username + access_token = redis.get(key) + if access_token: + user = db.User.find_one({'username': username}) + if not user: + user = db.User() + user['username'] = username + user['access_token'] = access_token + user.save() + +if __name__ == '__main__': + run() diff --git a/bin/run_tests.sh b/bin/run_tests.sh new file mode 100755 index 0000000..c97adea --- /dev/null +++ b/bin/run_tests.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python bin/_run_tests.py --logging=error $@ diff --git a/handlers.py b/handlers.py index 6c20044..fa68264 100644 --- a/handlers.py +++ b/handlers.py @@ -6,8 +6,11 @@ from tornado_utils.routes import route #from tornado_utils.decorators import login_required from tornado.escape import json_decode, json_encode +from pymongo.objectid import InvalidId, ObjectId #import settings +from models import User + class BaseHandler(tornado.web.RequestHandler): @@ -20,14 +23,21 @@ def write_jsonp(self, callback, struct): self.write('%s(%s)' % (callback, tornado.escape.json_encode(struct))) def get_current_user(self): - username = self.get_secure_cookie('user') - if username: - return unicode(username, 'utf8') + _id = self.get_secure_cookie('user') + if _id: + try: + return self.db.User.find_one({'_id': ObjectId(_id)}) + except InvalidId: # pragma: no cover + return self.db.User.find_one({'username': _id}) @property def redis(self): return self.application.redis + @property + def db(self): + return self.application.db + @route('/') class HomeHandler(BaseHandler): @@ -37,11 +47,6 @@ def get(self): 'page_title': 'Too Cool for Me?', } user = self.get_current_user() - if user: - # check that we still have the access token - key = 'access_tokens:%s' % user - if not self.redis.get(key): - user = None if user: url = '/static/bookmarklet.js' url = '%s://%s%s' % (self.request.protocol, @@ -96,23 +101,18 @@ def get(self): # All of this is commented out until I can figure out why cookie # headers aren't sent from bookmarklet's AJAX code - this_username = self.get_argument('you', self.get_current_user()) -# this_username = self.get_current_user() -# #print "THIS_USERNAME", repr(this_username) -# you = self.get_argument('you', None) # optional -# #print "YOU", repr(you) -# if you: -# print "THIS_USERNAME", repr(this_username) -# if this_username != you: -# self.write_json({ -# 'ERROR': "Logged in on %s as '%s'" % this_username -# }) -# return -# if you in usernames: -# usernames.remove(you) - access_token = self.redis.get('access_tokens:%s' % this_username) - if access_token: - access_token = json_decode(access_token) + this_username = self.get_argument('you', None) + access_token = None + if this_username is not None: + user = self.db.User.find_one({'username': this_username}) + if user: + access_token = user['access_token'] + else: + user = self.get_current_user() + if user: + this_username = user['username'] + access_token = user['access_token'] + if not access_token: msg = {'ERROR': ('Not authorized. Go to http://%s and sign in' % self.request.host)} @@ -122,9 +122,6 @@ def get(self): self.write_json(msg) self.finish() return - #print "USERNAMES" - #pprint(usernames) - #print results = {} # pick some up already from the cache @@ -232,12 +229,18 @@ def _on_auth(self, user_struct): self.render('twitter_auth_failed.html', **options) return username = user_struct.get('username') - self.redis.rpush('usernames', username) + #self.redis.rpush('usernames', username) access_token = user_struct['access_token'] assert access_token - self.redis.set('access_tokens:%s' % username, json_encode(access_token)) + user = self.db.User.find_one({'username': username}) + if user is None: + user = self.db.User() + user['username'] = username + user['access_token'] = access_token + user.save() + self.set_secure_cookie("user", - username.encode('utf8'), + str(user['_id']), expires_days=30, path='/') self.redirect('/') @@ -275,22 +278,19 @@ class FollowingHandler(BaseHandler, tornado.auth.TwitterMixin): @tornado.web.asynchronous def get(self, username): options = {'username': username} - this_username = self.get_current_user() - if not this_username: + #this_username = self.get_current_user() + current_user = self.get_current_user() + if not current_user: self.redirect(self.reverse_url('auth_twitter')) return + this_username = current_user['username'] options['this_username'] = this_username options['follows'] = None key = 'follows:%s:%s' % (this_username, username) value = self.redis.get(key) if value is None: - access_token = self.redis.get('access_tokens:%s' % this_username) - if not access_token: - self.write('ERROR: Not authorized with Twitter for %s' % - self.request.host) - self.finish() - return - access_token = json_decode(access_token) + #access_token = self.redis.get('access_tokens:%s' % this_username) + access_token = current_user['access_token'] self.twitter_request( "/friendships/show", source_screen_name=this_username, @@ -302,8 +302,6 @@ def get(self, username): ) else: self._on_friendship(bool(int(value)), None, options) - #options['follows'] = bool(int(value)) - #self._fetch_info(options) def _on_friendship(self, result, key, options): if result is None: @@ -326,13 +324,13 @@ def _on_friendship(self, result, key, options): def _fetch_info(self, options, username=None): if username is None: username = options['username'] + key = 'info:%s' % username value = self.redis.get(key) if value is None: - access_token = self.redis.get('access_tokens:%s' % - options['this_username']) - access_token = json_decode(access_token) + user = self.db.User.find_one({'username': options['this_username']}) + access_token = user['access_token'] self.twitter_request( "/users/show", screen_name=username, @@ -343,10 +341,6 @@ def _fetch_info(self, options, username=None): ) else: self._on_info(json_decode(value), None, options, username) - #value = json_decode(value) - #options['info'] = value - #pprint(value) - #self._render(options) def _on_info(self, result, key, options, username): if result is None: @@ -395,7 +389,7 @@ def _set_ratio(self, options, key): @route(r'/coolest', name='coolest') -class CoolestHandler(BaseHandler): +class CoolestHandler(BaseHandler): # pragma: no cover (under development) def get(self): options = {} diff --git a/models.py b/models.py new file mode 100644 index 0000000..cd88efe --- /dev/null +++ b/models.py @@ -0,0 +1,19 @@ +import datetime +from mongolite import Connection, Document +connection = Connection() + +@connection.register +class User(Document): + __collection__ = 'users' + structure = { + 'username': unicode, + 'access_token': dict, + 'modify_date': datetime.datetime + } + + default_values = {'modify_date':datetime.datetime.utcnow} + + def save(self, *args, **kwargs): + if '_id' in self and kwargs.get('update_modify_date', True): + self.modify_date = datetime.datetime.utcnow() + super(User, self).save(*args, **kwargs) diff --git a/settings.py b/settings.py index 347bf1d..ba990f1 100644 --- a/settings.py +++ b/settings.py @@ -14,6 +14,8 @@ REDIS_HOST = 'localhost' REDIS_PORT = 6379 +DATABASE_NAME = 'toocool' + try: from local_settings import * except ImportError: diff --git a/templates/home.html b/templates/home.html index 1a016c7..c9c64db 100644 --- a/templates/home.html +++ b/templates/home.html @@ -11,7 +11,7 @@

Find out who on Twitter follows you back

Find out who on Twitter follows you back

1. Add the bookmarklet Drag-and-drop this link (bookmarklet) ... -twitter bird + Too cool for me?

diff --git a/tests/base.py b/tests/base.py index dd5cc47..0dbabd0 100644 --- a/tests/base.py +++ b/tests/base.py @@ -19,16 +19,15 @@ class _DatabaseTestCaseMixin(object): def setup_connection(self): if not self._once: self._once = True - #self.db = + self._emptyCollections() def teardown_connection(self): - # do this every time - pass - #self._emptyCollections() + self._emptyCollections() def _emptyCollections(self): - pass - + [self.db.drop_collection(x) for x + in self.db.collection_names() + if x not in ('system.indexes',)] class BaseAsyncTestCase(AsyncHTTPTestCase, _DatabaseTestCaseMixin): @@ -55,15 +54,19 @@ def setUp(self): def tearDown(self): super(BaseHTTPTestCase, self).tearDown() - self.db.flushall() + self.redis.flushall() def get_app(self): return app.Application(database_name='test') @property - def db(self): + def redis(self): return self.get_app().redis + @property + def db(self): + return self.get_app().db + def decode_cookie_value(self, key, cookie_value): try: return re.findall('%s=([\w=\|]+);' % key, cookie_value)[0] diff --git a/tests/test_handlers.py b/tests/test_handlers.py index fa28259..4cad47d 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -14,9 +14,10 @@ def setUp(self): def test_homepage(self): response = self.client.get('/') self.assertEqual(response.code, 200) - self.assertTrue('stranger' in response.body) + self.assertTrue('Login with' in response.body) def test_twitter_login(self): + assert not self.db.User.find_one({'username': 'peterbe'}) TwitterAuthHandler.get_authenticated_user = \ twitter_get_authenticated_user url = self.reverse_url('auth_twitter') @@ -27,10 +28,17 @@ def test_twitter_login(self): response = self.client.get(url, {'oauth_token':'xxx'}) self.assertEqual(response.code, 302) - key = 'access_tokens:peterbe' - self.assertEqual(self.db.get(key), - json.dumps({'key': '0123456789', - 'secret': 'xxx'})) + user = self.db.User.find_one({'username': 'peterbe'}) + self.assertEqual(user['access_token'], + {'key': '0123456789', + 'secret': 'xxx'}) + + url = self.reverse_url('logout') + response = self.client.get(url) + self.assertEqual(response.code, 302) + response = self.client.get('/') + self.assertEqual(response.code, 200) + self.assertTrue('Login with Twitter' in response.body) def test_twitter_login_twitter_failing(self): TwitterAuthHandler.get_authenticated_user = \ @@ -70,25 +78,25 @@ def test_home(self): url = self.reverse_url('test') response = self.client.get('/') self.assertEqual(response.code, 200) - self.assertTrue('stranger' in response.body) + self.assertTrue('Login with Twitter to start' in response.body) self._login(username='peppe') response = self.client.get('/') self.assertEqual(response.code, 200) - self.assertTrue('stranger' not in response.body) - assert self.db.get('access_tokens:peppe') - self.db.delete('access_tokens:peppe') - assert not self.db.get('access_tokens:peppe') - response = self.client.get('/') - self.assertEqual(response.code, 200) - self.assertTrue('stranger' in response.body) + self.assertTrue('Login with Twitter to start' not in response.body) def test_test_service(self): - self._login() url = self.reverse_url('test') + response = self.client.get(url) + self.assertEqual(response.code, 302) + self.assertTrue(self.reverse_url('auth_twitter') in + response.headers['location']) + self._login() + response = self.client.get(url) self.assertEqual(response.code, 200) + def test_json(self): FollowsHandler.twitter_request = \ make_mock_twitter_request({u'relationship': { @@ -124,6 +132,50 @@ def test_json(self): struct = json.loads(response.body) self.assertEqual(struct['obama'], False) + def test_json_with_overriding_you(self): + FollowsHandler.twitter_request = \ + make_mock_twitter_request({u'relationship': { + u'target': {u'followed_by': False, + u'following': False, + u'screen_name': u'obama'}}}) + + self._login() + url = self.reverse_url('json') + response = self.client.get(url, {'username': 'obama', 'you': 'bob'}) + self.assertEqual(response.code, 200) + struct = json.loads(response.body) + self.assertTrue(struct['ERROR']) + + url = self.reverse_url('jsonp') + response = self.client.get(url, {'username': 'obama', 'you': 'bob'}) + self.assertEqual(response.code, 200) + self.assertTrue('ERROR' in response.body) + + url = self.reverse_url('json') + response = self.client.get(url, {'username': 'obama', 'you': 'peterbe'}) + self.assertEqual(response.code, 200) + struct = json.loads(response.body) + self.assertEqual(struct['obama'], False) + + url = self.reverse_url('jsonp') + response = self.client.get(url, {'username': 'obama', 'you': 'peterbe'}) + self.assertEqual(response.code, 200) + self.assertTrue('"obama":' in response.body) + self.assertTrue('false' in response.body) + + url = self.reverse_url('json') + # do it again and it should be picked up by the cache + FollowsHandler.twitter_request = \ + make_mock_twitter_request({u'relationship': { + u'target': {u'followed_by': True, + u'following': True, + u'screen_name': u'obama'}}}) + response = self.client.get(url, {'username': 'obama'}) + self.assertEqual(response.code, 200) + struct = json.loads(response.body) + self.assertEqual(struct['obama'], False) + + def test_jsonp(self): FollowsHandler.twitter_request = \ make_mock_twitter_request({u'relationship': {