]> git.parisson.com Git - django-social-auth.git/commitdiff
Implemented working alternate storage backend, based on MongoEngine.
authorSteven Cummings <estebistec@gmail.com>
Fri, 22 Jun 2012 19:23:35 +0000 (14:23 -0500)
committerSteven Cummings <estebistec@gmail.com>
Fri, 22 Jun 2012 19:23:35 +0000 (14:23 -0500)
It doesn't necessarily need to live in the primary codebase, it is simply a proof of the original idea. With the appearance of a fair number of helper methods to decouple social auth from django models, it really is looking like a backend is in order. More details:

+ Add MongoEngine models
* Add several methods to both models modules to decouple social auth core and default pipelines from Django ORM code
* In social pipelines, make sure uid is a str value
* Only load django admin settings if current models are django models
+ Add a conf module to manage loading specific Django settings and default values to use

social_auth/admin.py
social_auth/backends/__init__.py
social_auth/backends/pipeline/__init__.py
social_auth/backends/pipeline/associate.py
social_auth/backends/pipeline/social.py
social_auth/backends/pipeline/user.py
social_auth/conf.py [new file with mode: 0644]
social_auth/context_processors.py
social_auth/django_models.py
social_auth/models.py
social_auth/mongoengine_models.py [new file with mode: 0644]

index ee42c41511f221af7eb42d53e56e598d78c4ef21..fad8d78b25f15970aa74d86c70f3838c213d4225 100644 (file)
@@ -1,31 +1,38 @@
 """Admin settings"""
-from django.contrib import admin
 
-from social_auth.models import UserSocialAuth, Nonce, Association
 
+from social_auth import conf
 
-class UserSocialAuthOption(admin.ModelAdmin):
-    """Social Auth user options"""
-    list_display = ('id', 'user', 'provider', 'uid')
-    search_fields = ('user__first_name', 'user__last_name', 'user__email')
-    list_filter = ('provider',)
-    raw_id_fields = ('user',)
-    list_select_related = True
 
+if conf.get_models_module().NAME == 'django_models':
 
-class NonceOption(admin.ModelAdmin):
-    """Nonce options"""
-    list_display = ('id', 'server_url', 'timestamp', 'salt')
-    search_fields = ('server_url',)
+    from django.contrib import admin
 
+    from social_auth.models import UserSocialAuth, Nonce, Association
 
-class AssociationOption(admin.ModelAdmin):
-    """Association options"""
-    list_display = ('id', 'server_url', 'assoc_type')
-    list_filter = ('assoc_type',)
-    search_fields = ('server_url',)
 
+    class UserSocialAuthOption(admin.ModelAdmin):
+        """Social Auth user options"""
+        list_display = ('id', 'user', 'provider', 'uid')
+        search_fields = ('user__first_name', 'user__last_name', 'user__email')
+        list_filter = ('provider',)
+        raw_id_fields = ('user',)
+        list_select_related = True
 
-admin.site.register(UserSocialAuth, UserSocialAuthOption)
-admin.site.register(Nonce, NonceOption)
-admin.site.register(Association, AssociationOption)
+
+    class NonceOption(admin.ModelAdmin):
+        """Nonce options"""
+        list_display = ('id', 'server_url', 'timestamp', 'salt')
+        search_fields = ('server_url',)
+
+
+    class AssociationOption(admin.ModelAdmin):
+        """Association options"""
+        list_display = ('id', 'server_url', 'assoc_type')
+        list_filter = ('assoc_type',)
+        search_fields = ('server_url',)
+
+
+    admin.site.register(UserSocialAuth, UserSocialAuthOption)
+    admin.site.register(Nonce, NonceOption)
+    admin.site.register(Association, AssociationOption)
index fb439a400d74f3d9741a9bb9c856653ddcd76d7c..59c47517efa4712307390481adff4af4045db85e 100644 (file)
@@ -25,6 +25,7 @@ from django.contrib.auth.backends import ModelBackend
 from django.utils import simplejson
 from django.utils.importlib import import_module
 
+from social_auth.models import get_social_auth_for_user
 from social_auth.utils import setting, log, model_to_ctype, ctype_to_model, \
                               clean_partial_pipeline
 from social_auth.store import DjangoOpenIDStore
@@ -35,10 +36,9 @@ from social_auth.backends.exceptions import StopPipeline, AuthException, \
 from social_auth.backends.utils import build_consumer_oauth_request
 
 
-if setting('SOCIAL_AUTH_USER_MODEL'):
-    User = models.get_model(*setting('SOCIAL_AUTH_USER_MODEL').rsplit('.', 1))
-else:
-    from django.contrib.auth.models import User
+def get_user_model():
+    from social_auth.models import User
+    return User
 
 
 # OpenID configuration
@@ -191,12 +191,14 @@ class SocialAuthBackend(ModelBackend):
             return {}
 
     def get_user(self, user_id):
+        
         """
         Return user with given ID from the User model used by this backend
         """
+        user_cls = get_user_model()
         try:
-            return User.objects.get(pk=user_id)
-        except User.DoesNotExist:
+            return user_cls.objects.get(id=user_id)
+        except user_cls.DoesNotExist:
             return None
 
 
@@ -412,9 +414,9 @@ class BaseAuth(object):
         Override if extra operations are needed.
         """
         if association_id:
-            user.social_auth.get(id=association_id).delete()
+            get_social_auth_for_user(user).get(id=association_id).delete()
         else:
-            user.social_auth.filter(provider=self.AUTH_BACKEND.name).delete()
+            get_social_auth_for_user(user).filter(provider=self.AUTH_BACKEND.name).delete()
 
     def build_absolute_uri(self, path=None):
         """Build absolute URI for given path. Replace http:// schema with
index ae04cded401036096ead9ceafdb40305c61a3862..2866a9984bbbaedfd1304b58e2c62eba2e8941e3 100644 (file)
@@ -10,11 +10,11 @@ import warnings
 from django.conf import settings
 
 from social_auth.models import User
+from social_auth.models import USERNAME, get_username_max_length
 from social_auth.backends import get_backend, PIPELINE
 
 
-USERNAME = 'username'
-USERNAME_MAX_LENGTH = User._meta.get_field(USERNAME).max_length
+USERNAME_MAX_LENGTH = get_username_max_length()
 
 
 def warn_setting(name, func_name):
index aafd2d3bd84b49f52b25a1886c6351a852e0b51b..fd7c850756b65735ef934cf90884eb9071c0d755 100644 (file)
@@ -1,5 +1,3 @@
-from django.core.exceptions import MultipleObjectsReturned
-
 from social_auth.utils import setting
 from social_auth.models import User
 from social_auth.backends.pipeline import warn_setting
@@ -18,7 +16,7 @@ def associate_by_email(details, *args, **kwargs):
         # objects are returned
         try:
             return {'user': User.objects.get(email=email)}
-        except MultipleObjectsReturned:
+        except User.MultipleObjectsReturned:
             raise AuthException(kwargs['backend'], 'Not unique email address.')
         except User.DoesNotExist:
             pass
index dc7d30bc63de4cbf4f75cc65fee83f4706ffd1d3..0920009033d969f047995d607145fa7e41d8f71e 100644 (file)
@@ -14,7 +14,7 @@ def social_auth_user(backend, uid, user=None, *args, **kwargs):
     Raise AuthException if UserSocialAuth entry belongs to another user.
     """
     try:
-        social_user = UserSocialAuth.objects.select_related('user')\
+        social_user = UserSocialAuth.select_related()\
                                             .get(provider=backend.name,
                                                  uid=uid)
     except UserSocialAuth.DoesNotExist:
@@ -37,6 +37,8 @@ def associate_user(backend, user, uid, social_user=None, *args, **kwargs):
         return None
 
     try:
+        if type(uid) is not str:
+            uid = str(uid)
         social = UserSocialAuth.objects.create(user=user, uid=uid,
                                                provider=backend.name)
     except IntegrityError:
index 6cd36768594d9aeff4228f23edb964df6c292532..ea06079b74ea932d5a2f88ef21a4a709dcafc5d2 100644 (file)
@@ -1,7 +1,8 @@
 from uuid import uuid4
 
 from social_auth.utils import setting
-from social_auth.models import User
+from social_auth.models import create_user as create_user_in_db
+from social_auth.models import simple_user_exists
 from social_auth.backends.pipeline import USERNAME, USERNAME_MAX_LENGTH, \
                                           warn_setting
 from social_auth.signals import socialauth_not_registered, \
@@ -9,12 +10,6 @@ from social_auth.signals import socialauth_not_registered, \
                                 pre_update
 
 
-def simple_user_exists(*args, **kwargs):
-    """Return True/False if a User instance exists with the given arguments.
-    Arguments are directly passed to filter() manager method."""
-    return User.objects.filter(*args, **kwargs).exists()
-
-
 def get_username(details, user=None, user_exists=simple_user_exists,
                  *args, **kwargs):
     """Return an username for new user. Return current user username
@@ -73,9 +68,9 @@ def create_user(backend, details, response, uid, username, user=None, *args,
                                        details=details)
         return None
 
-    email = details.get('email')
+    email = details.get('email') or None
     return {
-        'user': User.objects.create_user(username=username, email=email),
+        'user': create_user_in_db(username=username, email=email),
         'is_new': True
     }
 
diff --git a/social_auth/conf.py b/social_auth/conf.py
new file mode 100644 (file)
index 0000000..a3f5d88
--- /dev/null
@@ -0,0 +1,16 @@
+"""Centralized definition of settings"""
+
+
+from django.utils import importlib
+from social_auth.utils import setting
+
+
+SOCIAL_AUTH_MODELS = setting('SOCIAL_AUTH_MODELS', 'social_auth.django_models')
+
+
+def get_models_module():
+    """Load and return the module specified by the SOCIAL_AUTH_MODELS config
+    setting.
+
+    """
+    return importlib.import_module(SOCIAL_AUTH_MODELS)
index 0b1d9188086a0dffb2811e5cff7acc64747f9235..e7d5531726dec2c47b3630bcdf3e427b9fecb97a 100644 (file)
@@ -1,6 +1,7 @@
 from social_auth.backends import get_backends
 from social_auth.utils import group_backend_by_type
 from social_auth.models import User
+from social_auth.models import get_social_auth_for_user
 
 
 # Note: social_auth_backends, social_auth_by_type_backends and
@@ -40,7 +41,7 @@ def social_auth_by_name_backends(request):
 
     if isinstance(user, User) and user.is_authenticated():
         accounts.update((assoc.provider.replace('-', '_'), assoc)
-                            for assoc in user.social_auth.all())
+                            for assoc in get_social_auth_for_user(user))
 
     return {'social_auth': accounts}
 
@@ -65,7 +66,7 @@ def backends_data(user):
     # user comes from request.user usually, on /admin/ it will be an instance
     # of auth.User and this code will fail if a custom User model was defined
     if isinstance(user, User) and user.is_authenticated():
-        associated = user.social_auth.all()
+        associated = get_social_auth_for_user(user)
         not_associated = list(set(available) -
                               set(assoc.provider for assoc in associated))
         values['associated'] = associated
index 2a1cfa3969bec6345c823d978645ce86eae6d441..1fce960a7dbe90957f557b038df1f19031cbcd78 100644 (file)
@@ -1,4 +1,4 @@
-"""Social auth models"""
+"""Django ORM models for Social Auth"""
 from datetime import timedelta
 
 from django.db import models
@@ -7,6 +7,9 @@ from social_auth.fields import JSONField
 from social_auth.utils import setting
 
 
+NAME = 'django_models'
+
+
 # If User class is overridden, it *must* provide the following fields
 # and methods work with django-social-auth:
 #
@@ -22,6 +25,30 @@ else:
     from django.contrib.auth.models import User
 
 
+# TODO make this a complementary config setting to SOCIAL_AUTH_USER_MODEL
+USERNAME = 'username'
+
+
+def get_username_max_length():
+    """Get the max length constraint from the User model username field.
+    """
+    return User._meta.get_field(USERNAME).max_length
+
+
+def simple_user_exists(*args, **kwargs):
+    """Return True/False if a User instance exists with the given arguments.
+    Arguments are directly passed to filter() manager method."""
+    return User.objects.filter(*args, **kwargs).exists()
+
+
+def create_user(*args, **kwargs):
+    return User.objects.create_user(*args, **kwargs)
+
+
+def get_social_auth_for_user(user):
+    return user.social_auth.all()
+
+
 class UserSocialAuth(models.Model):
     """Social Auth association model"""
     user = models.ForeignKey(User, related_name='social_auth')
@@ -62,6 +89,10 @@ class UserSocialAuth(models.Model):
                 pass
         return None
 
+    @classmethod
+    def select_related(cls):
+        return cls.objects.select_related('user')
+
 
 class Nonce(models.Model):
     """One use numbers"""
index 75a66cf0539897f9bf154e19ff8c46aa6a5bf0a8..7fe0ecf3f7405250735d0e3bcac3ee4f60c2db30 100644 (file)
@@ -2,13 +2,10 @@
 # TODO define protocol for implementing modules...
 
 
-from django.conf import settings
-from django.utils import importlib
+from social_auth import conf
 
 
-models_module_name = getattr(settings, 'SOCIAL_AUTH_MODELS',
-        'social_auth.django_models')
-models_module = importlib.import_module(models_module_name)
+models_module = conf.get_models_module()
 
 this_module = globals()
 for key in dir(models_module):
diff --git a/social_auth/mongoengine_models.py b/social_auth/mongoengine_models.py
new file mode 100644 (file)
index 0000000..d6b746d
--- /dev/null
@@ -0,0 +1,122 @@
+"""MongoEngine models for Social Auth
+
+Requires MongoEngine 0.6.10
+
+"""
+# TODO extract common code into base objects/mixins
+
+
+from datetime import timedelta
+from mongoengine import DictField
+from mongoengine import Document
+from mongoengine import IntField
+from mongoengine import ReferenceField
+from mongoengine import StringField
+from social_auth.utils import setting
+
+
+NAME = 'mongoengine_models'
+
+
+# If User class is overridden, it *must* provide the following fields
+# and methods work with django-social-auth:
+#
+#   username   = CharField()
+#   last_login = DateTimeField()
+#   is_active  = BooleanField()
+#   def is_authenticated():
+#       ...
+
+if setting('SOCIAL_AUTH_USER_MODEL'):
+    User = models.get_model(*setting('SOCIAL_AUTH_USER_MODEL').rsplit('.', 1))
+else:
+    from mongoengine.django.auth import User
+
+
+# TODO make this a complementary config setting to SOCIAL_AUTH_USER_MODEL
+USERNAME = 'username'
+
+
+def get_username_max_length():
+    """Get the max length constraint from the User model username field.
+    """
+    return getattr(User, USERNAME).max_length
+
+
+def simple_user_exists(*args, **kwargs):
+    """Return True/False if a User instance exists with the given arguments.
+    Arguments are directly passed to filter() manager method."""
+    return User.objects.filter(*args, **kwargs).count()
+
+
+def create_user(*args, **kwargs):
+    return User.objects.create(*args, **kwargs)
+
+
+def get_social_auth_for_user(user):
+    return UserSocialAuth.objects(user=user)
+
+
+class UserSocialAuth(Document):
+    """Social Auth association model"""
+    user = ReferenceField(User)
+    provider = StringField(max_length=32)
+    uid = StringField(max_length=255, unique_with='provider')
+    extra_data = DictField()
+
+    def __unicode__(self):
+        """Return associated user unicode representation"""
+        return u'%s - %s' % (unicode(self.user), self.provider)
+
+    @classmethod
+    def select_related(cls):
+        return cls.objects #.select_related() No 'user', only provie a depth parameter
+
+    @property
+    def tokens(self):
+        """Return access_token stored in extra_data or None"""
+        # Make import here to avoid recursive imports :-/
+        from social_auth.backends import get_backends
+        backend = get_backends().get(self.provider)
+        if backend:
+            return backend.AUTH_BACKEND.tokens(self)
+        else:
+            return {}
+
+    def expiration_delta(self):
+        """Return saved session expiration seconds if any. Is returned in
+        the form of a timedelta data type. None is returned if there's no
+        value stored or it's malformed.
+        """
+        if self.extra_data:
+            name = setting('SOCIAL_AUTH_EXPIRATION', 'expires')
+            try:
+                return timedelta(seconds=int(self.extra_data.get(name)))
+            except (ValueError, TypeError):
+                pass
+        return None
+
+
+class Nonce(Document):
+    """One use numbers"""
+    server_url = StringField(max_length=255)
+    timestamp = IntField()
+    salt = StringField(max_length=40)
+
+    def __unicode__(self):
+        """Unicode representation"""
+        return self.server_url
+
+
+class Association(Document):
+    """OpenId account association"""
+    server_url = StringField(max_length=255)
+    handle = StringField(max_length=255)
+    secret = StringField(max_length=255)  # Stored base64 encoded
+    issued = IntField()
+    lifetime = IntField()
+    assoc_type = StringField(max_length=64)
+
+    def __unicode__(self):
+        """Unicode representation"""
+        return '%s %s' % (self.handle, self.issued)