Merge pull request #7 from ojii/crazy

Crazy
This commit is contained in:
Eryn Wells 2012-08-18 22:03:11 -07:00
commit 934ea0a905
8 changed files with 7231 additions and 44 deletions

View file

@ -43,6 +43,8 @@ class GitHub(object):
Returns an iterator over a resource, eg 'repos/divio/django-cms/watchers' that automatically handles
pagination.
"""
if params is None:
params = {}
data, response = self._get(path, params)
for thing in data:
yield thing

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from django.utils.functional import SimpleLazyObject
from githubnetwork.models import GHUser
def get_github_user(request):
if not request.user.is_authenticated():
return None
return GHUser.objects.get(user=request.user)
class GithubUserMiddleware(object):
def process_request(self, request):
request.gh_user = SimpleLazyObject(lambda: get_github_user(request))

View file

@ -1,19 +1,13 @@
# -*- coding: utf-8 -*-
from collections import defaultdict
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, Http404, HttpResponseForbidden
from django.shortcuts import render_to_response, redirect
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from django.utils import simplejson
from django.utils.decorators import method_decorator
from django.views.generic.base import TemplateView, TemplateResponseMixin
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, ModelFormMixin, FormView
from django.views.generic.list import ListView
from django.contrib.auth.models import User
from ghapi import api
from models import GHUser, Repo
from models import GHUser
class Graph(object):
@ -21,10 +15,10 @@ class Graph(object):
self.nodes = set()
self.edges = {}
self.distances = {}
def add_node(self, value):
self.nodes.add(value)
def add_edge(self, from_node, to_node, distance):
self._add_edge(from_node, to_node, distance)
self._add_edge(to_node, from_node, distance)
@ -41,48 +35,65 @@ class NetworkView(DetailView):
return self.user
def get_user_network(self):
user = self.get_user()
graph = []
user = self.get_user()
graph = []
def get_repo_network(self):
user = self.get_user()
graph = []
for repo in api.get_iter('user/%s/repos' % user):
# TODO: (Lynn) this is messy - clean up
links = {}
repo_info = api.get('repos/%s/%s' % (user, repo))
parent = repo_info['parent']['owner']['login']
child = repo_info['owner']['login']
watchers = repo_info['parent']['watchers']
network = repo_info['network_count']
date_updated_parent = repo_info['parent']['updated_at']
date_updated_child = repo_info['updated_at']
links['source'] = parent
links['target'] = child
links['weight'] = {'watchers': watchers, 'network' : network,
'date_updated_parent' : date_updated_parent,
'date_updated_child' : date_updated_child }
graph.append(links)
return graph
user = self.get_user()
graph = []
for repo in api.get_iter('user/%s/repos' % user):
# TODO: (Lynn) this is messy - clean up
links = {}
repo_info = api.get('repos/%s/%s' % (user, repo))
parent = repo_info['parent']['owner']['login']
child = repo_info['owner']['login']
watchers = repo_info['parent']['watchers']
network = repo_info['network_count']
date_updated_parent = repo_info['parent']['updated_at']
date_updated_child = repo_info['updated_at']
links['source'] = parent
links['target'] = child
links['weight'] = {'watchers': watchers, 'network': network,
'date_updated_parent': date_updated_parent,
'date_updated_child': date_updated_child}
graph.append(links)
return graph
def get_queryset(self):
self.get_user()
if repos:
# do repo-y things
self.get_repo_network()
else:
# do user-y things
self.get_user_network()
self.get_user()
if repos:
# do repo-y things
self.get_repo_network()
else:
# do user-y things
self.get_user_network()
def get_context_data(self, **kwargs):
# TODO: (Lynn) figure out what's needed for context data
context_object_name = self.get_context_object_name(queryset)
def get(self):
self.object_list = self.get_queryset()
self.object_list = self.get_queryset()
context = self.get_context_data(object_list=self.object_list)
return self.render_to_response(context)
@method_decorator(login_required)
def dispatch(self, *args. **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
def dispatch(self, *args, ** kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
@login_required
def me(request):
context = RequestContext(request)
context['followers'] = simplejson.dumps(
[{'name': unicode(follower), 'avatar': follower.avatar_url} for follower in GHUser.objects.filter(following=request.gh_user)])
return render_to_response('me.html', context)
@login_required
def get_user_followers(request):
name = request.GET.get('user', None)
if not name:
raise HttpResponseBadRequest()
names = simplejson.dumps([user['login'] for user in request.github.get_iter('users/%s/followers' % name)])
return HttpResponse(names, content_type='application/json')

View file

@ -43,6 +43,7 @@ MIDDLEWARE_CLASSES = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'ghapi.middleware.GithubAPIMiddleware',
'githubnetwork.middleware.GithubUserMiddleware',
]
TEMPLATE_CONTEXT_PROCESSORS = [
@ -85,7 +86,7 @@ AUTHENTICATION_BACKENDS = [
]
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
LOGIN_REDIRECT_URL = '/me/'
LOGIN_ERROR_URL = '/login/failed/'

7034
static/d3/d3.v2.js vendored Normal file

File diff suppressed because it is too large Load diff

4
static/d3/d3.v2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

118
templates/me.html Normal file
View file

@ -0,0 +1,118 @@
{% extends "graph_base.html" %}
{% block graph %}
<h1>People following you</h1>
<div id='chart'> </div>
<style>
circle.node {
stroke: #fff;
stroke-width: 1.5px;
}
line.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<script src="{{ STATIC_URL }}d3/d3.v2.js"></script>
<script src="{{ STATIC_URL }}/js/jquery.js"></script>
<script>
$(document).ready(function(){
var followers = {{ followers|safe }};
var map = {};
var w = 960,
h = 960;
var color = d3.scale.category20();
var force = d3.layout.force()
.gravity(.05)
.distance(250)
.charge(-500)
.size([w, h]);
var nodes = force.nodes(),
links = force.links();
var vis = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
force.on("tick", function() {
vis.selectAll("g.node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
vis.selectAll("line.link")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});
function restart() {
var link = vis.selectAll("line.link")
.data(links, function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("svg:line", "g.node")
.attr("class", "link");
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id;});
var nodeEnter = node.enter().append("svg:g").attr('class', 'node').call(force.drag);
nodeEnter.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", function(d){return d.avatar;})
.attr("x", "-16px")
.attr("y", "-16px")
.style("border-radius", "16px;")
.attr("width", "32px")
.attr("height", "32px");
nodeEnter.append("title")
.text(function(d) { return d.name });
node.exit().remove();
force.start();
}
// Add three nodes and three links.
function init() {
var center = {"name": "{{ request.gh_user.gh_login }}", "avatar": "{{ request.gh_user.avatar_url }}"};
nodes.push(center);
for (var i = 0; i<followers.length;i++){
nodes.push(followers[i]);
links.push({source:center, target:followers[i]});
map[followers[i].name] = followers[i];
}
restart();
}
function addLink(link) {
links.push(link);
restart();
}
restart();
init();
function loadFollowerFollowers(follower){
$.getJSON('{% url get_user_followers %}?user=' + follower.name, function(data){
for (var i = 0; i<data.length; i++){
var target = map[data[i]];
if (target){
addLink({source:follower, target:target})
}
}
});
// addLink({source: followers[15], target: followers[5]});
}
for (var i = 0; i<followers.length; i++){
loadFollowerFollowers(followers[i]);
}
});
</script>
{% endblock %}

View file

@ -14,6 +14,8 @@ urlpatterns = patterns('',
url(r'^followers/', views.graph_followers,
name='graph_followers'),
url(r'^login/$', views.login, name='login'),
url(r'^me/$', 'githubnetwork.views.me', name='me'),
url(r'^~followers/$', 'githubnetwork.views.get_user_followers', name='get_user_followers'),
url(r'^repo/(?P<user>\w+)/(?P<repo>\w+)/', views.graph_repo,
name='graph_repo'),
url(r'^$', views.index, name='index'),