简体   繁体   中英

Django Social Authentication Create New User With Custom Model

I'm trying to develop a Facebook social authentication feature on an application that uses a custom Django user model and django-rest-framework-social-oauth2 as the social authentication package. My custom user model is called 'Account' and it inherits from the AbstractBaseUser class. The Account model is shown below:

class Account(AbstractBaseUser):
  # Account model fields: 
  email = models.EmailField(verbose_name='email', max_length=60, unique=True)
  username = models.CharField(max_length=30, unique=True)
  first_name = models.CharField(max_length=30)
  last_name = models.CharField(max_length=30)
  date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
  last_login = models.DateTimeField(verbose_name='last login', auto_now=True)
  is_admin = models.BooleanField(default=False)
  is_active = models.BooleanField(default=True)
  is_staff = models.BooleanField(default=False)
  is_superuser = models.BooleanField(default=False)

  # The user will log in with their email instead of username:
  USERNAME_FIELD = 'email'

  # Required fields when registering, other than the email:
  REQUIRED_FIELDS = ['username', 'first_name', 'last_name']

  # Telling the Account object how to use the account manager:
  objects = MyAccountManager()

The function that handles creating a new user is called 'create_user' and is defined within my custom written MyAccountManager class which extends the Django BaseUserManager class. This is given below:

class MyAccountManager(BaseUserManager):
def create_user(self, email, username, first_name, last_name, password=None):
    # Checking to see if correct function parameters have been passed in: 
    if not email:
        raise ValueError('Users must have an email address')
    if not username:
        raise ValueError('Users must have a username')
    if not first_name:
        raise ValueError('Users must have a first name')
    if not last_name:
        raise ValueError('Users must have a last name')

    # Creating the new user:
    user = self.model(
        email = self.normalize_email(email),
        username = username,
        first_name = first_name,
        last_name = last_name,
    )
    user.set_password(password)
    user.save(using = self._db)
    
    return user

I've set up a working django-rest-framework-social-oauth2 url for creating a new user with a Facebook account. The relevant Facebook configuration in the Django settings.py file is shown below:

SOCIAL_AUTH_FACEBOOK_KEY = config('SOCIAL_AUTH_FACEBOOK_KEY')
SOCIAL_AUTH_FACEBOOK_SECRET = config('SOCIAL_AUTH_FACEBOOK_SECRET')

SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email' }

The issue that I've been having is the following: When the create_user function is called for a user that is using Facebook social login, the parameters email, first_name and last_name, that are required in the create_user function are not being provided by Facebook and I'm getting the error message shown in the image. The error message states the following:

create_user() missing 2 required positional arguments: 'first_name' and 'last_name'

Error Message from Django

Does anyone know how I would be able to access these additional parameters (email, first name, last name) from Facebook so that the correct parameters are passed into the create_user function?


Further Information

On implementing the pipeline suggestion I am still left with the same issue whereby the custom create_user function is missing both the first_name and last_name parameters. I think the reason that this occurring is due to the suggested pipeline cleanup_social_account function being called after create_user, where in my case both first_name and last_name are required fields, and as such a user object cannot be created in the database if they are not provided at the time the create_user function is called.

I am receiving this error due to the following function in the suggested custom pipeline:

social_core.pipeline.user.create_user

The code for this function in the social_core installed library is the following:

def create_user(strategy, details, backend, user=None, *args, **kwargs):
  if user:
      return {'is_new': False}

  fields = dict((name, kwargs.get(name, details.get(name)))
                for name in backend.setting('USER_FIELDS', USER_FIELDS))
  if not fields:
      return

  return {
      'is_new': True,
      'user': strategy.create_user(**fields)
  }

The details parameter passed into the above function contains the values that I need (first_name and last_name). However, they are not actually being added into the fields variable when it is created. The fields variable is shown above and is defined by the following:

fields = dict((name, kwargs.get(name, details.get(name)))
                for name in backend.setting('USER_FIELDS', USER_FIELDS))

In summary: The issue appears to be that first_name and last_name are not appearing within backend.settings('USER_FIELDS', USER_FIELDS), and therefore are not being added to the fields variable, and as such are not being passed into strategy.create_user(**fields).

So social_auth auto-populates those fields for me when I just get name and email from Facebook. It knows to bring in first_name and last_name. Since it doesn't seem to be working for you, you can create a custom pipeline function.

settings.py:

SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'social_core.pipeline.user.create_user',

# YOUR CUSTOM PIPELINE FUNCTION HERE.  I CREATED A FILE/MODULE
# NAMED pipeline.py AND STUCK IT IN THERE.  MAKE SURE TO PUT THIS
# AFTER CREATE USER.
'path.to.custom.pipeline.cleanup_social_account',

'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',

)

pipeline.py:

def cleanup_social_account(backend, uid, user=None, *args, **kwargs):
"""
3rd party: python-social-auth.

Social auth pipeline to cleanup the user's data.  Must be placed
after 'social_core.pipeline.user.create_user'.
"""

# Check if the user object exists and a new account was just created.
if user and kwargs.get('is_new', False):

    *** THIS IS UNTESTED, BUT FACEBOOK'S DATA SHOULD COME INTO THE DETAILS KWARG ***
    user.first_name = kwargs['details']['first_name']
    user.last_name = kwargs['details']['last_name']
    user.save()

return {'user': user}

Just add SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['username', 'email', 'first_name', 'last_name'] in your settings.py file and these fields should be added to **kwargs in you create_user method.

Sample Usage

In settings.py file add this

SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['first_name', 'last_name', 'email']

In your UserManager class modify create_user like below

def create_user(self, password=None, **kwargs):
    """Create and return a `User` with an email, username and password."""

    first_name = kwargs.get('first_name')
    last_name = kwargs.get('last_name')
    email = kwargs.get('email')

    if first_name is None or last_name is None:
        raise TypeError(f"Invalid FirstName: {first_name}, LastName: {last_name}, Email: {email}")

    if email is None:
        raise TypeError("Users must have an email address.")

    user = self.model(
        first_name=first_name,
        last_name=last_name,
        email=self.normalize_email(email),
    )
    user.set_password(password)
    user.save()

    return user

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM