0: Intro

The aim of this post will be explaining how to create a custom User Model in Django. So we can use that email address as the primary ‘user identifier’ instead of a username for authentication.

Default Django app will give you a User Model that has a mandatory username field, and an ‘optional’ email field.

However if you’re starting a new project, Django highly recommends you to set up a custom User Model. For more info on this subkect look at official doc: Custom User Model

In addition to that I usually need to set up email as username to authenticate users in my apps.

1: Setup The Repo

  1. I don’t like django’s default app template, therefore I created and using mine. You can clone this remote repo to run below code:

    $ git clone https://github.com/serhatteker.com/django-template.git
    
  2. Create a virtual environment named .venv:

    $ virtualenv -p python3 .venv
    

    You can use Poetry or Pipenv to create venv as well.

  3. Activate virtualenv:

    $ source .venv/bin/activate
    
  4. Install requirements:

    $ pip install -r requirements.txt
    
  5. Start an app named ‘users’:

    $ mkdir src/app && python manage.py startapp src/users
    
  6. Add the new app to the INSTALLED_APPS list into your settings.py:

    MY_APPS = [
        'src.core',
        'src.users',
    ]
    
    INSTALLED_APPS = DJANGO_CORE_APPS + THIRD_PARTY_APPS + MY_APPS
    

2: Creating User Model

2.0 Creating Model Manager

First of all we need to add a custom Manager, by subclassing BaseUserManager which uses an email as the unique identifier instead of a username.

Add below code to your users’ app model:

# src/users/model.py
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _


class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifiers
    for authentication instead of usernames.
    """
    def create_user(self, email, password, **extra_fields):
        """
        Create and save a User with the given email and password.
        """
        if not email:
            raise ValueError(_('The Email must be set'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', 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(email, password, **extra_fields)

2.1 Creating User Model

2.1.0 AbstractUser vs AbstractBaseUser

As we mentioned before the default User Model in Django uses a username to uniquely identify a user during authentication. If you’d rather use an email address, you’ll need to create a custom User model by either subclassing AbstractUser or AbstractBaseUser.

  • AbstractUser: If you are ok with the existing fields on the User Model and just want to remove the username field.
  • AbstractBaseUser: If you want to start from scratch by creating your own, completely new User Model.

2.1.1 Adding AbstractUser

We will use AbstractUser since we only want to remove username field and use django’s very well designed User Model.

Add below code into your model.py:

# src/users/model.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _


class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

By adding above code we:

  • Created a new class called CustomUser that subclasses AbstractUser
  • Removed the username field
  • Made the email field required and unique
  • Set the USERNAME_FIELD which defines the unique identifier for the `User Model to email
  • Specified that all objects for the class come from the CustomUserManager

3: Update Settings

Add below line into your settings.py for Referencing User Model:

AUTH_USER_MODEL = 'users.CustomUser'

4: Create Tables

Create new migrations and migrate them which will create a new database that uses our custom User Model:

$ python manage.py makemigrations
$ python manage.py migrate
Note:
It is important that before applying migrations in your app you must add CustomUser into your model. Again, I want to emphasize that before you apply your first migration you must add your ‘custom models’.

5: Admin

This is not a necessity; I mean that it doesn’t throw an error when running your app. However I believe that it is more than “good-to-have”. So we will add as well:

Add below code into your admin.py:

# src/users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import CustomUser


class CustomUserAdmin(UserAdmin):
    model = CustomUser
    list_display = ('email', 'is_staff', 'is_active',)
    list_filter = ('email', 'is_staff', 'is_active',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_staff', 'is_active')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)

That’s it. From now on you can use our CustomUser to authenticate users with email and password.

Now you can run the app:

$ python manage.py runserver

6: Test

When you create a superuser, you should be prompted to enter an email rather than a username:

$ python manage.py createsuperuser
Email address: user@test.api
Password:
Password (again):
Superuser created successfully.

All done!