The official Django documentation highly recommends using a custom user model for new projects, but it doesn’t provide a complete implementation of what one looks like. After spending way too much time figuring this out on my own, here is the short, concise way to configure a custom user model using AbstractUser in your Django projects.

If you’re brand new to user authentication in Django, I recommend first reviewing how to implement a regular login, logout, signup flow in Django. That covers the basics in detail. In this tutorial I’ll go a bit faster.

You can see complete source code on Github and a live, working example at https://django-custom-user-model.herokuapp.com/

Setup

Create a new Django project from the command line. We need to do several things:

  • create a new virtual environment users
  • install Django
  • make a new Django project djauth
  • make a new app users

Here are the commands to run:

$ python3 -m venv ~/.virtualenvs/users
$ source ~/.virtualenvs/users/bin/activate
(users) $ pip install django
(users) $ django-admin.py startproject djauth .
(users) $ ./manage.py startapp users
(users) $ ./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.

Welcome  page

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, don’t mess with it unless you really know what you’re doing. And if you did, you wouldn’t be reading this tutorial, would you?

So we’ll use AbstractUser which actually subclasses AbstractBaseUser but provides more default configuration.

Custom User Model

Creating our initial custom user model requires four steps:

  • update settings.py
  • create a new User model and manager
  • create new UserCreation and UserChangeForm
  • update the admin

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

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

# djauth/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users',
]
...
AUTH_USER_MODEL = 'users.CustomUser'

Now update models.py with a new User model and manager. Since we’re not making any changes at this point, we can simply subclass the existing functionality contained in UserManager and AbstractUser.

# users/models.py
from django.contrib.auth.models import AbstractUser, UserManager

class CustomUserManager(UserManager):
    pass

class CustomUser(AbstractUser):
    objects = CustomUserManager()

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 in the users app called forms.py.

(users) $ touch users/forms.py

We’ll update it with the following code to largely subclass the existing forms.

# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):

    class Meta(UserCreationForm.Meta):
        model = CustomUser
        fields = UserCreationForm.Meta.fields

class CustomUserChangeForm(UserChangeForm):

    class Meta:
        model = CustomUser
        fields = UserChangeForm.Meta.fields

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

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

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

class CustomUserAdmin(UserAdmin):
    model = CustomUser
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm

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.

(users) $ ./manage.py makemigrations
(users) $ ./manage.py migrate

Superuser

It’s helpful to create a superuser so we can use to login to the admin and test out login/logout. On the command line type the following command and go through the prompts.

(users) $ ./manage.py createsuperuser

Templates/Views/URLs

Our goal is a homepage with links to login, logout, and signup. Start by updating settings.py to use a project-level templates directory.

# djauth/settings.py
'DIRS': [os.path.join(BASE_DIR, 'templates')],

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

# djauth/settings.py
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'

Create a new project-level templates folder and within it a registration folder as that’s where Django will look for the login template.

(users) $ mkdir templates
(users) $ mkdir templates/registration

Then create four templates:

(users) $ touch registration/login.html
(users) $ touch base.html
(users) $ touch home.html
(users) $ touch signup.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' %}">logout</a></p>
{% else %}
  <p>You are not logged in</p>
  <a href="{% url 'login' %}">login</a> |
  <a href="{% url 'signup' %}">signup</a>
{% endif %}
{% endblock %}
<!-- templates/registration/login.html -->
{% extends 'base.html' %}

{% block title %}Login{% endblock %}

{% block content %}
<h2>Login</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Login</button>
</form>
{% endblock %}
<!-- templates/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.

# djauth/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('users/', include('users.urls')),
    path('users/', include('django.contrib.auth.urls')),
]

Create a urls.py file in the users app.

(users) $ touch users/urls.py

Then fill in the following code:

# users/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('signup/', views.SignUp.as_view(), name='signup'),
]

Last step is our views.py file in the users app which will contain our signup form.

# users/views.py
from django.urls import reverse_lazy
from django.views import generic

from .forms import CustomUserCreationForm

class SignUp(generic.CreateView):
    form_class = CustomUserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'signup.html'

Ok, phew! We’re done. Let’s test it out.

Start up the server with ./manage.py runserver and go to the homepage at http://127.0.0.1:8000/.

Home page

Click on login and use your superuser credentials.

Login page

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

Homepage with superuser

Now use the logout link and then click on signup.

Signup page

Create a new user. Mine is called testuser. After successfully submitting the form you’ll be redirected to the login page. Login 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 easily and at any time add additional fields to it. See the Django docs for instructions on how.

I hope this tutorial helps you out. If it did, please drop me a note at [email protected] and let me know how it went!