Compare commits
	
		
			54 commits
		
	
	
		
			master
			...
			feature/gr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cab417594c | |||
| 2abb0f7c49 | |||
| 036f35e1c0 | |||
|   | cec2d56338 | ||
| 0fdc66cd09 | |||
| 0dd428a43d | |||
| 35cb9ab6b2 | |||
| 1fa791829f | |||
| ac891f6fcb | |||
| 5331780a83 | |||
| 386c618981 | |||
| ebd371b43f | |||
| dab1b69fb7 | |||
| 9c44419146 | |||
| f306488b01 | |||
| 934ea0a905 | |||
| d0386b475a | |||
| e2aa534e76 | |||
|   | 3c57ff9910 | ||
| 8cd4940215 | |||
|   | 0dbc4c21e0 | ||
|   | 9e1a68aac5 | ||
| dac7766ee7 | |||
|   | 0ab9f790fe | ||
|   | 4bb36f6129 | ||
| 6d9f67d3bf | |||
|   | b456362a47 | ||
| d3a949833d | |||
|   | 8b988be06e | ||
| 6969af178c | |||
| c389dc5581 | |||
| c0bac128d7 | |||
| e34a4aa15d | |||
| 72e2ae9290 | |||
| cc84293480 | |||
| 0314a0f71e | |||
| 6bb4000d30 | |||
| 57ea318fcf | |||
| 8e2decc27e | |||
| 56587aa345 | |||
| e044dbf19a | |||
| 2184fab678 | |||
| 068403f81f | |||
| 2da6b7395c | |||
| 529c932e33 | |||
| fdb900a8a3 | |||
| d2fc4ca84b | |||
| 5435ed5789 | |||
| 99f37ede03 | |||
| 127ae209b1 | |||
| 1fb1bec70e | |||
| 41718d128a | |||
| 2d81fb0143 | |||
| f4218775ae | 
					 14 changed files with 7370 additions and 51 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): | ||||
|  | @ -59,9 +53,9 @@ class NetworkView(DetailView): | |||
|             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 } | ||||
|             links['weight'] = {'watchers': watchers, 'network': network, | ||||
|                                'date_updated_parent': date_updated_parent, | ||||
|                                'date_updated_child': date_updated_child} | ||||
|             graph.append(links) | ||||
|         return graph | ||||
| 
 | ||||
|  | @ -84,5 +78,34 @@ class NetworkView(DetailView): | |||
|         return self.render_to_response(context) | ||||
| 
 | ||||
|     @method_decorator(login_required) | ||||
|     def dispatch(self, *args. **kwargs): | ||||
|     def dispatch(self, *args, ** kwargs): | ||||
|         return super(ProtectedView, self).dispatch(*args, **kwargs) | ||||
| 
 | ||||
| 
 | ||||
| def _sorted_repos(request): | ||||
|     '''Get a list of repos for the currently authorized user, sort it, and | ||||
|     return it.''' | ||||
|     repos = [r for r in request.github.get_iter('users/%s/repos' % | ||||
|         request.user.username)] | ||||
|     repos.sort(key=lambda x: x['name']) | ||||
|     return repos | ||||
| 
 | ||||
| 
 | ||||
| @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)]) | ||||
|     context['repos'] = _sorted_repos(request) | ||||
|     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
											
										
									
								
							
							
								
								
									
										24
									
								
								templates/graph_base.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								templates/graph_base.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| {# vim: set ft=htmldjango #} | ||||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block head_css %} | ||||
| <style text="text/css"> | ||||
| h1.placeholder { | ||||
|   margin-top: 80px; | ||||
|   text-align: center; | ||||
|   color: #ccc; | ||||
| } | ||||
| </style> | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block container %} | ||||
| {% include "navbar.html" %} | ||||
| {#<div class="container-fluid">#} | ||||
|   {% block graph %}<h1 class="placeholder">PUT A GRAPH HERE</h1>{% endblock %} | ||||
| {#</div>#} | ||||
| {% endblock container %} | ||||
| 
 | ||||
| {% block body_js %} | ||||
| <script src="{{ STATIC_URL }}js/jquery.js"></script> | ||||
| <script src="{{ STATIC_URL }}bootstrap/js/bootstrap-dropdown.js"></script> | ||||
| {% endblock %} | ||||
							
								
								
									
										2
									
								
								templates/graph_followers.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								templates/graph_followers.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| {# vim: set ft=htmldjango #} | ||||
| {% extends "graph_base.html" %} | ||||
							
								
								
									
										6
									
								
								templates/graph_repo.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								templates/graph_repo.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| {# vim: set ft=htmldjango #} | ||||
| {% extends "graph_base.html" %} | ||||
| 
 | ||||
| {% block graph %} | ||||
| <h1 class="placeholder">PUT A GRAPH OF<br/>{{ username }}'s<br/>{{ repo }} REPO<br/>HERE</h1> | ||||
| {% endblock %} | ||||
							
								
								
									
										164
									
								
								templates/me.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								templates/me.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,164 @@ | |||
| {% extends "graph_base.html" %} | ||||
| 
 | ||||
| {% block graph %} | ||||
|     <h1>People following you</h1> | ||||
|     <div id='chart'> </div> | ||||
|     <style> | ||||
|         #chart { | ||||
|           position: absolute; | ||||
|           top: 0; | ||||
|           left: 0; | ||||
|           width: 100%; | ||||
|           height: 100%; | ||||
|           margin: 0; | ||||
|           padding: 0; | ||||
|         } | ||||
| 
 | ||||
|         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 = $('#chart').width(); | ||||
|             h = $('#chart').height(); | ||||
|         var color = d3.scale.category20(); | ||||
| 
 | ||||
|         var nodeSize = 64; | ||||
|         var borderSize = 3; | ||||
|         var nodeCenter = nodeSize / 2; | ||||
| 
 | ||||
|         /* Use a "flexible force-directed graph layout". */ | ||||
|         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; | ||||
|             nodeEnter = node.enter().append("svg:g") | ||||
|                 .attr('class', 'node') | ||||
|                 .call(force.drag); | ||||
| 
 | ||||
|             // Node clip path | ||||
|             nodeEnter.append("svg:clipPath") | ||||
|                 .attr("id", function(d) {return "clip-" + d.name;}) | ||||
|                 .append("svg:circle").attr("r", nodeCenter + "px"); | ||||
| 
 | ||||
|             // Border | ||||
|             nodeEnter.append("circle") | ||||
|                 .attr("r", (nodeCenter + borderSize) + "px") | ||||
|                 .attr("fill", "#2c2c2c"); | ||||
| 
 | ||||
|             // Node | ||||
|             nodeEnter.append("svg:image") | ||||
|                 .attr("class", "circle") | ||||
|                 .attr("clip-path", function(d) {return "url(#clip-" + d.name + ")";}) | ||||
|                 .attr("clip-rule", "nonzero") | ||||
|                 .attr("xlink:href", function(d) {return d.avatar;}) | ||||
|                 .attr("x", -nodeCenter + "px") | ||||
|                 .attr("y", -nodeCenter + "px") | ||||
|                 .attr("width", nodeSize + "px") | ||||
|                 .attr("height", nodeSize + "px"); | ||||
| 
 | ||||
|             // Node title/tooltip | ||||
|             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]); | ||||
|         } | ||||
| 
 | ||||
|         /* Install an event handler for window resizes */ | ||||
|         $(window).resize(function() { | ||||
|           var chart = $('#chart'); | ||||
|           var svg = $('#chart svg'); | ||||
|           var w = chart.width(); | ||||
|           var h = chart.height(); | ||||
| 
 | ||||
|           force.size([w, h]); | ||||
|           svg.width(w); | ||||
|           svg.height(h); | ||||
|         }); | ||||
|     }); | ||||
|     </script> | ||||
| {% endblock %} | ||||
| 
 | ||||
							
								
								
									
										29
									
								
								templates/navbar.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								templates/navbar.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| {# vim: set ft=htmldjango #} | ||||
| <div class="navbar navbar-fixed-top"> | ||||
|   <div class="navbar-inner"> | ||||
|     <div class="container-fluid"> | ||||
|       <a class="brand" href="">Cool name</a> | ||||
|       <ul class="nav"> | ||||
|         <li><a href="{% url me %}">Me</a></li> | ||||
|         <li class="dropdown"> | ||||
|           <a href="#" class="dropdown-toggle" data-toggle="dropdown">My Repos</a> | ||||
|           <ul class="dropdown-menu"> | ||||
|             {% for r in repos %} | ||||
|             <li><a href="#">{{ r.name }}</a></li> | ||||
|             {% endfor %} | ||||
|           </ul> | ||||
|         </li> | ||||
|       </ul> | ||||
|       <ul class="nav pull-right"> | ||||
|         <li class="dropdown"> | ||||
|           <a href="#" class="dropdown-toggle" data-toggle="dropdown"> | ||||
|             {{ request.user.username }} | ||||
|           </a> | ||||
|           <ul class="dropdown-menu"> | ||||
|             <li><a href="#">Logout</a></li> | ||||
|           </ul> | ||||
|         </li> | ||||
|       </ul> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										6
									
								
								urls.py
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								urls.py
									
										
									
									
									
								
							|  | @ -11,7 +11,13 @@ admin.autodiscover() | |||
| urlpatterns = patterns('', | ||||
|     url(r'^%s(?P<path>.*)$' % re.escape(settings.STATIC_URL.lstrip('/')), 'django.contrib.staticfiles.views.serve', {'insecure': True}), | ||||
|     url(r'^admin/', include(admin.site.urls)), | ||||
|     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'), | ||||
|     url(r'', include('social_auth.urls')), | ||||
| ) | ||||
|  |  | |||
							
								
								
									
										23
									
								
								views.py
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								views.py
									
										
									
									
									
								
							|  | @ -1,4 +1,5 @@ | |||
| from django.contrib.auth.decorators import login_required | ||||
| from django.http import HttpResponse | ||||
| from django.shortcuts import redirect, render_to_response | ||||
| from django.template import RequestContext | ||||
| 
 | ||||
|  | @ -7,12 +8,12 @@ def index(request): | |||
|     '''Index page. Everyone starts here. If the user is logged in (that is, they | ||||
|     have a session id) return the follower_graph view. Otherwise, render the | ||||
|     index page.''' | ||||
|     if request.session.get('sessionid', False): | ||||
|         return follower_graph(request) | ||||
|     if request.user.is_authenticated(): | ||||
|         return redirect('graph_followers') | ||||
|     # Set a test cookie. When the user clicks the 'Login' button, test and make | ||||
|     # sure this cookie was set properly. | ||||
|     request.session.set_test_cookie() | ||||
|     return render_to_response('login.html', RequestContext(request)) | ||||
|     return render_to_response('index.html', RequestContext(request)) | ||||
| 
 | ||||
| 
 | ||||
| def login(request): | ||||
|  | @ -21,13 +22,21 @@ def login(request): | |||
|     # Make sure the user can accept cookies. | ||||
|     if request.session.test_cookie_worked(): | ||||
|         request.session.delete_test_cookie() | ||||
|         return redirect('/login/github/') | ||||
|         return redirect('socialauth_begin', backend='github') | ||||
|     else: | ||||
|         # During development, I've landed here a lot, despite having cookies | ||||
|         # enabled. So, set the test cookie so that trying to login from here | ||||
|         # actually works. | ||||
|         request.session.set_test_cookie() | ||||
|         # Render an error -- fix your damn cookies! | ||||
|         return render_to_response('login.html', | ||||
|         return render_to_response('index.html', | ||||
|                                   { 'error': "Fix your damn cookies!" }) | ||||
| 
 | ||||
| 
 | ||||
| @login_required | ||||
| def follower_graph(request): | ||||
|     return 'Hello!' | ||||
| def graph_repo(request, user=None, repo=None): | ||||
|     return render_to_response('graph_repo.html', { | ||||
|         'graph_user': user, | ||||
|         'graph_repo': repo, | ||||
|         'repos': _sorted_repos(request) | ||||
|     }, RequestContext(request)) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue