How to Customize User Model in Django?
Django

How to Customize User Model in Django?

Mishel Shaji
Mishel Shaji

If you are a Django developer, you might have already noticed that Django comes with a built-in authentication system. Django's authentication system provides a mechanism for user signup, login, password reset, authentication, and authorization. In this post, we'll learn how to create a custom user model in Django.

Although we can use the default user model provided by Django's authentication module without making any changes, there can be situations in which the default user model has to be modified.

For example, you may have to add additional fields to the user model or use email instead of a username for login.

In fact, we have a couple of different ways to customize the Django user model. We mainly use these two methods.

  1. AbstractUser
  2. AbstractBaseUser

In this post, we'll use AbstractBaseUser to customize the user model.

The source code of this project can be downloaded from GitHub.

geekinsta/django-abstractbaseuser
Custom User model using AbstractBaseUser. Contribute to geekinsta/django-abstractbaseuser development by creating an account on GitHub.

Create a Django Project

Let us start by creating a new Django project.

django-admin startproject djangouser

Navigate to the project folder using:

cd djangouser

Next, we have to create a new app called accounts. This app will hold our custom user model.

python manage.py startapp accounts

Register the app under INSTALLED_APPS in settings.py.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts'
]

Customize User Model With AbstractBaseUser

The AbstractBaseUser and AbstractUser classes provide the basic fields and features of a User model password filed and password hashing mechanism so that we don't have to reinvent the wheel.

Open the models.py file of accounts and import AbstractBaseUser and BaseUserManager from django.contrib.auth.models.

Here's the code of my models.py file.

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.core import validators

Now, let us create our Custom User Model by creating a class named User that inherits from AbstractBaseUser.

class User(AbstractBaseUser, PermissionsMixin):

    # Primary key of the model
    id = models.BigAutoField(
        primary_key = True,
    )

    # Email field that serves as the username field
    email = models.CharField(
        max_length = 50, 
        unique = True, 
        validators = [validators.EmailValidator()],
        verbose_name = "Email"
    )

    # Other required fields for authentication
    # If the user is a staff, defaults to false
    is_staff = models.BooleanField(default=False)

    # If the user account is active or not. Defaults to True.
    # If the value is set to false, user will not be allowed to sign in.
    is_active = models.BooleanField(default=True)
    
    # Setting email instead of username
    USERNAME_FIELD = 'email'
    
    # Custom user manager
    objects = UserManager()
    
    def get_full_name(self):
        # Returns the first_name and the last_name
        return f'{self.first_name} {self.last_name}'

    def get_short_name(self):
        # Returns the short name for the user.
        return self.first_name

Note that I have not included the password field and is_superuser in this model. This is because, as I mentioned earlier, the password field will be inherited from AbstarctBaseUser class and is_superuser field will be inherited from PermissionsMixin. Also, note that we are using email instead of a username.

We can also add additional fields like First name and Last Name to the model if we want.

class User(AbstractBaseUser, PermissionsMixin):

    # Primary key of the model
    id = models.BigAutoField(
        primary_key = True,
    )

    first_name = models.CharField(
        max_length = 20,
        verbose_name = "First Name",
    )

    last_name = models.CharField(
        max_length = 20,
        verbose_name = "Last Name",
    )

    # Email field that serves as the username field
    email = models.CharField(
        max_length = 50, 
        unique = True, 
        validators = [validators.EmailValidator()],
        verbose_name = "Email"
    )

    # Other required fields for authentication
    # If the user is a staff, defaults to false
    is_staff = models.BooleanField(default=False)

    # If the user account is active or not. Defaults to True.
    # If the value is set to false, user will not be allowed to sign in.
    is_active = models.BooleanField(default=True)
    
    # Setting email instead of username
    USERNAME_FIELD = 'email'
    
    # Custom user manager
    objects = UserManager()
    
    def get_full_name(self):
        # Returns the first_name and the last_name
        return f'{self.first_name} {self.last_name}'

    def get_short_name(self):
        # Returns the short name for the user.
        return self.first_name

The default UserManager can only be used if our model contains username, email, is_staff, is_active, is_superuser, last_login, and date_joined fields. Note that we are using email instead of username and we have completely removed the username field. So, we should customize the default UserManager to make it work with our custom User Model.

In models.py, create a new UserManager above the User model.

class UserManager(BaseUserManager):
    use_in_migrations = True

    # Method to save user to the database
    def save_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('The given email must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)

        # Call this method for password hashing
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields['is_superuser'] = False
        extra_fields['is_staff'] = False
        return self.save_user(email, password, **extra_fields)

    # Method called while creating a staff user
    def create_staffuser(self, email, password, **extra_fields):
        extra_fields['is_staff'] = True
        extra_fields['is_superuser'] = False
        
        return self.save_user(email, password, **extra_fields) 

    # Method called while calling creatsuperuser
    def create_superuser(self, email, password, **extra_fields):

        # Set is_superuser parameter to true
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('is_superuser should be True')
        
        extra_fields['is_staff'] = True

        return self.save_user(email, password, **extra_fields) 

The following line in the User model configures it to use our custom UserManager class.

objects = UserManager()

Here's the complete code of my models.py file.

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.core import validators

# Create your models here.
class UserManager(BaseUserManager):
    use_in_migrations = True

    # Method to save user to the database
    def save_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('The given email must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)

        # Call this method for password hashing
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields['is_superuser'] = False
        extra_fields['is_staff'] = False
        return self.save_user(email, password, **extra_fields)

    # Method called while creating a staff user
    def create_staffuser(self, email, password, **extra_fields):
        extra_fields['is_staff'] = True
        extra_fields['is_superuser'] = False
        
        return self.save_user(email, password, **extra_fields) 

    # Method called while calling creatsuperuser
    def create_superuser(self, email, password, **extra_fields):

        # Set is_superuser parameter to true
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('is_superuser should be True')
        
        extra_fields['is_staff'] = True

        return self.save_user(email, password, **extra_fields) 
    

class User(AbstractBaseUser, PermissionsMixin):

    # Primary key of the model
    id = models.BigAutoField(
        primary_key = True,
    )

    first_name = models.CharField(
        max_length = 20,
        verbose_name = "First Name",
    )

    last_name = models.CharField(
        max_length = 20,
        verbose_name = "Last Name",
    )

    # Email field that serves as the username field
    email = models.CharField(
        max_length = 50, 
        unique = True, 
        validators = [validators.EmailValidator()],
        verbose_name = "Email"
    )

    # Other required fields for authentication
    # If the user is a staff, defaults to false
    is_staff = models.BooleanField(default=False)

    # If the user account is active or not. Defaults to True.
    # If the value is set to false, user will not be allowed to sign in.
    is_active = models.BooleanField(default=True)
    
    # Setting email instead of username
    USERNAME_FIELD = 'email'

    # Custom user manager
    objects = UserManager()

    def get_full_name(self):
        # Returns the first_name plus the last_name, with a space in between.
        return f'{self.first_name} {self.last_name}'

    def get_short_name(self):
        # Returns the short name for the user.
        return self.first_name

Now we have a customized Django user model and we have to tell Django to use this model instead of the default one. For that, add the following line to the end of settings.py

AUTH_USER_MODEL = 'accounts.User'

The last step is to commit the changes to database using:

python manage.py makemigrations
python manage.py migrate

If you previously made any migrations, you may get some errors at this point. The easiest way to solve this issue is to delete the existing database and migrations and run the above mentioned commands again.

You can test the User model by creating a new superuser using this command.

python manage.py createsuperuser

Instead of username, Django will now ask for email id and the login page of django admin dashboard will use email instead of username and password.

If you are facing, let me know in the comments below.