From 055e10b97eb591b441f716a78e0aa510210132af Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Sat, 18 Aug 2012 17:57:24 +0200 Subject: [PATCH 1/3] updates the ghuser on login --- ghapi/api.py | 8 +- githubnetwork/admin.py | 7 ++ githubnetwork/management/__init__.py | 1 + githubnetwork/management/commands/__init__.py | 1 + .../management/commands/make_admin.py | 8 ++ .../management/commands/revoke_admin.py | 8 ++ githubnetwork/migrations/0001_initial.py | 80 ++++++------ githubnetwork/models.py | 114 ++++++++++++++---- settings.py | 1 + 9 files changed, 166 insertions(+), 62 deletions(-) create mode 100644 githubnetwork/admin.py create mode 100644 githubnetwork/management/__init__.py create mode 100644 githubnetwork/management/commands/__init__.py create mode 100644 githubnetwork/management/commands/make_admin.py create mode 100644 githubnetwork/management/commands/revoke_admin.py diff --git a/ghapi/api.py b/ghapi/api.py index ec43902..599d9bb 100644 --- a/ghapi/api.py +++ b/ghapi/api.py @@ -25,8 +25,11 @@ class GitHub(object): """ Gets a resource, eg 'users/ojii'. - Returns tuple (jsondata, response) + Returns parsed json data as a python dictionary """ + return self._get(path, params)[0] + + def _get(self, path, params=None): if params is None: params = {} params['per_page'] = 100 @@ -34,12 +37,13 @@ class GitHub(object): response.raise_for_status() return response.json, response + def get_iter(self, path, params=None): """ Returns an iterator over a resource, eg 'repos/divio/django-cms/watchers' that automatically handles pagination. """ - data, response = self.get(path, params) + data, response = self._get(path, params) for thing in data: yield thing next_page = get_next_page(response) diff --git a/githubnetwork/admin.py b/githubnetwork/admin.py new file mode 100644 index 0000000..07729db --- /dev/null +++ b/githubnetwork/admin.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin +from githubnetwork.models import GHUser, Repo + + +admin.site.register(GHUser) +admin.site.register(Repo) diff --git a/githubnetwork/management/__init__.py b/githubnetwork/management/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/githubnetwork/management/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/githubnetwork/management/commands/__init__.py b/githubnetwork/management/commands/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/githubnetwork/management/commands/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/githubnetwork/management/commands/make_admin.py b/githubnetwork/management/commands/make_admin.py new file mode 100644 index 0000000..fd7b98c --- /dev/null +++ b/githubnetwork/management/commands/make_admin.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + def handle(self, username, **option): + User.objects.filter(username=username).update(is_staff=True, is_superuser=True) diff --git a/githubnetwork/management/commands/revoke_admin.py b/githubnetwork/management/commands/revoke_admin.py new file mode 100644 index 0000000..ede277f --- /dev/null +++ b/githubnetwork/management/commands/revoke_admin.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + def handle(self, username, **option): + User.objects.filter(username=username).update(is_staff=False, is_superuser=False) diff --git a/githubnetwork/migrations/0001_initial.py b/githubnetwork/migrations/0001_initial.py index 42ecd05..413dce8 100644 --- a/githubnetwork/migrations/0001_initial.py +++ b/githubnetwork/migrations/0001_initial.py @@ -12,26 +12,27 @@ class Migration(SchemaMigration): db.create_table('githubnetwork_ghuser', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('last_sync', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True)), - ('created_at', self.gf('django.db.models.fields.DateTimeField')()), - ('acct_type', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('gh_login', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('blog', self.gf('django.db.models.fields.URLField')(max_length=255)), - ('email', self.gf('django.db.models.fields.EmailField')(max_length=255)), - ('avatar_url', self.gf('django.db.models.fields.URLField')(max_length=255)), - ('public_gists', self.gf('django.db.models.fields.IntegerField')()), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True, null=True, blank=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), + ('acct_type', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('gh_login', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), + ('blog', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)), + ('email', self.gf('django.db.models.fields.EmailField')(max_length=255, blank=True)), + ('avatar_url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)), + ('public_gists', self.gf('django.db.models.fields.IntegerField')(default=0)), ('hireable', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('followers_count', self.gf('django.db.models.fields.IntegerField')()), - ('html_url', self.gf('django.db.models.fields.URLField')(max_length=255)), - ('bio', self.gf('django.db.models.fields.TextField')()), - ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('company', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('url', self.gf('django.db.models.fields.URLField')(max_length=255)), - ('gravatar_id', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('gh_id', self.gf('django.db.models.fields.IntegerField')()), - ('public_repos', self.gf('django.db.models.fields.IntegerField')()), - ('following_count', self.gf('django.db.models.fields.IntegerField')()), - ('location', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('followers_count', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('html_url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)), + ('bio', self.gf('django.db.models.fields.TextField')(blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('company', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)), + ('gravatar_id', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('gh_id', self.gf('django.db.models.fields.IntegerField')(default=-1)), + ('public_repos', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('following_count', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('location', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('complete', self.gf('django.db.models.fields.BooleanField')(default=False)), )) db.send_create_signal('githubnetwork', ['GHUser']) @@ -125,29 +126,30 @@ class Migration(SchemaMigration): }, 'githubnetwork.ghuser': { 'Meta': {'object_name': 'GHUser'}, - 'acct_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'avatar_url': ('django.db.models.fields.URLField', [], {'max_length': '255'}), - 'bio': ('django.db.models.fields.TextField', [], {}), - 'blog': ('django.db.models.fields.URLField', [], {'max_length': '255'}), - 'company': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}), - 'followers_count': ('django.db.models.fields.IntegerField', [], {}), - 'following': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'following_rel_+'", 'to': "orm['githubnetwork.GHUser']"}), - 'following_count': ('django.db.models.fields.IntegerField', [], {}), - 'gh_id': ('django.db.models.fields.IntegerField', [], {}), - 'gh_login': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'gravatar_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'acct_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'avatar_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'bio': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'blog': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'company': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'blank': 'True'}), + 'followers_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'following': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'following_rel_+'", 'blank': 'True', 'to': "orm['githubnetwork.GHUser']"}), + 'following_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'gh_id': ('django.db.models.fields.IntegerField', [], {'default': '-1'}), + 'gh_login': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'gravatar_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'hireable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'html_url': ('django.db.models.fields.URLField', [], {'max_length': '255'}), + 'html_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'last_sync': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'location': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'public_gists': ('django.db.models.fields.IntegerField', [], {}), - 'public_repos': ('django.db.models.fields.IntegerField', [], {}), - 'url': ('django.db.models.fields.URLField', [], {'max_length': '255'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + 'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'public_gists': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'public_repos': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) }, 'githubnetwork.repo': { 'Meta': {'object_name': 'Repo'}, diff --git a/githubnetwork/models.py b/githubnetwork/models.py index 32a1e0c..866dc54 100644 --- a/githubnetwork/models.py +++ b/githubnetwork/models.py @@ -1,4 +1,5 @@ import datetime +from django.contrib.auth.signals import user_logged_in from django.db import models from django.contrib.auth.models import User @@ -9,33 +10,94 @@ class BaseAPIModel(models.Model): class Meta: abstract = True + def needs_refresh(self): + now = datetime.datetime.now() + return now - self.last_sync > datetime.timedelta(days=7) + + def refresh(self, api): + """ + Refresh the data on this object + """ + pass # TODO + + +class GHUserManager(models.Manager): + def create_from_api(self, username, api): + obj = self.model(complete=True, gh_login=username) + obj.refresh(api) + return obj + class GHUser(BaseAPIModel): - user = models.ForeignKey(User, unique=True) - following = models.ManyToManyField('self', related_name='followers') - created_at = models.DateTimeField('date account created') - acct_type = models.CharField(max_length=255) - gh_login = models.CharField(max_length=255) - blog = models.URLField(max_length=255) - email = models.EmailField(max_length=255) - avatar_url = models.URLField(max_length=255) - public_gists = models.IntegerField() - hireable = models.BooleanField() - followers_count = models.IntegerField() - html_url = models.URLField(max_length=255) - bio = models.TextField() - name = models.CharField(max_length=255) - company = models.CharField(max_length=255) - url = models.URLField(max_length=255) - gravatar_id = models.CharField(max_length=255) - gh_id = models.IntegerField() - public_repos = models.IntegerField() - following_count = models.IntegerField() - location = models.CharField(max_length=255) + user = models.ForeignKey(User, unique=True, null=True, blank=True) + following = models.ManyToManyField('self', related_name='followers', blank=True) + created_at = models.DateTimeField('date account created', blank=True, null=True ) + acct_type = models.CharField(max_length=255, blank=True) + gh_login = models.CharField(max_length=255, unique=True) + blog = models.URLField(max_length=255, blank=True) + email = models.EmailField(max_length=255, blank=True) + avatar_url = models.URLField(max_length=255, blank=True) + public_gists = models.IntegerField(default=0) + hireable = models.BooleanField(default=False) + followers_count = models.IntegerField(default=0) + html_url = models.URLField(max_length=255, blank=True) + bio = models.TextField(blank=True) + name = models.CharField(max_length=255, blank=True) + company = models.CharField(max_length=255, blank=True) + url = models.URLField(max_length=255, blank=True) + gravatar_id = models.CharField(max_length=255, blank=True) + gh_id = models.IntegerField(default=-1) + public_repos = models.IntegerField(default=0) + following_count = models.IntegerField(default=0) + location = models.CharField(max_length=255, blank=True) + complete = models.BooleanField(default=False) + + objects = GHUserManager() def __unicode__(self): return self.gh_login + def _translate(self, data): + data['acct_type'] = data.pop('type', '') + data['followers_count'] = data.pop('followers', '0') + data['following_count'] = data.pop('following', '0') + data['gh_login'] = data.pop('login') + data['gh_id'] = data.pop('id', '0') + return data + + def refresh(self, api): + data = api.get('users/%s' % self.gh_login) + data = self._translate(data) + for key, value in data.items(): + setattr(self, key, value) + self.complete = True + self.save() + # set followers/following + followers = [] + cache = {} + for shortuser in api.get_iter('users/%s/followers' % self.gh_login): + follower = cache.get(shortuser['login'], None) + if not follower: + try: + follower = GHUser.objects.get(gh_login=shortuser['login']) + except self.DoesNotExist: + follower = GHUser.objects.create(**self._translate(shortuser)) + followers.append(follower) + cache[follower.gh_login] = follower + self.followers = followers + following = [] + for shortuser in api.get_iter('users/%s/following' % self.gh_login): + follower = cache.get(shortuser['login'], None) + if not follower: + try: + follower = GHUser.objects.get(gh_login=shortuser['login']) + except self.DoesNotExist: + follower = GHUser.objects.create(**self._translate(shortuser)) + following.append(follower) + self.following = following + return self + + class Repo(BaseAPIModel): owner = models.ForeignKey(GHUser, unique=True) forks = models.IntegerField() @@ -64,3 +126,13 @@ class Repo(BaseAPIModel): def __unicode__(self): return self.name + +def update_user(request, user, **kwargs): + try: + ghuser = GHUser.objects.get(gh_login=user.username) + except GHUser.DoesNotExist: + return GHUser.objects.create_from_api(user.username, request.github) + ghuser.user = user + ghuser.refresh(request.github) + +user_logged_in.connect(update_user) diff --git a/settings.py b/settings.py index 79d9ef8..c67469f 100644 --- a/settings.py +++ b/settings.py @@ -17,6 +17,7 @@ assert 'SECRET_KEY' in os.environ, 'Set SECRET_KEY in your .env file!' SECRET_KEY = os.environ['SECRET_KEY'] USE_L10N = USE_I18N = False +USE_TZ = True MEDIA_ROOT = os.path.join(PROJECT_DIR, 'media') MEDIA_URL = '/media/' From 1e41d2cfee9c21ca3c628012778e06570e130c53 Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Sat, 18 Aug 2012 18:05:15 +0200 Subject: [PATCH 2/3] disable normal model auth backend use make_admin and revoke_admin management commands instead --- settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/settings.py b/settings.py index c67469f..b66fea8 100644 --- a/settings.py +++ b/settings.py @@ -82,7 +82,6 @@ INSTALLED_APPS = [ AUTHENTICATION_BACKENDS = [ 'social_auth.backends.contrib.github.GithubBackend', - 'django.contrib.auth.backends.ModelBackend', ] LOGIN_URL = '/login/' From 903be7f383fd0dfed713a2e375ea4a9d3be376f8 Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Sat, 18 Aug 2012 18:12:49 +0200 Subject: [PATCH 3/3] incomplete objects always need refresh --- githubnetwork/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/githubnetwork/models.py b/githubnetwork/models.py index 866dc54..6274d29 100644 --- a/githubnetwork/models.py +++ b/githubnetwork/models.py @@ -12,7 +12,7 @@ class BaseAPIModel(models.Model): def needs_refresh(self): now = datetime.datetime.now() - return now - self.last_sync > datetime.timedelta(days=7) + return not self.complete or now - self.last_sync > datetime.timedelta(days=7) def refresh(self, api): """