Django Custom User Model

Updated

Table of Contents

Django ships with a built-in User model for authentication and if you'd like a basic tutorial on how to implement login, logout, signup and so on see the Django Login and Logout tutorial for more.

However, for a real-world project, the official Django documentation highly recommends using a custom user model instead; it provides far more flexibility down the line so, as a general rule, always use a custom user model for all new Django projects.

But how to implement one? The official documentation example is different from what many Django experts recommend using. There is a far more straightforward yet still powerful approach to starting new Django projects with a custom user model, which I'll demonstrate here.

Set Up

To start, create a new Django project from the command line. We need to do several things:

  • create and navigate into a dedicated directory called accounts for our code
  • install Django
  • make a new Django project called django_project
  • make a new app accounts
  • start the local web server

Here are the commands to run:

# Windows
$ cd onedrive\desktop\code
$ mkdir pages
$ cd pages
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $ python -m pip install django~=5.0.0
(.venv) $ django-admin startproject django_project .
(.venv) $ python manage.py startapp accounts
(.venv) $ python manage.py runserver

# macOS
$ cd ~/desktop/code
$ mkdir pages
$ cd pages
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ python3 -m pip install django~=5.0.0
(.venv) $ django-admin startproject django_project .
(.venv) $ python3 manage.py startapp accounts
(.venv) $ python3 manage.py runserver

Note that we did not run migrate to configure our database. It's important to wait until after we've created our new custom user model before doing so.

If you navigate to http://127.0.0.1:8000, you'll see the Django welcome screen.

Django welcome page

Sweet. For now, stop the local server with Control+c; otherwise, it will kick off many errors as we implement a custom user model.

AbstractUser vs AbstractBaseUser

There are two modern ways to create a custom user model in Django: AbstractUser and AbstractBaseUser. In both cases, we can subclass them to extend existing functionality; however, AbstractBaseUser requires much, much more work. Seriously, only mess with it if you know what you're doing. And if you did, you wouldn't be reading this tutorial, would you?

So we'll use AbstractUser, which subclasses AbstractBaseUser but provides more default configuration.

Custom User Model

Creating our initial custom user model requires four steps:

  • update django_project/settings.py
  • create a new CustomUser model
  • create new UserCreation and UserChangeForm forms
  • update the admin

In settings.py, we'll add the accounts app and use the AUTH_USER_MODEL config to tell Django to use our new custom user model instead of the built-in User model. We'll call our custom user model CustomUser.

Within INSTALLED_APPS add accounts at the bottom. Then at the bottom of the entire file, add the AUTH_USER_MODEL config.

# django_project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "accounts",  # new
]
...
AUTH_USER_MODEL = "accounts.CustomUser"  # new

Now update accounts/models.py with a new User model, which we'll call CustomUser.

# accounts/models.py
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    pass
    # add additional fields in here

    def __str__(self):
        return self.username

We need new versions of two form methods that receive heavy use working with users. Stop the local server with Control+c and create a new file accounts/forms.py. We'll update it with the following code to largely subclass the existing forms.

# accounts/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):

    class Meta:
        model = CustomUser
        fields = ("username", "email")

class CustomUserChangeForm(UserChangeForm):

    class Meta:
        model = CustomUser
        fields = ("username", "email")

Finally, we update admin.py since the admin is highly coupled to the default User model.

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser
    list_display = ["email", "username",]

admin.site.register(CustomUser, CustomUserAdmin)

And we're done! We can now run makemigrations and migrate for the first time to create a new database that uses the custom user model.

(.venv) $ python manage.py makemigrations accounts
(.venv) $ python manage.py migrate

Superuser

It's helpful to create a superuser that we can use to log in to the admin and test out login/logout. On the command line, type the following command and go through the prompts.

(.venv) $ python manage.py createsuperuser

Templates/Views/URLs

Our goal is a homepage with links to log in, log out, and sign up. Start by updating settings.py to use a project-level templates directory.

# django_project/settings.py
TEMPLATES = [
    {
        ...
        "DIRS": [BASE_DIR / "templates"],  # new
        ...
    },
]

Then set the redirect links for login and logout, which will go to our home template. Add these two lines at the bottom of the file.

# django_project/settings.py
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"

Create a new project-level templates folder and a registration folder within it, as that's where Django will look for the login template. We will also put our signup.html template in there.

(.venv) $ mkdir templates
(.venv) $ mkdir templates/registration

Then, create four templates using the touch command on macOS or your text editor on Windows.

(.venv) $ touch templates/registration/login.html
(.venv) $ touch templates/registration/signup.html
(.venv) $ touch templates/base.html
(.venv) $ touch templates/home.html

Update the files as follows:

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>{% block title %}Django Auth Tutorial{% endblock %}</title>
</head>
<body>
  <main>
    {% block content %}
    {% endblock %}
  </main>
</body>
</html>
<!-- templates/home.html -->
{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
{% if user.is_authenticated %}
  Hi {{ user.username }}!
  <p><a href="{% url 'logout' %}">Log Out</a></p>
{% else %}
  <p>You are not logged in</p>
  <a href="{% url 'login' %}">Log In</a> |
  <a href="{% url 'signup' %}">Sign Up</a>
{% endif %}
{% endblock %}
<!-- templates/registration/login.html -->
{% extends "base.html" %}

{% block title %}Log In{% endblock %}

{% block content %}
<h2>Log In</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Log In</button>
</form>
{% endblock %}
<!-- templates/registration/signup.html -->
{% extends "base.html" %}

{% block title %}Sign Up{% endblock %}

{% block content %}
<h2>Sign Up</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Sign Up</button>
</form>
{% endblock %}

Now for our urls.py files at the project and app level.

# django_project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView

urlpatterns = [
    path("", TemplateView.as_view(template_name="home.html"), name="home"),
    path("admin/", admin.site.urls),
    path("accounts/", include("accounts.urls")),
    path("accounts/", include("django.contrib.auth.urls")),
]

Create a urls.py file in the accounts app using the touch command on macOS or your text editor on Windows.

(.venv) $ touch accounts/urls.py

Then fill in the following code:

# accounts/urls.py
from django.urls import path

from .views import SignUpView

urlpatterns = [
    path("signup/", SignUpView.as_view(), name="signup"),
]

The last step is our views.py file in the accounts app which will contain our signup form.

# accounts/views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView

from .forms import CustomUserCreationForm

class SignUpView(CreateView):
    form_class = CustomUserCreationForm
    success_url = reverse_lazy("login")
    template_name = "registration/signup.html"

Ok, phew! We're done. Let's test it out. Start the server with python manage.py runserver and go to the homepage at http://127.0.0.1:8000/.

Home page

Click on Log In and use your superuser credentials.

Log In page

Upon successful submission, you'll be redirected to the homepage and see a personalized greeting.

Homepage with superuser

Now use the logout link and click signup.

Sign Up page

Create a new user. Mine is called testuser. After successfully submitting the form, you'll be redirected to the login page. Log in with your new user, and you'll again be redirected to the homepage with a personalized greeting for the new user.

Homepage with new user

If you want to peruse the admin, log into it with your superuser account at http://127.0.0.1:8000/admin. If you look at Users, you can see our two users.

Users in the Admin

Conclusion

Now that our custom user model is configured, you can quickly and at any time add additional fields. See the Django docs for further instructions.

You can also check out DjangoX, which is an open-source Django starter framework that includes a custom user model, email/password by default instead of username/email/password, social authentication, and more.

And if you'd like an even more in-depth example of starting a new Django project, check out the book Django for Beginners.