Custom User with "USERNAME_FIELD" non-unique in Django

1.4k views Asked by At

Situation:
We have an existing system where current employee logins to the system using their mobile number.
But as same mobile number can be used by multiple users, so there is constraint in existing db that, at any moment there can be only one user with given "Mobile Number" and "is_active: True" (i.e their may be other employees registered with same number earlier, but their status of "is_active" would have changed to "false", when they left).
Also, only admin can add/register new employees/user.
I have created a custom "User" model and "UserManager" as below:

models.py

from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.utils.translation import gettext_lazy as _
from .managers import UserManager


# Create your models here.
class User(AbstractBaseUser, PermissionsMixin):
    """
    A class implementing a fully featured User model.
    Phone number is required. Other fields are optional.
    """
    first_name = models.CharField(_('first name'), max_length=50, null=True, blank=True)
    last_name = models.CharField(_('last name'), max_length=50, null=True, blank=True)
    phone = models.CharField(
        _('phone number'),
        max_length=10,
        null=False,
        blank=False,
        help_text=_('Must be of 10 digits only')
        )
    email = models.EmailField(_('email address'), null=True, blank=True)
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user is a staff member.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active.'
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
    last_updated = models.DateTimeField(_('last updated'), auto_now=True)

    objects = UserManager()

    USERNAME_FIELD = 'phone'
    REQUIRED_FIELDS = []

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        return self.first_name + ' ' + self.last_name

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    # def email_user(self, subject, message, from_email=None, **kwargs):
    #     """Send an email to this user."""
    #     send_mail(subject, message, from_email, [self.email], **kwargs)

    # def sms_user(self, subject, message, from=None, **kwargs):
    #     """Send a sms to this user."""
    #     send_sms(subject, message, from, [self.phone], **kwargs)

    def __str__(self):
        return self.first_name + ' ' + self.last_name

managers.py

from django.contrib.auth.models import BaseUserManager


# Create your models here.
class UserManager(BaseUserManager):
    """Define a model manager for User model"""

    use_in_migrations = True

    def normalize_phone(self, phone):
        """
        Applies NFKC Unicode normalization to usernames so that visually identical
        characters with different Unicode code points are considered identical
        """
        phone = self.model.normalize_username(phone)
        phone = phone.strip()
        if not phone.isdigit() or len(phone) != 10:
            raise ValueError('Phone number must of 10 digits only')
        return phone

    def make_default_password(self, phone):
        """
        Generates a default password
        by concatenating Digi + Phone number
        """
        return 'Pass' + phone

    def _create_user(self, phone, password=None, **extra_fields):
        """Create and save a User in DB with the given phone"""
        if not phone:
            raise ValueError('Phone number must be provided')
        phone = self.normalize_phone(phone)
        password = self.make_default_password(phone)
        user = self.model(phone=phone, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, phone, password=None, **extra_fields):
        """Create and save a regular User with the given phone and password"""
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(phone, password, **extra_fields)

    def create_staff(self, phone, password=None, **extra_fields):
        """Create and save a regular User with the given phone and password"""
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(phone, password, **extra_fields)

    def create_superuser(self, phone, password=None, **extra_fields):
        """Create and save a Superuser with the given phone and password"""
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True')
        return self._create_user(phone, password, **extra_fields)

Now when I'm running python manage.py makemigrations, its giving following error:

users.User: (auth.E003) 'User.phone' must be unique because it is named as the 'USERNAME_FIELD'

I'm stuck, what to do next.
Any help related to above query or suggestion for how to proceed in the above situation will be highly appreciated.
I am novice to Django and trying to rewrite the rest based backend in Django (using DjangoRestFramework).

1

There are 1 answers

1
NFroll On

Add unique=False on the phone field in your User model.

phone = models.CharField(
        _('phone number'),
        max_length=10,
        unique=False
        null=False,
        blank=False,
        help_text=_('Must be of 10 digits only')
        )

Or you can use another field to be the USERNAME_FIELD = ' '. It is set to phone now.