The Problem

Django is a really great web application framework that can help developers quickly and (relatively) painlessly get going on the creation of web applications. It does a lot of stuff out of the box that we used to have to spin up by hand.

When I first started learning to code projects using Django, however, I ran into an issue with user management that the getting started documentation didn't do a great job of explaining. Here's the issue: most of the time, developers will want to substitute a custom user model for Django's default. If you don't do this at the very beginning of the project, before even your first database migration, you're going to be in a load of trouble, as much downstream logic depends on having a custom user model substitution baked into your app from the outset.

Again, the Django quick start docs don't adequetly warn you about this! They claim the following: "The authentication that comes with Django is good enough for most common cases, but you may have needs not met by the out-of-the-box defaults." I think it's misleading to say the default is "good enough for most cases." The default, for example, requires both a username and an email address as part of the user model. Many web apps do separate usernames and email addresses, but many don't. And if you don't want separate usernames and email addresses for your users, you're going to need to customize the user model.

The Solution

For this and many other reasons having to do with customizing your users' experience of your Django app, I highly recommened ALWAYS replacing the Django user model with a custom one at the beginning of a Djagno project. There are a few ways to do this, but I'm going to cover the one I use most frequently: subclassing the AbstractUser model and setting up a corresponding Profile table to store non-authentication user information. When we choose this route, we're basically saying that most of Django's preferences about the user model are ok with us, but we'd like to change the username field and associate profile information with each user in a separate Profile table. In general, it's best to keep profile information (date of birth, address, profile picture, etc) separate from authentication information.

Step 1: Project Setup

Make a project folder, navigate to it, create a virtual environment, and activate the virtual environment.

$ mkdir my-django-app && cd my-django-app
$ python3 -m venv djangoenv
$ source djangoenv/bin/activate

Install Django in your virtual environment. Create your Django project and a "users" app within that project.

(djangoenv)$ pip install django
(djangoenv)$ django-admin.py startproject django_appname && cd django_appname
(djangoenv)$ python manage.py startapp users

Add your users app to the installed apps in your settings.py file. This file is located within your main project folder, created above.

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
// Add your additional apps here
'users',
]

In the same settings.py file, add a local development database. (Or, if you're really fancy, a live development or production database.) I am using a MySQL database in my setup, but you can use whatever DB you want:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'your_database_name',
'USER': 'db_user',
'PASSWORD': 'db_password',
}
}

DO NOT RUN MIGRATIONS AT THIS POINT! Before we run any migrations to our database, we need to create our custom user model.

Step 2: Create a Model Manager and Custom User Class

From the Django docs: A Manager is a Django class that provides the interface between database query operations and a Django model. It essentially functions as a gate between your application and its database. Every model has a manager, and now we need to define a custom manager for our custom user model, which will allow us to use email addresses instead of usernames as unique identifiers for our application's users. Our model manager will allow us to create both regular users and superusers.

In your /users directory, create a file called managers.py that contains the following:

from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _

class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError(_('Email address cannot be blank.'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user

def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)

if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(email, password, **extra_fields)

Now we're going to create our custom user model by subclassing Django's built-in AbstractUser. In your /users directory, update the models.py file to contain the following custom user model definition:

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _

from .managers import CustomUserManager

class MyAppUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []

objects = CustomUserManager()

def __str__(self):
return self.email

class Meta:
db_table = 'MyAppUser'

Now we've got a subclass of AbstractUser called MyAppUser. This is your new, custom user model! We've removed the username requirement and set the email address to be a unique and required field.

Step 3: Tell the Django App to Use the New Model

In your main project folder, /django_appname, open settings.py and add the following line:

AUTH_USER_MODEL = 'users.MyAppUser'

This tells Django to use the new custom user model you created instead of its default. This is a very important step!

Now you are ready to create a migration and run that migration so that your Django app's database reflects the changes you have made.

(djangoenv)$ python manage.py makemigrations
(djangoenv)$ python manage.py migrate

Congratulations! Your app now uses your new custom user model.

Step 4: Forms and Admin

Next, we need to make a few updates to our application's user forms and admin views to reflect the fact that we are using a custom user model.

In your /users directory, create a forms.py file with the following:

from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import MyAppUser

class CustomUserCreationForm(UserCreationForm):

class Meta(UserCreationForm):
model = MyAppUser
fields = ('email',)

class CustomUserChangeForm(UserChangeForm):

class Meta:
model = MyAppUser
fields = ('email',)

This tells the application that the forms that control user creation and user changes should refer to our new user model.

Now open users/admin.py and replace with the following:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

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

class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = MyAppUser
list_display = ('email', 'is_staff', 'is_active',)
list_filter = ('email', 'is_staff', 'is_active',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Permissions', {'fields': ('is_staff', 'is_active')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
search_fields = ('email',)
ordering = ('email',)

admin.site.register(MyAppUser, CustomUserAdmin)
Step 5: Test Everything

Now we're going to make sure everything is working. Let's start by using the terminal to create a superuser for your application. If everything worked correctly, you will be prompted to enter an email address instead of a username:

(djangoenv)$ python manage.py createsuperuser
Email address: your_email@test.com
Password:
Password (again):
Superuser created successfully.

Now, start your server and navigate to the application URL. I've specified a port here (:8005), but that's optional.

(djangoenv)$ python manage.py runserver 127.0.0.1:8005

Navigate to 127.0.0.1:8005/admin and login with the credentials you just created. Now, you'll be able to create users with your custom user model!

Step 6: Add Profiles

Now we need to create a one-to-one link between the custom user model we just created and a new Profile table that we're going to spin up. In users/models.py, we're going to add the following under the MyAppUser class that you defined earlier:

class Profile(models.Model):
user = models.OneToOneField(MyAppUser, on_delete=models.CASCADE)
birth_date = models.DateField(db_column='DateOfBirth', null=True, blank=True)

class Meta:
db_table = 'Profile'

def __str__(self):
return self.user

Now, we're going to set up some signals, which are Django's way of defining senders and receivers and allowing instructions to be sent from one part of your application to another. Add a few more imports to the top of users/models.py:

from django.db.models.signals import post_save
from django.dispatch import receiver

And now, below the Profile class, define the signals that will tell your application to create or update user Profiles when the MyAppUser instance is created or updated:

@receiver(post_save, sender=MyAppUser)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)

@receiver(post_save, sender=MyAppUser)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()

Now, run a migration:

python manage.py makemigrations
python manage.py migrate

Head back to 127.0.0.1:8005/admin with your superuser credentials. Now, when you create a new user, you will also create an entry in your profile table! You can learn more about using this one-to-one relationship in Django views and forms by checking out this article.

That about wraps it up. Following these few simple steps at the very beginning of your Django project will save you a lot of time and effort later. If you're just starting your Django journey but you're hoping to use your app in production some day, remember to employ a custom user model!