]> git.parisson.com Git - django-social-auth.git/commitdiff
Improvements and documentation on custom User class definition.
authorMatías Aguirre <matiasaguirre@gmail.com>
Tue, 23 Nov 2010 05:36:09 +0000 (03:36 -0200)
committerMatías Aguirre <matiasaguirre@gmail.com>
Tue, 23 Nov 2010 05:37:19 +0000 (03:37 -0200)
Closes #2

README.rst
example/app/models.py [new file with mode: 0644]
example/local_settings.py.template
example/settings.py
social_auth/backends.py
social_auth/base.py
social_auth/conf.py
social_auth/models.py

index 9dbbea3b24123aabc006f95372a1a0e3d0280810..ed364daf986fc5a61ef9548d9a8f0159e3469604 100644 (file)
@@ -86,10 +86,37 @@ or::
 
 final user name will have an integer suffix in case it's already taken.
 
-- OAuth authentication will store access_token by default, set this value to False to avoid such behavior::
+- OAuth authentication will store access_token by default, set this value
+  to False to avoid such behavior::
 
     SOCIAL_AUTH_EXTRA_DATA = False
 
+- It's possible to override the used User class if needed::
+
+    SOCIAL_AUTH_USER_MODEL = 'myapp.CustomUser'
+
+this class must define the following fields::
+
+    username   = CharField(...)
+    email      = EmailField(...)
+    password   = CharField(...)
+    last_login = DateTimeField(blank=True)
+    is_active  = BooleanField(...)
+
+and the methods::
+
+    is_authenticated()
+
+AttributeError will be raised in case of any of these is
+missing, also the following are recommended but notenforced::
+
+    first_name = CharField(...)
+    last_name  = CharField(...)
+
+by default `auth.User`_ is used. Check example application for
+implementation details, but first, please take a look to `User Profiles`_,
+it might solve your case.
+
 
 ------
 OpenId
@@ -147,6 +174,15 @@ Bugs
 Several, maybe, please report :-)
 
 
+------------
+Contributors
+------------
+
+Attributions to whom deserves:
+
+- caioariede_ (Caio Ariede) 
+
+
 ----------
 Copyrights
 ----------
@@ -175,3 +211,6 @@ django-openid-auth::
 .. _Facebook development resources: http://developers.facebook.com/docs/authentication/
 .. _Facebook App Creation: http://developers.facebook.com/setup/
 .. _AUTHENTICATION_BACKENDS: http://docs.djangoproject.com/en/dev/ref/settings/?from=olddocs#authentication-backends
+.. _auth.User: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py#L186
+.. _User Profiles: http://www.djangobook.com/en/1.0/chapter12/#cn222
+.. _caioariede: https://github.com/caioariede
diff --git a/example/app/models.py b/example/app/models.py
new file mode 100644 (file)
index 0000000..45ecf24
--- /dev/null
@@ -0,0 +1,15 @@
+# Define a custom User class to work with django-social-auth
+#
+# from django.db import models
+# 
+# class CustomUser(models.Model):
+#     username = models.CharField(max_length=128)
+#     email = models.EmailField()
+#     password = models.CharField(max_length=128)
+#     last_login = models.DateTimeField(blank=True, null=True)
+#     first_name = models.CharField(max_length=128, blank=True)
+#     last_name = models.CharField(max_length=128, blank=True)
+#     is_active = models.BooleanField(default=True)
+# 
+#     def is_authenticated(self):
+#         return True
index 8a478bfb882ef2d18dd69fdd8e11411994372580..f6726faf5879659f35e6fbb211dd62d2429bf4d9 100644 (file)
@@ -7,3 +7,4 @@ SOCIAL_AUTH_FORCE_RANDOM_USERNAME = False
 SOCIAL_AUTH_DEFAULT_USERNAME      = 'socialauth_user'
 SOCIAL_AUTH_COMPLETE_URL_NAME     = 'social:complete'
 LOGIN_ERROR_URL                   = '/login/error/'
+#SOCIAL_AUTH_USER_MODEL            = 'app.CustomUser'
index 69cbe16c8a6ef34f44493a5249d8e32185f37521..c99965eb0f5a3684e4c049c194fab5e2986a8137 100644 (file)
@@ -89,6 +89,7 @@ INSTALLED_APPS = (
     'django.contrib.messages',
     'django.contrib.admin',
     'social_auth',
+    'app',
 )
 
 AUTHENTICATION_BACKENDS = (
index dcf357106f800e8fe2d8a635647b686d63df58f8..c73e70c67e92ff59281bc9206e39a488f208306a 100644 (file)
@@ -32,8 +32,8 @@ class TwitterBackend(OAuthBackend):
         return {'email': '', # not supplied
                 'username': response['screen_name'],
                 'fullname': response['name'],
-                'firstname': response['name'],
-                'lastname': ''}
+                'first_name': response['name'],
+                'last_name': ''}
 
 
 class FacebookBackend(OAuthBackend):
@@ -50,8 +50,8 @@ class FacebookBackend(OAuthBackend):
         return {'email': response.get('email', ''),
                 'username': response['name'],
                 'fullname': response['name'],
-                'firstname': response.get('first_name', ''),
-                'lastname': response.get('last_name', '')}
+                'first_name': response.get('first_name', ''),
+                'last_name': response.get('last_name', '')}
 
        
 class OpenIDBackend(SocialAuthBackend):
@@ -72,8 +72,8 @@ class OpenIDBackend(SocialAuthBackend):
         values = {'email': '',
                   'username': '',
                   'fullname': '',
-                  'firstname': '',
-                  'lastname': ''}
+                  'first_name': '',
+                  'last_name': ''}
 
         resp = sreg.SRegResponse.fromSuccessResponse(response)
         if resp:
@@ -87,20 +87,20 @@ class OpenIDBackend(SocialAuthBackend):
                             for src, alias in OLD_AX_ATTRS + AX_SCHEMA_ATTRS)
 
         fullname = values.get('fullname') or ''
-        firstname = values.get('firstname') or ''
-        lastname = values.get('lastname') or ''
+        first_name = values.get('first_name') or ''
+        last_name = values.get('last_name') or ''
 
-        if not fullname and firstname and lastname:
-            fullname = firstname + ' ' + lastname
+        if not fullname and first_name and last_name:
+            fullname = first_name + ' ' + last_name
         elif fullname:
             try: # Try to split name for django user storage
-                firstname, lastname = fullname.rsplit(' ', 1)
+                first_name, last_name = fullname.rsplit(' ', 1)
             except ValueError:
-                lastname = fullname
+                last_name = fullname
 
         values.update({'fullname': fullname,
-                       'firstname': firstname,
-                       'lastname': lastname,
+                       'first_name': first_name,
+                       'last_name': last_name,
                        'username': values.get('username') or \
-                                   (firstname.title() + lastname.title())})
+                                   (first_name.title() + last_name.title())})
         return values
index 76d152d153b148cd1a83d5d9921ce2b4ed6744fb..ed9929eff98ffcb1a9c6519a62ab5c14f53db5d7 100644 (file)
@@ -4,10 +4,13 @@ import md5
 
 from django.conf import settings
 from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.models import UNUSABLE_PASSWORD
 
-from .models import UserSocialAuth, get_user_model
+from .models import UserSocialAuth
+
+# get User class, could not be auth.User
+User = UserSocialAuth._meta.get_field('user').rel.to
 
-User = get_user_model()
 
 class BaseAuth(object):
     """Base authentication class, new authenticators should subclass
@@ -97,7 +100,14 @@ class SocialAuthBackend(ModelBackend):
     def create_user(self, response, details):
         """Create user with unique username"""
         username = self.get_username(details)
-        user = User.objects.create_user(username, details.get('email', ''))
+        email = details.get('email', '')
+
+        if hasattr(User.objects, 'create_user'): # auth.User
+            user = User.objects.create_user(username, email)
+        else: # create user setting password to an unusable value
+            user = User.objects.create(username=username, email=email,
+                                       password=UNUSABLE_PASSWORD)
+
         self.update_user_details(user, details) # load details
         self.associate_auth(user, response, details) # save account association
         return user
@@ -129,13 +139,16 @@ class SocialAuthBackend(ModelBackend):
 
     def update_user_details(self, user, details):
         """Update user details with new (maybe) data"""
-        first_name = details.get('firstname') or user.first_name
-        last_name = details.get('lastname') or user.last_name
-        email = details.get('email') or user.email
-        if (user.first_name, user.last_name, user.email) != (first_name, last_name, email):
-            user.first_name = first_name
-            user.last_name = last_name
-            user.email = email
+        fields = user._meta.get_all_field_names()
+        changed = False
+
+        for name in ('first_name', 'last_name', 'email'):
+            value = details.get(name)
+            if name in fields and value != getattr(user, name, value):
+                setattr(user, name, value)
+                changed = True
+
+        if changed:
             user.save()
 
     def get_user_id(self, details, response):
@@ -147,7 +160,14 @@ class SocialAuthBackend(ModelBackend):
             {'email': <user email if any>,
              'username': <username if any>,
              'fullname': <user full name if any>,
-             'firstname': <user first name if any>,
-             'lastname': <user last name if any>}
+             'first_name': <user first name if any>,
+             'last_name': <user last name if any>}
         """
         raise NotImplementedError, 'Implement in subclass'
+
+    def get_user(self, user_id):
+        """Return user instance for @user_id"""
+        try:
+            return User.objects.get(pk=user_id)
+        except User.DoesNotExist:
+            return None
index 25dc555fd6b5a180c5cfc9447b0ddf17d8e3a22d..91604f5e5642f24a33f9bec0f50b453e5983e70f 100644 (file)
@@ -24,8 +24,8 @@ AX_SCHEMA_ATTRS = [
     # providers offer one but not the other.
     ('http://axschema.org/contact/email', 'email'),
     ('http://axschema.org/namePerson', 'fullname'),
-    ('http://axschema.org/namePerson/first', 'firstname'),
-    ('http://axschema.org/namePerson/last', 'lastname'),
+    ('http://axschema.org/namePerson/first', 'first_name'),
+    ('http://axschema.org/namePerson/last', 'last_name'),
     ('http://axschema.org/namePerson/friendly', 'nickname'),
 ]
 AX_ATTRS          = AX_SCHEMA_ATTRS + OLD_AX_ATTRS
index 78b797ea1d25773f64ad8216b98e05bd940dd367..325f6ddadbea118c96059c35147e83b65baa0d37 100644 (file)
@@ -2,11 +2,19 @@
 from django.db import models 
 from django.conf import settings
 
-def get_user_model():
-    """Allow setting a custom (extended) user model"""
-    return models.get_model(*getattr(settings, 'SOCIAL_AUTH_USER_MODEL', 'auth.User').split('.'))
+# If User class is overrided, it must provide the following fields:
+#   username = CharField()
+#   email = EmailField()
+#   password = CharField()
+MANDATORY_FIELDS = ('username', 'email', 'password', 'last_login')
+
+try: # try to import User model override and validate needed fields
+    User = models.get_model(*settings.SOCIAL_AUTH_USER_MODEL.split('.'))
+    if not all(User._meta.get_field(name) for name in MANDATORY_FIELDS):
+        raise AttributeError, 'Some mandatory field missing'
+except: # fail silently and fallback to auth.User on any error
+    from django.contrib.auth.models import User
 
-User = get_user_model()
 
 class UserSocialAuth(models.Model):
     """Social Auth association model"""