commit
						934ea0a905
					
				
					 8 changed files with 7231 additions and 44 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										15
									
								
								githubnetwork/middleware.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								githubnetwork/middleware.py
									
										
									
									
									
										Normal 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)) | ||||
| 
 | ||||
|  | @ -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') | ||||
|  |  | |||
|  | @ -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
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										118
									
								
								templates/me.html
									
										
									
									
									
										Normal 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 %} | ||||
| 
 | ||||
							
								
								
									
										2
									
								
								urls.py
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								urls.py
									
										
									
									
									
								
							|  | @ -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'), | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue