Making a Custom Django User Model Tutorial
Table of Contents
- Understanding the Django User model
- Django User fields
- Do I need a custom Django User model?
- Application-specific data
- Additional / different authentication data
- Why do it anyway?
- Tutorial: Defining the custom model
- The model in action
- Referencing the User model
- What’s next?
- Key takeaways
Understanding the Django User model
The Django User model is at the center of Django’s authentication system. It is the mechanism for identifying the users of your web application.
A user will log in by providing their username and password. Then (depending on your authentication backend) the identity of that user is preserved across requests either through a session, a token, or some other mechanism.
When a request is made to your web application, Django loads an instance of the User model that represents (and identifies) the user of your web application.
Django User fields
All the fields on the built-in Django User model are used for authenticating the user, communicating with the user, and understanding what the user is authorized to access.
Authentication
username
password
last_login
date_joined
Communication
first_name
last_name
email
Authorization
groups
user_permissions
is_staff
is_active
is_superuser
Do I need a custom Django User model?
The short answer is: No, but use one anyway.
Application-specific data
As we saw above, the model is meant to represent your system’s user from an authentication or authorization point of view, but there’s often other data you might be tempted to include.
Most Django projects have application-specific data associated with a user, such as a profile image or application preferences. Data like this could be stored or associated directly via the model, or you could alternatively create a Profile model that stores the application-specific data.
Storing application data on the User model
There are a few advantages to storing application-specific data on the User model. For smaller applications, it’s simpler. There are fewer objects you need to keep track of and fewer objects that you need to load from the database. Anything your application needs to know about the user and their preferences can easily be stored and accessed in a single place.
The risk here is that your model ends up being bloated with extra fields that you may not always need to apply to every user in the system, which can make your user table in the database a little unwieldy.
Storing application data in a profile model
The alternative to keeping this data is to store application-specific data on another model that has a one-to-one relationship with the User model. This keeps your model simple, lean, and authentication-specific.
By keeping your application data separate from your user’s identity, you’re able to be more flexible for future changes. Often, account data in an application starts off being specific to a user, but as the application grows in complexity, so does the scope of the data’s availability.
When application data is separate from authentication data, the decoupling makes it easier to make changes in the future. The disadvantage here is that when you want to get data about your application’s user, you need to load another object from the database on top of the model. Although, depending on your application, it could be worth it.
Additional / different authentication data
Another reason you may want to change the default model is if you want authentication to behave differently from the default behavior. Perhaps you want to support a phone number for communication purposes, or even use it as a unique identifier for the user. Many websites use an email address or a phone number in place of a username, in which case you’d definitely want to use a custom model.
Why do it anyway?
Whatever decisions you make now about how you want authentication to work in your system, it’s very likely that sometime in the future, you may want to change something. While you can always change things even without a custom User model, it’s much easier to implement changes if you already have one defined and created upfront.
Tutorial: Defining the custom model
For demonstration purposes, let’s assume with have a newly created Django project called Foo. For this project, we want to use an email address instead of a username for authentication, but still want to use Django’s built-in permissions system.
foo_auth application
First, we need to create an application where the custom model will reside, so let’s make a new application called foo_auth
:
./manage.py startapp foo_auth
With the new application created, we can register it with Django by adding it to INSTALLED_APPS
so that in foo/settings.py
, it looks like:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'foo_auth',
]
Defining the model
We want our custom user to look very similar to the default Django User, but instead of a username field, we want the email field to be the unique identifier. When you override the User model, there are a number of steps you have to take to make sure the interface is what Django expects, so that all the provided Django features still work.
To help with this, Django provides an AbstractBaseUser class and a BaseUserManager class that help provide some default behaviors and interfaces that Django expects to be present. Since we also want our user to support the default permissions, we can use the provided PermissionsMixin to make sure our User class works with Django’s permissions system.
Putting this all together in foo_auth/models.py
:
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin
)
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
class UserManager(BaseUserManager):
def create_user(
self, email, first_name, last_name, password=None,
commit=True):
"""
Creates and saves a User with the given email, first name, last name
and password.
"""
if not email:
raise ValueError(_('Users must have an email address'))
if not first_name:
raise ValueError(_('Users must have a first name'))
if not last_name:
raise ValueError(_('Users must have a last name'))
user = self.model(
email=self.normalize_email(email),
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
if commit:
user.save(using=self._db)
return user
def create_superuser(self, email, first_name, last_name, password):
"""
Creates and saves a superuser with the given email, first name,
last name and password.
"""
user = self.create_user(
email,
password=password,
first_name=first_name,
last_name=last_name,
commit=False,
)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name=_('email address'), max_length=255, unique=True
)
# password field supplied by AbstractBaseUser
# last_login field supplied by AbstractBaseUser
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_(
'Designates whether the user can log into this admin site.'
),
)
# is_superuser field provided by PermissionsMixin
# groups field provided by PermissionsMixin
# user_permissions field provided by PermissionsMixin
date_joined = models.DateTimeField(
_('date joined'), default=timezone.now
)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def __str__(self):
return '{} <{}>'.format(self.get_full_name(), self.email)
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
Registering the model
Now that we’ve defined our custom User model, we need to tell Django to use it. In foo/settings.py
we can add:
AUTH_USER_MODEL = 'foo_auth.User'
This tells Django (and any third party applications) where to find the model we need to use for authentication.
Generating the database migrations
Now that we have our custom User model defined and registered with Django, we can generate the migrations required to create the database table(s) for our new structure:
./manage.py makemigrations
The model in action
Now, we want to see our User model in action. The easiest way for us to do so within our application is to see what it looks like in the Django admin.
Setting up the database
Before we can see our model in action, we need to get the database setup. We do this by applying the migrations we created previously:
./manage.py migrate
Creating an admin user
To gain access to the Django Admin site, we need to create a user with admin access. The simplest way of doing this is to create a superuser through the command line client:
./manage.py createsuperuser
You’ll notice that this command prompts you for your email (the field we marked as the username field in our custom model), your first name, your last name (fields we marked as required) and finally a password. Once the command has been completed, the user has been created!
Making the custom user available in the admin
Using the email address and password provided to the createsuperuser command lets you log in to the admin site. When developing locally with default settings, the admin site should be available at: http://localhost:8000/admin/
Once you’ve logged in, you’ll notice that there is nothing in the admin for managing users. This is because we haven’t registered our new User model with the Django Admin site. To do this, in foo_auth/admin.py
we have:
Once you’ve logged in, you’ll notice that there is nothing in the admin for managing users. This is because we haven’t registered our new User model with the Django Admin site. To do this, in foo_auth/admin.py
we have:
from django.contrib import admin
from .models import User
admin.site.register(User)
This tells Django to create an admin page for the model with default settings. Now, if you reload the Admin, you’ll see a new entry for managing the mode, which means you can now create, update, and delete users.
Customizing the admin User page
The default page that Django generates for managing the user isn’t very well organized, the password setting doesn’t work correctly, and ultimately poses a security problem.
Anybody with admin access is able to modify the Superuser Status of any user, so let’s customize the Admin User page and change foo_auth/admin.py
:
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from .models import User
class AddUserForm(forms.ModelForm):
"""
New User Form. Requires password confirmation.
"""
password1 = forms.CharField(
label='Password', widget=forms.PasswordInput
)
password2 = forms.CharField(
label='Confirm password', widget=forms.PasswordInput
)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords do not match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UpdateUserForm(forms.ModelForm):
"""
Update User Form. Doesn't allow changing password in the Admin.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = User
fields = (
'email', 'password', 'first_name', 'last_name', 'is_active',
'is_staff'
)
def clean_password(self):
# Password can't be changed in the admin
return self.initial["password"]
class UserAdmin(BaseUserAdmin):
form = UpdateUserForm
add_form = AddUserForm
list_display = ('email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', )
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('first_name', 'last_name')}),
('Permissions', {'fields': ('is_active', 'is_staff')}),
)
add_fieldsets = (
(
None,
{
'classes': ('wide',),
'fields': (
'email', 'first_name', 'last_name', 'password1',
'password2'
)
}
),
)
search_fields = ('email', 'first_name', 'last_name')
ordering = ('email', 'first_name', 'last_name')
filter_horizontal = ()
admin.site.register(User, UserAdmin)
These changes let us create a new user with a properly set password in the Admin: we can update the “safe” fields on the user, and all of the fields are then nicely grouped and organized.
Referencing the User model
If you’d like to support projects where the AUTH_USER_MODEL
setting is modified, you unfortunately can’t just import the User model or reference it using appName.ModelName
.
Django provides safe mechanisms for accessing the User model as needed.
Referencing the User model (without using the actual model class)
When you want to reference the User model without importing it, such as in defining foreign key relationships, you should use the AUTH_USER_MODEL
setting:
from django.conf import settings
from django.db import models
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
Getting the User model
The ultimate location of the model is determined at runtime, so you can’t just import it. In your own projects where you control the AUTH_USER_MODEL
setting, you can get by with importing the Model directly. If you want to write code that can be used in other Django projects, however, this is unsafe.
Django provides a get_user_model method to access the model as needed, and it’s a good idea to get into the habit of using it to save yourself stress when you want to reuse an application you’ve built in another project.
For example, let’s assume that we’re creating a view that lists all of our users. This view might look something like:
from django.contrib.auth import get_user_model
from django.shortcuts import render
def user_list_view(request):
User = get_user_model()
return render(request, 'users_list.html', {'users': User.objects.all()})
In this view, the User model is loaded dynamically and correctly even if the User model has been customized.
What next?
We now have a custom User model that’s fully integrated into Django that we can reference correctly in our applications!
If there are changes that we want to make to our User model down the line, our project is set up to make those changes quickly and easily. It’s a little extra work when you’re getting your project started, but is definitely worth it for the flexibility it provides later on.
Key takeaways
Use a custom User model
Django makes it very easy to set up your own custom User model. Every project is likely to change over time and setting up a custom model from the onset can save you from some big headaches down the line!
Introducing a custom model later requires you to make changes to table names, as well as manipulate migrations to get the database to match the new state Django thinks it’s in when you introduce a new model in a new application.
Correctly reference the User model
Remember to always reference and retrieve the model in the recommended ways to keep your code consistent, so that if you decide to generalize or reuse existing code, it all works without having to find every reference to the model and update it.