From 18f23a7d9304fb1b62c3d292c324bdcf71775953 Mon Sep 17 00:00:00 2001 From: Alen Mujezinovic Date: Tue, 4 Oct 2011 11:17:13 +0100 Subject: [PATCH] Added foursquare support --- example/app/views.py | 4 +- example/settings.py | 9 ++- example/templates/index.html | 4 +- socialregistration/clients/oauth.py | 47 ++++++++------- .../contrib/foursquare/__init__.py | 0 socialregistration/contrib/foursquare/auth.py | 12 ++++ .../contrib/foursquare/client.py | 59 +++++++++++++++++++ .../contrib/foursquare/models.py | 36 +++++++++++ .../foursquare/foursquare.html | 3 + .../foursquare/foursquare_button.html | 7 +++ .../foursquare/templatetags/__init__.py | 0 .../foursquare/templatetags/foursquare.py | 6 ++ socialregistration/contrib/foursquare/urls.py | 11 ++++ .../contrib/foursquare/views.py | 24 ++++++++ socialregistration/contrib/github/client.py | 4 +- socialregistration/urls.py | 7 ++- 16 files changed, 207 insertions(+), 26 deletions(-) create mode 100644 socialregistration/contrib/foursquare/__init__.py create mode 100644 socialregistration/contrib/foursquare/auth.py create mode 100644 socialregistration/contrib/foursquare/client.py create mode 100644 socialregistration/contrib/foursquare/models.py create mode 100644 socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare.html create mode 100644 socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare_button.html create mode 100644 socialregistration/contrib/foursquare/templatetags/__init__.py create mode 100644 socialregistration/contrib/foursquare/templatetags/foursquare.py create mode 100644 socialregistration/contrib/foursquare/urls.py create mode 100644 socialregistration/contrib/foursquare/views.py diff --git a/example/app/views.py b/example/app/views.py index 0cb219a..bb3c19e 100644 --- a/example/app/views.py +++ b/example/app/views.py @@ -2,6 +2,7 @@ from django.shortcuts import render_to_response from django.template import RequestContext from socialregistration.contrib.facebook.models import FacebookProfile +from socialregistration.contrib.foursquare.models import FoursquareProfile from socialregistration.contrib.github.models import GithubProfile from socialregistration.contrib.linkedin.models import LinkedInProfile from socialregistration.contrib.openid.models import OpenIDProfile @@ -14,5 +15,6 @@ def index(request): twitter=TwitterProfile.objects.all(), openid=OpenIDProfile.objects.all(), linkedin=LinkedInProfile.objects.all(), - github = GithubProfile.objects.all(), + github=GithubProfile.objects.all(), + foursquare=FoursquareProfile.objects.all(), ), context_instance=RequestContext(request)) diff --git a/example/settings.py b/example/settings.py index d58da40..3a99fc9 100644 --- a/example/settings.py +++ b/example/settings.py @@ -117,6 +117,7 @@ 'socialregistration.contrib.linkedin', 'socialregistration.contrib.github', 'socialregistration.contrib.facebook', + 'socialregistration.contrib.foursquare', 'example.app', ) @@ -153,6 +154,7 @@ 'socialregistration.contrib.linkedin.auth.LinkedInAuth', 'socialregistration.contrib.github.auth.GithubAuth', 'socialregistration.contrib.facebook.auth.FacebookAuth', + 'socialregistration.contrib.foursquare.auth.FoursquareAuth', ) MIDDLEWARE_CLASSES = ( @@ -189,9 +191,14 @@ # Add your Github API keys here GITHUB_CLIENT_ID = '' -GITHUB_SECRET = '' +GITHUB_CLIENT_SECRET = '' GITHUB_REQUEST_PERMISSIONS = '' +# Add your Foursquare API keys here +FOURSQUARE_CLIENT_ID = '' +FOURSQUARE_CLIENT_SECRET = '' +FOURSQUARE_REQUEST_PERMISSIONS = '' + SOCIALREGISTRATION_USE_HTTPS = False SOCIALREGISTRATION_GENERATE_USERNAME = False diff --git a/example/templates/index.html b/example/templates/index.html index c1d010e..591bca7 100644 --- a/example/templates/index.html +++ b/example/templates/index.html @@ -7,12 +7,13 @@

django-socialregistration login

Hi, {{ request.user }}

{% endif %} -{% load facebook twitter openid linkedin github %} +{% load facebook twitter openid linkedin github foursquare %}
  1. {% facebook_button %}
  2. {% twitter_button %}
  3. {% linkedin_button %}
  4. {% github_button %}
  5. +
  6. {% foursquare_button %}
  7. {% openid_form %}
  8. @@ -30,6 +31,7 @@

    Connected profiles:

  9. Twitter: {{ twitter.count }}
  10. LinkedIn: {{ linkedin.count }}
  11. Github: {{ github.count }}
  12. +
  13. Foursquare: {{ foursquare.count }}
  14. OpenID: {{ openid.count }}
diff --git a/socialregistration/clients/oauth.py b/socialregistration/clients/oauth.py index 091e3e1..12f204e 100644 --- a/socialregistration/clients/oauth.py +++ b/socialregistration/clients/oauth.py @@ -120,16 +120,16 @@ class OAuth2(Client): _access_token = None - def __init__(self, access_token = None): + def __init__(self, access_token=None): self._access_token = access_token def client(self): return httplib2.Http() - def get_redirect_url(self,state = ''): + def get_redirect_url(self, state=''): params = { 'response_type': 'code', - 'client_id': self.client_id, + 'client_id': self.client_id, 'redirect_uri': self.get_callback_url(), 'scope': self.scope or '', 'state': state, @@ -137,52 +137,59 @@ def get_redirect_url(self,state = ''): return '%s?%s' % (self.auth_url, urllib.urlencode(params)) - def _get_access_token(self, code): - params = { + def parse_access_token(self, content): + return dict(urlparse.parse_qsl(content)) + + def request_access_token(self, params): + return self.request(self.access_token_url, method="POST", params=params) + + def _get_access_token(self, code, **params): + params.update({ 'code': code, 'client_id': self.client_id, 'client_secret': self.secret, 'redirect_uri': self.get_callback_url(), - } + }) - resp, content = self.request(self.access_token_url, method = "POST", - params = params) + resp, content = self.request_access_token(params=params) - content = dict(urlparse.parse_qsl(content)) + content = self.parse_access_token(content) if 'error' in content: raise OAuthError(_( - "Received error while obtaining access token from %s: %s") %( + "Received error while obtaining access token from %s: %s") % ( self.access_token_url, content['error'])) return content - def get_access_token(self, code = None): + def get_access_token(self, code=None, **params): if self._access_token is None: if code is None: raise ValueError(_('Invalid code.')) - self._access_token = self._get_access_token(code)['access_token'] - + self._access_token = self._get_access_token(code, **params)['access_token'] return self._access_token def complete(self, GET): - return self.get_access_token(GET.get('code')) + return self.get_access_token(code=GET.get('code')) + + def get_signing_params(self): + return dict(access_token=self._access_token) - def request(self, url, method = "GET", params = None, headers = None): + def request(self, url, method="GET", params=None, headers=None): params = params or {} headers = headers or {} - params.update(access_token = self._access_token) + params.update(self.get_signing_params()) if method.upper() == "GET": url = '%s?%s' % (url, urllib.urlencode(params)) - return self.client().request(url, method = method, headers = headers) - return self.client().request(url, method, body = urllib.urlencode(params), - headers = headers) + return self.client().request(url, method=method, headers=headers) + return self.client().request(url, method, body=urllib.urlencode(params), + headers=headers) def get_user_info(self): raise NotImplementedError def get_callback_url(self): raise NotImplementedError - \ No newline at end of file + diff --git a/socialregistration/contrib/foursquare/__init__.py b/socialregistration/contrib/foursquare/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/socialregistration/contrib/foursquare/auth.py b/socialregistration/contrib/foursquare/auth.py new file mode 100644 index 0000000..ffcefef --- /dev/null +++ b/socialregistration/contrib/foursquare/auth.py @@ -0,0 +1,12 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.sites.models import Site +from socialregistration.contrib.foursquare.models import FoursquareProfile + +class FoursquareAuth(ModelBackend): + def authenticate(self, foursquare=None): + try: + return FoursquareProfile.objects.get( + foursquare=foursquare, + site=Site.objects.get_current()).user + except FoursquareProfile.DoesNotExist: + return None diff --git a/socialregistration/contrib/foursquare/client.py b/socialregistration/contrib/foursquare/client.py new file mode 100644 index 0000000..2cd6109 --- /dev/null +++ b/socialregistration/contrib/foursquare/client.py @@ -0,0 +1,59 @@ +from django.conf import settings +from django.contrib.sites.models import Site +from django.core.urlresolvers import reverse +from socialregistration.clients.oauth import OAuth2 +import json + + +class Foursquare(OAuth2): + client_id = getattr(settings, 'FOURSQUARE_CLIENT_ID', '') + secret = getattr(settings, 'FOURSQUARE_CLIENT_SECRET', '') + scope = getattr(settings, 'FOURSQUARE_REQUEST_PERMISSIONS', '') + + auth_url = 'https://foursquare.com/oauth2/authorize' + access_token_url = 'https://foursquare.com/oauth2/access_token' + + _user_info = None + + def get_callback_url(self): + if self.is_https(): + return 'https://%s%s' % (Site.objects.get_current().domain, + reverse('socialregistration:foursquare:callback')) + return 'http://%s%s' % (Site.objects.get_current().domain, + reverse('socialregistration:foursquare:callback')) + + def parse_access_token(self, content): + """ + Forsquare returns JSON instead of url encoded data. + """ + return json.loads(content) + + def request_access_token(self, params): + """ + Foursquare does not accept POST requests to retrieve an access token, + so we'll be doing a GET request instead. + """ + return self.request(self.access_token_url, method="GET", params=params) + + def get_access_token(self, **params): + """ + Foursquare requires a `grant_type` parameter when requesting the access + token. + """ + return super(Foursquare, self).get_access_token(grant_type='authorization_code', **params) + + def get_signing_params(self): + """ + Foursquare requires `oauth_token` parameter instead of `access_token` + """ + return dict(oauth_token=self._access_token) + + def get_user_info(self): + if self._user_info is None: + resp, content = self.request('https://api.foursquare.com/v2/users/self') + self._user_info = json.loads(content)['response']['user'] + return self._user_info + + @staticmethod + def get_session_key(): + return 'socialreg:foursquare' diff --git a/socialregistration/contrib/foursquare/models.py b/socialregistration/contrib/foursquare/models.py new file mode 100644 index 0000000..1b3f7fb --- /dev/null +++ b/socialregistration/contrib/foursquare/models.py @@ -0,0 +1,36 @@ +from django.contrib.auth import authenticate +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.db import models +from socialregistration.signals import connect + +class FoursquareProfile(models.Model): + user = models.ForeignKey(User, unique=True) + site = models.ForeignKey(Site, default=Site.objects.get_current) + foursquare = models.CharField(max_length=255) + + def __unicode__(self): + try: + return u'%s: %s' % (self.user, self.foursquare) + except User.DoesNotExist: + return u'None' + + def authenticate(self): + return authenticate(foursquare=self.foursquare) + +class FoursquareAccessToken(models.Model): + profile = models.OneToOneField(FoursquareProfile, related_name='access_token') + access_token = models.CharField(max_length=255) + +def save_foursquare_token(sender, user, profile, client, **kwargs): + try: + FoursquareAccessToken.objects.get(profile=profile).delete() + except FoursquareAccessToken.DoesNotExist: + pass + + FoursquareAccessToken.objects.create(access_token=client.get_access_token(), + profile=profile) + + +connect.connect(save_foursquare_token, sender=FoursquareProfile, + dispatch_uid='socialregistration_github_token') diff --git a/socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare.html b/socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare.html new file mode 100644 index 0000000..a87797a --- /dev/null +++ b/socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare.html @@ -0,0 +1,3 @@ +{% if error %} +

{{ error }}

+{% endif %} \ No newline at end of file diff --git a/socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare_button.html b/socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare_button.html new file mode 100644 index 0000000..f3a7001 --- /dev/null +++ b/socialregistration/contrib/foursquare/templates/socialregistration/foursquare/foursquare_button.html @@ -0,0 +1,7 @@ + + {% csrf_token %} + {% if next %} + + {% endif %} + + diff --git a/socialregistration/contrib/foursquare/templatetags/__init__.py b/socialregistration/contrib/foursquare/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/socialregistration/contrib/foursquare/templatetags/foursquare.py b/socialregistration/contrib/foursquare/templatetags/foursquare.py new file mode 100644 index 0000000..5293a00 --- /dev/null +++ b/socialregistration/contrib/foursquare/templatetags/foursquare.py @@ -0,0 +1,6 @@ +from django import template +from socialregistration.templatetags import button + +register = template.Library() + +register.tag('foursquare_button', button('socialregistration/foursquare/foursquare_button.html')) diff --git a/socialregistration/contrib/foursquare/urls.py b/socialregistration/contrib/foursquare/urls.py new file mode 100644 index 0000000..173b680 --- /dev/null +++ b/socialregistration/contrib/foursquare/urls.py @@ -0,0 +1,11 @@ +from django.conf import settings +from django.conf.urls.defaults import * +from socialregistration.contrib.foursquare.views import FoursquareRedirect, \ + FoursquareCallback, FoursquareSetup + + +urlpatterns = patterns('', + url('^redirect/$', FoursquareRedirect.as_view(), name='redirect'), + url('^callback/$', FoursquareCallback.as_view(), name='callback'), + url('^setup/$', FoursquareSetup.as_view(), name='setup'), +) diff --git a/socialregistration/contrib/foursquare/views.py b/socialregistration/contrib/foursquare/views.py new file mode 100644 index 0000000..162dbc9 --- /dev/null +++ b/socialregistration/contrib/foursquare/views.py @@ -0,0 +1,24 @@ +from django.core.urlresolvers import reverse +from socialregistration.contrib.foursquare.client import Foursquare +from socialregistration.contrib.foursquare.models import FoursquareProfile +from socialregistration.views import OAuthRedirect, OAuthCallback, SetupCallback + +class FoursquareRedirect(OAuthRedirect): + client = Foursquare + template_name = 'socialregistration/foursquare/foursquare.html' + +class FoursquareCallback(OAuthCallback): + client = Foursquare + template_name = 'socialregistration/foursquare/foursquare.html' + + def get_redirect(self): + return reverse('socialregistration:foursquare:setup') + +class FoursquareSetup(SetupCallback): + client = Foursquare + profile = FoursquareProfile + template_name = 'socialregistration/foursquare/foursquare.html' + + def get_lookup_kwargs(self, request, client): + return {'foursquare': client.get_user_info()['id']} + diff --git a/socialregistration/contrib/github/client.py b/socialregistration/contrib/github/client.py index 0a3f6d0..e70c1ad 100644 --- a/socialregistration/contrib/github/client.py +++ b/socialregistration/contrib/github/client.py @@ -7,7 +7,7 @@ class Github(OAuth2): client_id = getattr(settings, 'GITHUB_CLIENT_ID', '') - secret = getattr(settings, 'GITHUB_SECRET', '') + secret = getattr(settings, 'GITHUB_CLIENT_SECRET', '') scope = getattr(settings, 'GITHUB_REQUEST_PERMISSIONS', '') auth_url = 'https://github.com/login/oauth/authorize' @@ -24,7 +24,7 @@ def get_callback_url(self): def get_user_info(self): if self._user_info is None: - resp, content = self.request('https://api.github.com/user') + resp, content = self.request('https://api.github.com/user') self._user_info = json.loads(content) return self._user_info diff --git a/socialregistration/urls.py b/socialregistration/urls.py index 8028926..4abfa7a 100644 --- a/socialregistration/urls.py +++ b/socialregistration/urls.py @@ -27,7 +27,12 @@ if 'socialregistration.contrib.github' in settings.INSTALLED_APPS: urlpatterns = urlpatterns + patterns('', url(r'^github/', include('socialregistration.contrib.github.urls', - namespace = 'github'))) + namespace='github'))) + +if 'socialregistration.contrib.foursquare' in settings.INSTALLED_APPS: + urlpatterns = urlpatterns + patterns('', + url(r'^foursquare/', include('socialregistration.contrib.foursquare.urls', + namespace='foursquare'))) urlpatterns = urlpatterns + patterns('', url(r'^setup/$', Setup.as_view(), name='setup'),