Permalink
Browse files

implementing new everyone I follow feature

  • Loading branch information...
1 parent 1edb934 commit c3e5ea78553c3e115f7835cc0d164508833bf582 @peterbe committed Oct 7, 2011
Showing with 219 additions and 7 deletions.
  1. +90 −5 handlers.py
  2. +4 −1 models.py
  3. +97 −0 templates/everyone.html
  4. +2 −1 templates/home.html
  5. +26 −0 utils.py
View
@@ -8,6 +8,7 @@
from tornado_utils.routes import route
from tornado.escape import json_decode, json_encode
from pymongo.objectid import InvalidId, ObjectId
+import utils
#import settings
from models import User
@@ -146,11 +147,24 @@ def get(self):
access_token=access_token)
self._on_show(result, this_username, username, results)
elif usernames:
+ if len(usernames) > 100:
+ raise HTTPError(400, "Too many usernames to look up (max 100)")
# See https://dev.twitter.com/docs/api/1/get/friendships/lookup
- result = yield tornado.gen.Task(self.twitter_request,
- "/friendships/lookup",
- screen_name=','.join(usernames),
- access_token=access_token)
+ result = None
+ attempts = 0
+ while result is None:
+ result = yield tornado.gen.Task(self.twitter_request,
+ "/friendships/lookup",
+ screen_name=','.join(usernames),
+ access_token=access_token)
+ if result is not None:
+ break
+ else:
+ attempts += 1
+ from time import sleep
+ sleep(1)
+ if attempts > 2:
+ raise HTTPError(500, "Unable to look up friendships")
self._on_lookup(result, this_username, results)
else:
# all usernames were lookup'able by cache
@@ -167,7 +181,7 @@ def _on_lookup(self, result, this_username, data):
else:
data[each['screen_name']] = False
key = 'follows:%s:%s' % (this_username, each['screen_name'])
- self.redis.setex(key, int(data[each['screen_name']]), 60)
+ self.redis.setex(key, int(data[each['screen_name']]), 60 * 5)
if self.jsonp:
self.write_jsonp(self.jsonp, data)
@@ -383,3 +397,74 @@ def get(self):
options = {}
options['page_title'] = "Screenshots"
self.render('screenshots.html', **options)
+
+@route('/everyone', name='everyone')
+class EveryoneIFollowHandler(BaseHandler, tornado.auth.TwitterMixin):
+
+ @tornado.web.asynchronous
+ @tornado.gen.engine
+ def get(self):
+
+ current_user = self.get_current_user()
+ if not current_user:
+ self.redirect(self.reverse_url('auth_twitter'))
+ return
+
+ this_username = current_user['username']
+ access_token = current_user['access_token']
+ key = 'friends:%s' % this_username
+ result = self.redis.get(key)
+ if result is None:
+ result = yield tornado.gen.Task(self.twitter_request,
+ "/friends/ids",
+ screen_name=this_username,
+ access_token=access_token
+ )
+ self.redis.setex(key, json_encode(result), 60 * 60)
+ else:
+ result = json_decode(result)
+ # now turn these IDs into real screen names
+ unknown = []
+ screen_names = []
+ for id_ in result:
+ user = self.db.User.find_one({'user_id': id_})
+ if user:
+ screen_names.append(user['username'])
+ else:
+ key = 'screen_name:%s' % id_
+ screen_name = self.redis.get(key)
+ if screen_name is None:
+ unknown.append(id_)
+ else:
+ screen_names.append(screen_name)
+
+ buckets = utils.bucketize(unknown, 100)
+
+ for bucket in buckets:
+ users = None
+ attempts = 0
+ while True:
+ users = yield tornado.gen.Task(self.twitter_request,
+ "/users/lookup",
+ user_id=','.join(str(x) for x in bucket)
+ )
+ if users is not None:
+ break
+ else:
+ from time import sleep
+ sleep(1)
+ attempts += 1
+ if attempts > 3:
+ raise HTTPError(500, "Unable to connect to twitter")
+ for user in users:
+ username = user['screen_name']
+ key = 'screen_name:%s' % user['id']
+ self.redis.setex(key, username, 7 * 24 * 60 * 60)
+ screen_names.append(username)
+
+ assert len(result) == len(screen_names)
+
+ options = {}
+ options['screen_names'] = screen_names
+ options['page_title'] = "Everyone I follow"
+ self.render('everyone.html', **options)
View
@@ -5,11 +5,14 @@
@connection.register
class User(Document):
__collection__ = 'users'
- structure = {
+ skeleton = {
'username': unicode,
'access_token': dict,
'modify_date': datetime.datetime
}
+ optional = {
+ 'user_id': int,
+ }
default_values = {'modify_date':datetime.datetime.utcnow}
View
@@ -0,0 +1,97 @@
+{% extends "base.html" %}
+
+
+{% block extra_head %}
+<style>
+div.clearer {clear: both; line-height: 0; height: 0;}
+#followsyounot {
+ float:right;
+ width:50%;
+ border-left: 1px solid #ccc;
+}
+#split div { padding:20px; }
+a.profile {
+ font-weight:bold;
+ text-decoration: none;
+ color:black;
+}
+a.profile:hover { text-decoration:underline; }
+a.compare { font-size: 0.7em; }
+ul li { list-style: none; margin:1px 10px; clear:left; }
+li a.thumbnail { float:left; padding:3px; margin:0 5px 0 0; }
+</style>
+{% end %}
+
+{% block content %}
+
+<h2>{{ page_title }}</h2>
+
+<div id="split">
+ <div id="followsyounot">
+ <h3 class="followsyounot">Too cool for me</h3>
+ <ul>
+ </ul>
+ </div>
+ <div id="followsyou">
+ <h3 class="followsyou">Follows me!</h3>
+ <ul>
+ </ul>
+ </div>
+</div>
+
+<div class="clearer">&nbsp;</div>
+
+<ul id="users" style="display:none">
+{% for username in screen_names %}
+<li id="user__{{ username }}">
+<a href="https://twitter.com/#!{{ username }}" class="thumbnail"
+><img width="48" height="48"
+ src="http://api.twitter.com/1/users/profile_image/{{ username }}.png"></a>
+<a href="https://twitter.com/#!{{ username }}" class="profile"
+>{{ username }}</a><br>
+<a href="/following/{{ username }}" class="compare">compare</a>
+</li>
+{% end %}
+</ul>
+
+{% end %}
+
+{% block extra_js %}
+<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
+<script>
+function lookup(usernames) {
+ $.getJSON('/json', {'usernames': usernames.join(',')}, function (response) {
+ if (response.ERROR) {
+ alert('ERROR: ' + response.ERROR);
+ return;
+ }
+ var c;
+ $.each(response, function(x, y) {
+ if (y)
+ c = $('#followsyou ul');
+ else
+ c = $('#followsyounot ul');
+ $('#user__' + x).detach().appendTo(c);
+ });
+ });
+}
+
+var MAX = 100;
+$(function() {
+ var buckets = [], username, usernames = [];
+ $('#users li').each(function() {
+ username = $(this).attr('id').replace('user__', '');
+ usernames.push(username);
+ if (usernames.length == MAX) {
+ buckets.push(usernames);
+ usernames = [];
+ }
+ });
+ if (usernames.length) buckets.push(usernames);
+ for (var i in buckets) {
+ lookup(buckets[i]);
+ }
+});
+
+</script>
+{% end %}
View
@@ -14,7 +14,8 @@
logged in as <strong>@{{ user['username'] }}</strong>
<span style="font-size:0.7em">(<a href="/auth/logout/">Log out</a>)</span>
<br/>
- <a href="/test">Test the service manually</a>
+ <a href="/test">Test the service manually</a><br>
+ <a href="/everyone">See Everyone I follow</a>
</p>
<!-- END LOGGED IN BLOCK -->
{% else %}
View
@@ -0,0 +1,26 @@
+def bucketize(sequence, max_size):
+ buckets = []
+ if not sequence:
+ return buckets
+ for i, each in enumerate(sequence):
+ if not i:
+ bucket = []
+ elif not i % max_size:
+ buckets.append(bucket)
+ bucket = []
+ bucket.append(each)
+ if bucket:
+ buckets.append(bucket)
+ return buckets
+
+
+def test_bucketize():
+ buckets = bucketize(range(1, 12), 5)
+ assert buckets == [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11]]
+
+ buckets = bucketize(range(1, 11), 5)
+ assert buckets == [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
+
+
+if __name__ == '__main__':
+ test_bucketize()

0 comments on commit c3e5ea7

Please sign in to comment.