Compare commits

...
Sign in to create a new pull request.

46 commits

Author SHA1 Message Date
0dd428a43d Move repo list functions to githubnetwork
Add repos item to context for me view.
2012-08-18 23:25:24 -07:00
35cb9ab6b2 Me link 2012-08-18 23:20:31 -07:00
386c618981 Merge branch 'feature/ui' of github.com:erynofwales/rogueojiiofwales into feature/ui 2012-08-18 22:08:28 -07:00
ebd371b43f Fix indentation in navbar template 2012-08-18 22:03:53 -07:00
dab1b69fb7 Remove some remnants of a bad merge... 2012-08-18 22:03:53 -07:00
9c44419146 Sort repo list 2012-08-18 22:03:53 -07:00
f306488b01 Use a named url instead of the url to reference login view 2012-08-18 22:03:53 -07:00
934ea0a905 Merge pull request #7 from ojii/crazy
Crazy
2012-08-18 22:03:11 -07:00
d0386b475a Fix indentation in navbar template 2012-08-18 18:57:06 -07:00
e2aa534e76 Remove some remnants of a bad merge... 2012-08-18 18:43:17 -07:00
Jonas Obrist
3c57ff9910 minor adjustments 2012-08-19 03:42:58 +02:00
8cd4940215 Sort repo list 2012-08-18 18:40:45 -07:00
Jonas Obrist
0dbc4c21e0 oops, wrong way round 2012-08-19 03:36:09 +02:00
Jonas Obrist
9e1a68aac5 spacing
styling
avatars
2012-08-19 03:34:07 +02:00
dac7766ee7 Use a named url instead of the url to reference login view 2012-08-18 18:27:56 -07:00
Jonas Obrist
0ab9f790fe Merge branch 'master' of github.com:econchick/rogueojiiofwales into crazy
Conflicts:
	urls.py
2012-08-19 03:01:47 +02:00
Jonas Obrist
4bb36f6129 Merge pull request #4 from erynofwales/feature/ui
Followers graph view
2012-08-18 17:48:53 -07:00
6d9f67d3bf Merge pull request #6 from erynofwales/bug/test-cookie
Set test cookie even if cookies are disabled
2012-08-18 17:16:14 -07:00
Jonas Obrist
b456362a47 Merge branch 'master' of github.com:econchick/rogueojiiofwales into crazy
Conflicts:
	githubnetwork/views.py
2012-08-19 02:14:57 +02:00
d3a949833d Merge branch 'feature/ui' of github.com:erynofwales/rogueojiiofwales into feature/ui
Conflicts:
	templates/graph_base.html
	templates/navbar.html
2012-08-18 17:09:56 -07:00
Jonas Obrist
8b988be06e just showing off, don't merge this! 2012-08-19 01:20:08 +02:00
6969af178c Merge remote-tracking branch 'fork/feature/ui' into feature/ui
Conflicts:
	templates/graph_base.html
	templates/navbar.html
	urls.py
	views.py
2012-08-18 16:17:04 -07:00
c389dc5581 Merge remote-tracking branch 'fork/feature/ui' into feature/ui
Conflicts:
	templates/graph_base.html
	templates/navbar.html
	urls.py
	views.py
2012-08-18 16:12:56 -07:00
c0bac128d7 Set test cookie even if cookies are disabled
It doesn't hurt anything, but we've hit this during development a bunch.
2012-08-18 16:06:21 -07:00
e34a4aa15d Fix navbar for loop for generate repo list 2012-08-18 16:03:32 -07:00
72e2ae9290 Round out graph views to generate list of user's repos 2012-08-18 16:02:31 -07:00
cc84293480 Rename graph views 2012-08-18 16:01:59 -07:00
0314a0f71e Rename GitHubRequestContext -> GraphRequestContext 2012-08-18 12:42:23 -07:00
6bb4000d30 Placeholder for repo graph 2012-08-18 12:42:23 -07:00
57ea318fcf Repo graph template 2012-08-18 12:42:23 -07:00
8e2decc27e Dropdown for user's repos 2012-08-18 12:42:23 -07:00
56587aa345 Repo stub view 2012-08-18 12:42:23 -07:00
e044dbf19a Dropdown menu for user stuff 2012-08-18 12:42:22 -07:00
2184fab678 Rename follower_graph to graph_followers
Create graph_followers template
Add urlconf for graph_followers
Redirect use to followers view instead of just calling it
2012-08-18 12:42:22 -07:00
068403f81f Render the graph template when rendering followers view 2012-08-18 12:42:22 -07:00
2da6b7395c Graph base template 2012-08-18 12:42:22 -07:00
529c932e33 Navbar 2012-08-18 12:42:22 -07:00
fdb900a8a3 Check if user is authenticated and show follower_graph 2012-08-18 12:42:22 -07:00
d2fc4ca84b Move login template to index.html 2012-08-18 12:42:22 -07:00
5435ed5789 Set test cookie even if cookies are disabled
It doesn't hurt anything, but we've hit this during development a bunch.
2012-08-18 12:41:03 -07:00
99f37ede03 Rename follower_graph to graph_followers
Create graph_followers template
Add urlconf for graph_followers
Redirect use to followers view instead of just calling it
2012-08-18 09:52:40 -07:00
127ae209b1 Render the graph template when rendering followers view 2012-08-18 09:49:08 -07:00
1fb1bec70e Graph base template 2012-08-18 09:48:10 -07:00
41718d128a Navbar 2012-08-18 09:47:09 -07:00
2d81fb0143 Check if user is authenticated and show follower_graph 2012-08-18 09:27:56 -07:00
f4218775ae Move login template to index.html 2012-08-18 09:23:50 -07:00
14 changed files with 7324 additions and 51 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,77 @@ 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)
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')

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

24
templates/graph_base.html Normal file
View 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 %}

View file

@ -0,0 +1,2 @@
{# vim: set ft=htmldjango #}
{% extends "graph_base.html" %}

View 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 %}

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 %}

29
templates/navbar.html Normal file
View 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>

View file

@ -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')),
)

View file

@ -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))