What makes Django different to other web frameworks is it includes a model for users. This means it is possible to add user registration to your project without having to create a table to store user data in the database yourself.
I recently wrote a tutorial on basic user registration showing how little custom code it takes to add a registration form to your project.
You can use Django’s built in user model for convenience but it does mean you have to use the
username field to identify users. You may ask, can users identify themselves with their email instead?
This is what I am going to show you today.
We are not going to create a user model from scratch. Django’s built-in user model is very useful. It would be a shame to lose it just because we want to make a small tweak.
Instead, we going to extend the default user model with our own Custom User Model. This means we can change what ever we need and keep the rest.
Project Set Up
I have created a repository in GitHub with the code for this tutorial. To follow along, clone the START branch. The main branch contains the finished code.
Or start with a blank project. I have a tutorial on setting up a Django project.
I also have a tutorial on creating a basic user registration system with a username and password. The end result is the same.
Because we are going to override the default user model, I recommend starting with a new project. This is because there are some changes we need to make before running migrations for the first time. If you want to retrofit an existing project, then I recommend dropping your database before removing migrations from your apps.
This is what we are starting with.
A simple homepage telling the user if they are logged in.
The ability to register users with a username and password. This makes use of Django’s
A login form that makes use of Django’s built-in
What we are going to do
Our goal is to authenticate users using their email address. Users should not have to provide a username.
- Creating a custom user model
- Creating a custom user manager
- Creating a new user creation form
This is more complicated than authenticating with a username because Django’s default user manager and user creation forms both expect a username and not an email. However, by the end of this tutorial, you will have a deeper understanding of how Django works.
If you don’t already refer to the Django source code, this is the perfect opportunity to start. Look at the classes we inherit from, including
UserCreationForm. This will help you understand how Django works and why each step is required.
What we are not going to do
We are not going to write anything from scratch. Django’s default user model and user creation forms have lots of useful functionality such as validating passwords. Rather than write completely new classes, we are going to extend these classes to tailor it to our needs.
Our approach is to write a little custom code as possible.
Let’s get started.
Why do we need to start with a fresh database?
We are going to create a custom user model that replaces Django’s default user model.
The problem is, Django creates the User model when you run migrations for the first time. Once that User table has been created in the database, it is difficult to replace it with a custom model.
users.0001_initial creates the User table.
Before we run those migrations, we need to make some changes.
Create a Custom User Model
If you have cloned the START branch of the repository, you can skip this step as the code already has a custom model defined.
We need to extend Django’s default user model, which we can do by inheritance.
users/models.py and add a class called
User. It must inherit from
AbstractUser which is imported from
# users/models.py from django.contrib.auth.models import AbstractUser class User(AbstractUser): pass
For now, we will just add
pass, which means we’re keeping all the functionality of the AbstractUser model. We will customise it to use email instead of username later in the tutorial.
By inheriting from
AbstractUser, we give ourselves the ability to customise the model without having to write a new one from scratch. After we run migrations for the first time, we will still be able to customise it how we like.
Tell Django Where to Find the User Model
So far, we have inherited the
AbstractUser model but Django doesn’t know it’s intended to be the user model.
We need to update
settings.py to state the location of the user model.
# settings.py AUTH_USER_MODEL = "users.User"
Make sure you also add
users to the list of installed apps:
# settings.py INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "users", "core", ]
Create & Run Migrations
Now that we have created a custom user model, we need to create and run the migrations. Migrations are what Django uses to interpret your Python models and convert it into SQL operations for the database. They can be really confusing for beginners, which is why I have written this guide on understanding migrations.
Create migrations by running this line. If you have any problems with your settings, they will appear when you attempt to run this command. If you’re stuck, refer to the settings in my GitHub repository.
python manage.py makemigrations
You should get an output similar to this:
Migrations for 'users': users/migrations/0001_initial.py - Create model User
Now run the migrations:
python manage.py migrate
Customise the User Model
Right now, our User model only inherits from Django’s Abstract User model.
class User(AbstractUser): pass
We want to customise this model so that the email field is used as the unique identifier of users.
We can do this by overriding the
USERNAME_FIELD of the Abstract User model.
The problem is, Django expects to the value of the
USERNAME_FIELD to be unique. In the source code for
AbstractUser, the username field is required to be unique, but not the email field. We need to override the email field to make sure every user has a unique email address.
We also need to remove “email” from the
REQUIRED_FIELDS list. Django will give you an error if you forget to do that.
This is the code for my model.
# users/models.py class User(AbstractUser): email = models.EmailField(unique=True, blank=False, null=False) USERNAME_FIELD = "email" REQUIRED_FIELDS =  objects = CustomUserManager()
At this point, you should get an error to say the
CustomUserManager is not defined. We will define it in the next step.
Create a Custom User Manager
Why is a custom user manager required?
If you use
User.objects.create to create users, passwords would be stored in plain text. We need to hash and salt passwords before saving them to the database.
A model manager lets you define your own interface for creating users. By default, the
AbstractUser model uses a class called
UserManager to provide methods called
Instead of calling
User.objects.create(...) to create users, you can use
We can’t use the
UserManager class as it expects a username. We need to create our own manager that will handle email instead.
The manager needs two public methods:
The two methods will be almost identical. The only difference is that superusers will have the
is_staff set to “True”, which is needed to access the admin site.
The Custom User Manager class needs to go in
models.py above the User class.
from django.db import models from django.contrib.auth.models import AbstractUser, UserManager from django.core.validators import validate_email class CustomUserManager(UserManager): def _get_email(self, email: str): validate_email(email) return self.normalize_email(email) def _create_user( self, email: str, password: str, commit: bool, is_staff: bool = False, is_superuser: bool = False ): email = self._get_email(email) user = User(email=email, username=email, is_staff=is_staff, is_superuser=is_superuser) user.set_password(password) if commit: user.save() return user def create_superuser(self, email: str, password: str, commit: bool = True): return self._create_user(email, password, is_staff=True, is_superuser=True, commit=commit) def create_user(self, email: str, password: str, commit: bool = True): return self._create_user(email, password, commit=commit)
To avoid writing the same code twice, I have created a private method called
_create_user that checks the email and creates the user. This is called by both
You can define private methods on a class by prefixing the name with an underscore. This means that other methods of the class can access
_create_user but you can’t access it from outside the class. This means
User.objects._create_user won’t work.
Our user model inherited a username field from
AbstractUser. When you inherit models, it is easy to add or modify fields but hard to take them away.
I want users to be able to sign up without providing a username. As a workaround, we will save the email address to the username field. This will satisfy the requirement for the username to be unique.
If you don’t want to have a username column in your user model at all, then you will need to declare a user model that inherits from
AbstractBaseUser. This will give you full control over what fields go into the model, but requires more work.
For now, this workaround lets us keep the benefits of the
Checking the email
In most cases, users will be created using a form or using
python manage.py createsuperuser. Both of these ways will do their own validation of the email address before
User.objects.create_superuser is called.
However, if your application has a third way to create users that doesn’t have its own validation, then it makes sense to check emails before saving them to the database.
I have used
validate_email, a function imported from
django.core.validators. If you try to provide an invalid email or an empty email, then Django will raise a Validation Error.
I have also used the
normalize_email method to convert the domain part of the name to lower case.
Notice how we don’t call
User.objects.create to create the user. If you do this, the password would be stored in plain text for all the admin users to see.
Instead, we first create an instance of the User model. This means you create an object without saving it to the database.
Our user model has inherited a method called
set_password from the
The best way to set a password is by calling the
set_password method on the user instance. The
set_password method will encode the password provided by the user which means admins and database users will not be able to see users’ passwords.
What is commit for?
create_superuser methods have an argument called
commit. This gives us the option to not commit the object to the database. Sometimes, you want to get an instance of a user without saving it to the database. You would usually do this if you wanted to modify the instance before saving it.
You can leave it out if you like. However, we do make use of it later in this tutorial when we override the
save method of the registration form.
Migrate your changes to the database
Now that we have updated the User model and added a custom user manager, we are now ready to make migrations and apply them to the database.
python manage.py makemigrations.
You should see a similar output to the snippet below.
Migrations for 'users': users/migrations/0002_alter_user_managers_alter_user_email.py - Change managers on user - Alter field email on user
Create a form
Our next step is to create a new form.
We can’t use Django’s
UserCreationForm on its own as it displays a username field and not an email field.
To start, create a new file in the
users app called
forms.py, create a new class that inherits from Django’s
UserCreationForm inherits from Django’s
ModelForm, which means all the fields on the User model are available to include on the form. This means, we just need to hide the username field and display the email field.
# users/form.py from django.contrib.auth.forms import UserCreationForm from .models import User class UserRegistrationForm(UserCreationForm): class Meta: model = User fields = ("email",)
You don’t need to include “password” in the fields tuple because they have already been defined in the
Override the save method
Our form comes with a method called
save, which we inherited from Django’s
If you call save on a form instance (e.g.
form.save()), then Django will create an object in the database.
The problem is, the
save method on the
UserCreationForm doesn’t make use of the model manager we created in the previous step.
If you don’t override the save method, then Django will attempt to assign an empty string to the username field. This will work for the first user you create, but you will get an
IntegrityError when creating a second user. This is because the username field must be unique.
Your options are:
- Override the username field on the model to remove the uniqueness constraint
- Don’t use
form.save(). Configure your view to use
- Override the save method to use the model manager.
We are going to override the save method.
Here is our updated form.
class UserRegistrationForm(UserCreationForm): class Meta: model = User fields = ("email",) def save(self, commit: bool = True) -> User: email = self.cleaned_data["email"] password = self.cleaned_data["password1"] return User.objects.create_user(email, password, commit=commit)
The save method accepts a boolean argument called
commit is True, then the user will be saved to the database. If
commit is False, then the save method will just return an instance of
User. If you do this, you can call
user.save() later to save the user to the database.
Update the View
The view needs to be updated to use our new User Registration form.
We can remove the import of
UserCreationForm and replace it with our custom form.
from django.contrib.auth.views import LoginView from django.views.generic import CreateView from django.urls import reverse from .forms import UserRegistrationForm class UserRegistration(CreateView): template_name = "user_registration.html" form_class = UserRegistrationForm def get_success_url(self): return reverse("login")
We can now reload our registration page to see the results
Same form, except it now accepts an email instead of a username.
We don’t need to make any changes to our login form. This is because we set the
USERNAME_FIELD on our User model to
Once logged in, we are redirected to the homepage.
Add the User model to the admin site
Our final step is to register the User model, so that we can view and update users on the admin site.
To do this, go to
users/admin.py and register the model.
# users/models.py from django.contrib import admin from .models import User admin.site.register(User)
We have created a user registration system that uses an email instead of a username to identify the user.
There are several ways to go about this, but I have chosen the method that minimises the amount of custom code we have to write.
First, we created a custom user model that inherits from
AbstractUser. We save time by choosing
AbstractBaseUser. The latter only includes basic authentication and we would have to write the rest from scratch.
Next, we created a custom user manager. This provides us an interface to create users that handles passwords properly.
Then, we created a custom user creation form. We have to do this because the default form displays the username and not the email. Using a class-based view, we only have to update the
form_class attribute of our view to use our new form.
We didn’t have to update the Login form because we set the
USERNAME_FIELD on our model. This tells Django to display the email field instead of the username field.
Our final step was to register the User model in
admin.py so we could view and edit users in the admin site.
If you have made it this far, then congratulations. This is not an easy topic. It requires an understanding of authentication and how Django’s models, managers, views and forms fit together.