Auth in Django with other fields
I have a Django rest framework app and I modified its User model with AbstractBaseUser. I deployed the project on a VPS and now the customer wants to make changes on authentication. I have to set BOTH phone and email as USERNAME_FIELD to let users send phone or their email and I have to make it dynamic.
Here's my first approach:
from django.db import models
from django.core.validators import RegexValidator, MinValueValidator
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
class AllUser(BaseUserManager):
def create_user(self, username, phone, email=None, password=None, first_name=None, last_name=None):
if not email:
raise ValueError('need Email')
if not username:
raise ValueError('need Email')
if not phone:
raise ValueError('need Phone Number')
if not first_name:
raise ValueError('First Name')
if not last_name:
raise ValueError('Last Name')
user = self.model(
email=self.normalize_email(email),
phone=phone,
username=username,
first_name=first_name,
last_name=last_name,
)
user.is_active = True
user.set_password(password)
user.save(using=self._db)
return user
def create_staff(self, username, phone, email, password, first_name, last_name):
user = self.create_user(
email=email,
phone=phone,
username=username,
password=password,
first_name=first_name,
last_name=last_name,
)
user.is_active = True
user.is_staff = True
user.is_superuser = False
user.save(using=self._db)
return user
def create_superuser(self, username, phone, email, password, first_name, last_name):
user = self.create_user(
email=email,
phone=phone,
username=username,
password=password,
first_name=first_name,
last_name=last_name,
)
user.is_staff = True
user.is_active = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', message='ASCII Characters Only!')
numbers = RegexValidator(r'^[0-9a]*$', message='Numbers Only!')
phone = models.CharField(max_length=11, validators=[numbers], blank=True, null=True)
email = models.EmailField(max_length=244, blank=True, null=True)
first_name = models.CharField(max_length=30, null=True, blank=True)
last_name = models.CharField(max_length=50, null=True, blank=True)
is_active = models.BooleanField(default=True, null=False)
is_staff = models.BooleanField(default=False, null=False)
is_superuser = models.BooleanField(default=False, null=False)
# TODO: Changeable between `phone` and `email`
username = models.CharField(max_length=244, unique=True)
objects = AllUser()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['phone', 'email', 'first_name', 'last_name']
# TODO: Override `save` method to save user's USERNAME_FIELD
def save(self, *args, **kwargs):
if self.phone:
self.username = self.phone
elif self.email:
self.username = self.email
super(User, self).save(*args, **kwargs)
@property
def fullName(self):
return f"{self.first_name} {self.last_name}"
def __str__(self):
return f"{self.username}"
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
class Meta:
# TODO : Representation of a single object from User model
verbose_name = 'User'
# TODO : Representation of User model in admin panel
verbose_name_plural = 'Users'
ordering = ['last_name']
The problem is I can't delete the database because its in production mode. And I have to change the USERNAME_FIELD as a dynamic choice. I couldn't run the migrate command and it gives me this error:
psycopg.errors.UniqueViolationError: Could not create unique index "user_user_username_key"
DETAIL: Key (username)=['self']) is duplicated
Is there possible to do that while VPS is active? to make USERNAME_FIELD dynamic?
you need to have a validator which can include the phone no or email or username. createa function to validate that username
is correct or not, and add it against username_validator
field as below
below is basic example
def username_email_phone_no_validtor(username):
# validation logic for email or phone no
return True # or false
class User(AbstractBaseUser, PermissionsMixin):
:
:
username_validator = username_email_phone_no_validtor
:
: # other fields
this should allow you to add phone no or emial or anything for username which you want.