Django Minimalist Custom User Model

In this article I will look at creating a minimal custom user model in Django.

The aim is to create the simplest possible user model, providing only a username field and a password field, that will allow logging in, logging out and testing authorisation with the standard Django auth interface.

Being as small as possible is the goal at all costs. The user model we create here will not be suitable for accessing the Django admin site, as the admin requires fields that we will be leaving out.

This user model is unlikely to be usable as it stands, but provides a clean slate on which a customised user model could be built, without clutter in the background.

Research - AbstractBaseUser

Online sources including the official Django documentation suggest we may want to build our custom user model on top of the AbstractBaseUser class. We will begin there, and it is instructive to first examine the code in the libary for AbstractBaseUser.

django/lib/python3.8/site-packages/django/contrib/auth/base_user.py

[...]

class AbstractBaseUser(models.Model):
    password = models.CharField(_('password'), max_length=128)
    last_login = models.DateTimeField(_('last login'), blank=True, null=True)

    is_active = True

    REQUIRED_FIELDS = []

    [...]

Examining AbstractBaseUser in the django library, a password field is already provided for us, along with a last login field, a fixed is_active property, and the REQUIRED_FIELDS empty list)

Coding Our Custom User Model

In this example, we have named our custom user app 'customusers'.

We will be writing our classes in 'customusers/models.py', building on BaseUserManager and AbstractBaseUser, so we include these at the top of the file.

customusers/models.py

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser

The User Manager

Our custom user model will require a custom user manager, whose only required purpose is to handle creation of users. The user manager is set as the 'objects' property on the user model, and inherits from BaseUserManager, which in turn inherits from models.Manager, therefore inheriting the usual database management methods.

Django passwords are stored as a hash and therefore need to be set using the set_password function (provided by the AbstractBaseUser class).

The create_superuser method is required so `manage.py createsuperuser` will work.

customusers/models.py

class CustomUserManager(BaseUserManager):
    def create_user(self, username, password):
        user = CustomUser.objects.create(username=username)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, username, password):
        user = self.create_user(username, password)
        return user

The User Model

The field used for user identification must have unique=True set, and must be named in the USERNAME_FIELD property. It can otherwise be any kind of field we like, we could for instance use an email field as the username.

As we discovered above, a last_login field is provided by the AbstractBaseUser class; if we desire to go really minimalist, it can be disabled by setting last_login = None in our custom user model.

The objects property is required and must be set to our custom user manager class.

customusers/models.py

class CustomUser(AbstractBaseUser):
    username = models.CharField(max_length=50, unique=True)
    last_login = None

    USERNAME_FIELD = 'username'

    objects = CustomUserManager()

Using Our CustomUser Model

The above minimalist code is sufficient for user login, logout and authorisation testing and so achieves what we set out to do.

In the interest of completeness, I will summarise how to deploy the user model we have created on our site.

Settings.py

To use our custom user model we must add it to INSTALLED_APPS in settings.py in the usual manner, and additionally also add AUTH_USER_MODEL = 'customusers.CustomUser' somewhere (I favour just after the AUTH_PASSWORD_VALIDATORS list, above the # Internationalization comment in the Django-generated settings.py).

Login and Logout Views

Here is an example of the views required to perform login and logout functions.

customusers/views.py

from django.shortcuts import redirect

from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout


def login(request):
    user = authenticate(request, username=request.POST['username'], password=request.POST['password'])

    if user is None:  # invalid login
        return redirect('error')

    # successful login
    auth_login(request, user)
    return redirect('home')


def logout(request):
    auth_logout(request)
    return redirect('home')

A login form should action a POST request on the url for the login view, and the logout view can be called as a GET request from an ordinary hyperlink.

Testing Authorisation

The visitor's login/logout status may be tested in a view using request.user.is_authenticated:

demo/views.py

def home(request):
    if request.user.is_authenticated:
        message = "You are logged in."
    else:
        message = "You are not logged in."

The same can also be tested in the template. Assuming the view's request.user has been passed in the template context as user:

demo/templates/home.html

{% if user.is_authenticated %}
    <p>You are logged in as "{{ user.username }}" - <a href=/logout>Logout</a>
{% else %}
    <form method=post action=/login>
        <input type=text name=username>
        <input type=password name=password>
        <input type=submit value=Login>
        {% csrf_token %}
    </form>
{% endif %}

Summary

In this article I have demonstrated the most minimalist Django custom user model possible.

The code for this model and a project demonstrating it is available on GitHub.