From be463c854d66d84d609661294c3942393eaa85e9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mat=C3=ADas=20Aguirre?= Date: Tue, 23 Nov 2010 03:36:09 -0200 Subject: [PATCH] Improvements and documentation on custom User class definition. Closes #2 --- README.rst | 41 +++++++++++++++++++++++++++- example/app/models.py | 15 ++++++++++ example/local_settings.py.template | 1 + example/settings.py | 1 + social_auth/backends.py | 30 ++++++++++---------- social_auth/base.py | 44 ++++++++++++++++++++++-------- social_auth/conf.py | 4 +-- social_auth/models.py | 16 ++++++++--- 8 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 example/app/models.py diff --git a/README.rst b/README.rst index 9dbbea3..ed364da 100644 --- a/README.rst +++ b/README.rst @@ -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 index 0000000..45ecf24 --- /dev/null +++ b/example/app/models.py @@ -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 diff --git a/example/local_settings.py.template b/example/local_settings.py.template index 8a478bf..f6726fa 100644 --- a/example/local_settings.py.template +++ b/example/local_settings.py.template @@ -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' diff --git a/example/settings.py b/example/settings.py index 69cbe16..c99965e 100644 --- a/example/settings.py +++ b/example/settings.py @@ -89,6 +89,7 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.admin', 'social_auth', + 'app', ) AUTHENTICATION_BACKENDS = ( diff --git a/social_auth/backends.py b/social_auth/backends.py index dcf3571..c73e70c 100644 --- a/social_auth/backends.py +++ b/social_auth/backends.py @@ -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 diff --git a/social_auth/base.py b/social_auth/base.py index 76d152d..ed9929e 100644 --- a/social_auth/base.py +++ b/social_auth/base.py @@ -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': , 'username': , 'fullname': , - 'firstname': , - 'lastname': } + 'first_name': , + 'last_name': } """ 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 diff --git a/social_auth/conf.py b/social_auth/conf.py index 25dc555..91604f5 100644 --- a/social_auth/conf.py +++ b/social_auth/conf.py @@ -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 diff --git a/social_auth/models.py b/social_auth/models.py index 78b797e..325f6dd 100644 --- a/social_auth/models.py +++ b/social_auth/models.py @@ -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""" -- 2.39.5