In this article I will show you how to add a custom user model to your Django Project.
Why do you need a custom user model in your Django project?¶
Django is a powerful, batteries-included web framework.
It's strong side is built-in authentication, user model, permissions already taken care for you.
The standard user model might not well for your project.
In fact, in almost all projects I've seen there was a need to add some additional pieces of data to the existing user model. Be it social media links, profile picture, some security related fields.
Here comes a few problems:
- You can't change the standard user model
- It is close to impossible to change the user model to a custom one mid-project, a.k.a. when the data is already there
If you already have an existing project and you need additional pieces of data, the best option for you is to create an additional model with 1-to-1 relationship to the user model, e.g. UserProfile
and add all the fields there. Of course, you need to populate the UserProfile
table and make sure a new user profile is created for new users, for example using signals.
But if you haven't published your project yet you need to create a custom user model and set your Django project to use it right away. Then you have freedom to shape it as you want during the lifetime of your project.
In this article I will show you how to create a custom user model and enable it in your Django project.
Requirements¶
You need to have Docker installed, because we will be using it to run our app.
Also, we use this tutorial as a starting point for this article Django Docker Tutorial with Postgres
Please clone that repository to be able to follow this one step by step.
Source code¶
The code for this tutorial is available on GitHub: appliku/django-custom-user-model
Create a new app for our custom user model¶
If you have followed the previous article, you know that we are running commands within a Docker container.
To start a shell within a Docker container run at the root of the project:
docker compose run web bash
Now let's start our app for a custom user model usermodel
:
python manage.py startapp usermodel
Go to project/settings.py
and add usermodel
to INSTALLED_APPS
and specify the user model setting.
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"mainapp",
"usermodel",
]
AUTH_USER_MODEL = "usermodel.User"
Open usermodel/models.py
Let's create our User
model:
import uuid
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.conf import settings
from usermodel.managers import UserManager
class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(
db_collation="und-x-icu",
max_length=255,
unique=True,
error_messages={
'unique': _('A user with such email already exists')
}
)
first_name = models.CharField(_('First Name'), max_length=50, blank=True)
last_name = models.CharField(_('Last Name'), max_length=50, blank=True)
is_staff = models.BooleanField(
_('Staff Status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.')
)
is_active = models.BooleanField(
_('Active'),
default=True,
help_text=_('Designates whether this user should be treated as active.')
)
is_email_confirmed = models.BooleanField(
_('Email Confirmed'),
default=False
)
date_joined = models.DateTimeField(
_('Date Joined'),
default=timezone.now
)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [
'first_name',
'last_name',
]
class Meta:
verbose_name = _('User')
verbose_name_plural = _('Users')
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
def get_short_name(self):
return f"{self.first_name}"
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email, recipient_list=[self.email], **kwargs)
Let's go over this code:
- We have defined a
User
model - the
id
field is a UUID field, not a sequential integer. If you'll need to show ID somewhere, people won't be able to estimate the number of users in your app, nor perform enumeration attacks - The
email
field is a case-insensitiveEmailField
with collation set to be used for Postgres. UseNOCASE
for SQLite andutf8mb4_unicode_ci
for MySQL USERNAME_FIELD = 'email'
makes theemail
field a username field. This means we won't have a separate username field anymore. Unless, you are doing some sort of a social network, I think it is more convenient.- We also define a custom manager
objects = UserManager()
Create usermodel/managers.py
and define the custom user model manager:
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
if not email:
raise ValueError('The email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email=email, password=password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields['is_staff'] = True
extra_fields['is_superuser'] = True
return self._create_user(email=email, password=password, **extra_fields)
We need a custom manager because Django's standard user creation methods have username as an argument and we don't use that field anymore.
Last step, open usermodel/admin.py
file.
Define and register UserAdmin
class.
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from usermodel.models import User
from django.contrib.auth.admin import UserAdmin as DefaultUserAdmin
class UserAdmin(DefaultUserAdmin):
fieldsets = (
(None, {"fields": ("email", "password")}),
(
_("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(
_("Important dates"),
{
"fields": (
"last_login",
"date_joined",
)
},
),
(_("User data"), {"fields": (("is_email_confirmed",),)}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("email", "password1", "password2"),
},
),
)
list_display = ("email", "first_name", "last_name", "is_staff")
search_fields = ("first_name", "last_name", "email")
ordering = ("email",)
admin.site.register(User, UserAdmin)
Now create migrations and apply migrations. As always, in the Docker container.
python manage.py makemigrations
python manage.py migrate
The output should look like this:
app@299f5be8f005:/code$ python manage.py makemigrations
Migrations for 'usermodel':
usermodel/migrations/0001_initial.py
+ Create model User
app@299f5be8f005:/code$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, usermodel
Running migrations:
Applying contenttypes.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0001_initial... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying usermodel.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying sessions.0001_initial... OK
app@299f5be8f005:/code$
Automatically create a superuser¶
Using built-in Django user management command¶
Now we need to add a user.
The first option is to use python manage.py createsuperuser
command.
Hint: you can do unattended superuser creation if you set environment variable with the password and a few arguments:
DJANGO_SUPERUSER_PASSWORD=somethingsupersecret123 python manage.py createsuperuser --email=admin@example.com --first_name=Admin --last_name=Super --noinput
Please note, that --first_name
and --last_name
are required here because of these lines in our model:
REQUIRED_FIELDS = [
'first_name',
'last_name',
]
I am a huge fan of automation and that implies usage of idempotent commands, meaning those which you can run as many times as you want and get the same result.
We will make a custom management command for superuser creation.
Continue reading: Create a custom management command: Create super user for a custom user model