Reputation: 11
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'
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).
Upvotes: 1
Views: 3040
Reputation: 1
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.
SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['first_name', 'last_name', 'email']
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
Upvotes: 0
Reputation: 321
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}
Upvotes: 2