Creating a Custom User Model

There are few different ways of customizing Django's User model. Depending on your needs, one could be more appropriate than the other. In this post I will show you how to replace the default User model by creating a custom model. Django's recommended method for this is to create a class that extends AbstractBaseUser that Django provides, and that is what we are going to use.

Considerations

It is really important to be sure that a custom model is indeed what you need, as you will see later, there are few complications associated with it. Here are some of the other options that might be more suitable for your needs.

  • One-to-one Profile model: You can create a new model that holds extra information about a user that is not related to the authentication process. This model will has a One-to-One relation with the default User model, and the User model will not have to be modified or replaced. If you want to have fields like date of birth, location or hobbies for each user, then this option might be more appropriate for you.

  • Proxy model: Proxy models are used to change the behavior of existing models, without adding extra tables in the database. You can use a Proxy model for the User model if you want to change something in the default manager or add new methods to the model.

  • Extend AbstractUser: This is very similar to what I'm going to show today. In this method, you extend the AbstractUser class, instead of AbstractBaseUser class as in our case. This gives you less options for customization, but it could be suitable for you if all you want is to add some extra fields to the User Model.

You can read about all the options in their documentation.

Implementing a custom user model mid-project

If you are going a create a custom user model by extending AbstractUser or AbstractBaseUser, you should always try do it at the start of the project. The documentation says that the custom User model and it's reference (which I'll show later) "must be created in the first migration of its app", otherwise you’ll have dependency issues. I personally always create the custom model before the first migration of the project itself, as I usually still get errors if I do it later. Would appreciate any thoughts on this in the comments.

Now that that's out of the way, lets get started.

What we are dealing with

The default User class inherits the AbstractUser class, which uses the UserManager class. Since we will be replacing these, lets take a look at what they are.

Open django\contrib\auth\models.py and find these classes:

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        """
        Creates and saves a User with the given username, email and password.
        """
        if not username:
            raise ValueError('The given username must be set')
        email = self.normalize_email(email)
        username = self.model.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', 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(username, email, password, **extra_fields)


class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """
    username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator()

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
        validators=[username_validator],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    email = models.EmailField(_('email address'), 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. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        abstract = True

    def clean(self):
        super(AbstractUser, self).clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Returns 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 get_short_name(self):
        "Returns the short name for the user."
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """
        Sends an email to this User.
        """
        send_mail(subject, message, from_email, [self.email], **kwargs)


class User(AbstractUser):
    """
    Users within the Django authentication system are represented by this
    model.

    Username, password and email are required. Other fields are optional.
    """
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

Let's look at the significant parts of the above code:

BaseUserManager

  • create_user() method takes the field values, sets the value of is_staff and is_superuser to False, and calls _create_user()
  • create_superuser() method takes the field values, and makes sure values of is_staff and is_superuser is set to True, and then calls _create_user().
  • _create_user() takes in values from either of the function, makes sure the username field is provided and basically just saves the user in the database.

AbstractUser

  • Like any other model, it defines the fields that should be in the model, from username to date_joined.
  • It defines the Manager to be used with objects = UserManager()
  • EMAIL_FIELD defines the field which represents the user's email address.
  • USERNAME_FIELD This defines which field is to be used as the unique identifier. In the default case, it is the username field. We will be changing this in our custom implementation.
  • REQUIRED_FIELDS = This defines the list of fields that should be required, apart from the USERNAME_FIELD. This only has the effect when creating a user using the createsuperuser management command , and doesn't affect other parts of Django.
  • def clean() uses normalize_email, which just lower-cases the domain portion of the supplied email address.
  • get_full_name() - A formal representation of the user, in the default case, it is literally full name of the user.
  • get_short_name() - An informal way to represent the user.
  • email_user() - Well, this one... to quote the doc-string itself, "Sends an email to this User."

User

This simply inherits the AbstractUser, and makes itself swappable by AUTH_USER_MODEL setting, which we will actually do later.

Now that you have some understanding of how the default User model works, lets start creating what we need.

Creating the User Model

The Django documentation shows an example of a custom user model that sets the email address of a user as the unique identifier, and lets them login with that, instead of the username. I'm going to do the same by having the email as the unique identifier and also adding some extra fields.

Make sure you create an app where you want to add all the code for the custom model. I'm going to call mine 'accounts'. Also remember, don't run any migrations till you have defined and referenced the custom model.

In accounts' model.py

from django.contrib.auth.models import (
    AbstractBaseUser,
    BaseUserManager,
    PermissionsMixin
)
from django.db import models
from django.utils import timezone


class UserManager(BaseUserManager):
    def create_user(self, email, display_name=None, password=None):
        if not email:
            raise ValueError("Users must have an email address")

        user = self.model(
            email=self.normalize_email(email),
            display_name=display_name
        )
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, display_name, password):
        user = self.create_user(
            email,
            display_name,
            password
        )
        user.is_staff = True
        user.is_superuser = True
        user.save()
        return user


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=30, blank=True)
    last_name = models.CharField(max_length=30, blank=True)
    display_name = models.CharField(max_length=140)
    bio = models.CharField(max_length=140, blank=True, default="")
    date_joined = models.DateTimeField(default=timezone.now)
    last_login = models.DateTimeField(blank=True, null=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["display_name",]

    def __str__(self):
        return self.display_name

    def get_short_name(self):
        return self.display_name

    def get_full_name(self):
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

Since we have already looked at the default User model, this shouldn't be too hard to understand. Lets again look at this step by step.

  • At the top we import the classes AbstractBaseUser , BaseUserManager and PermissionsMixin. The default user model and manager used these same classes and we need to do the same.
  • We import models because we want to create some model fields. We import timezone because we have a DateTimeField that uses it.
  • The UserManager is just like the default manager, except instead of a username field, we just have an email and display_name field. Also notice that it doesn't use a common function like _create_user, instead the createsuperuser relies on createuser to create the user. But functionally there is no difference.
  • In the User class, the email field is set to true and the display_name and bio fields are added.
  • We set the USERNAME_FIELD as email, because that's all that we ever wanted (letting users login with email instead of username).
  • REQUIRED_FIELDS is set to display_name, as we want to ask for it when creating a superuser.
  • __str__ returns the user's display_name. This wasn't there in the default User model, but since it's there in the documentation, I've included I used it too.
  • get_short_name returns the display_name
  • get_full_name, this is the same as it was in the default model.

This is it as far as creating a model is concerned. Now we need to make sure our project and all the apps are able use this model instead of the default one.

Making the switch

Now we just need to tell Django that we have created a custom user model and would like it to use that instead.

In settings.py, add:

AUTH_USER_MODEL = "accounts.User"

In all of your models, where you import the default User model and use it in your fields, you need to replace all that with the custom one.

In models.py of all your apps:

# Replace the import of default User model
from django.contrib.auth.models import User

# With
from django.conf import settings

# Then change fields that use the User model, Like so:
# Replace
user = models.ForeignKey(User)

# With
user = models.ForeignKey(settings.AUTH_USER_MODEL)

You have to do the above wherever you refer to the string representation of a model. In cases where you actually define a 'model', such as in Model Forms, you have to use get_user_model()

Inside forms.py:

# Replace the import of default User model
from django.contrib.auth.models import User

# With
from django.contrib.auth import get_user_model

# Then:

#Replace
model = User

#With
model = get_user_model()

Now you should be able to run migrations for all your apps. However, let me remind you that you should always try to implement custom user model at the start of a project, or you could get errors.

Admin

In order to view, create and update users from your admin, you need to register them in the admin.py file. Also, the forms UserCreationForm UserChangeForm that are used in creating and updating users, need to be rewritten as they are tied to the default User model.

In accounts' forms.py add:

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import ReadOnlyPasswordHashField

class UserCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = get_user_model()
        fields = ('email', 'display_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 don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

class UserChangeForm(forms.ModelForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = get_user_model()
        fields = '__all__'

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]

If you look at the default forms in the source code, there is really nothing much we have changed, mostly we only changed the model value to get_user_model()

Next, we need to register our new models in the admin and make it use our custom fields and these two forms. In accounts' admin.py add:

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from . import forms

class UserAdmin(BaseUserAdmin):
    # The forms to add and change user instances
    form = forms.UserChangeForm
    add_form = forms.UserCreationForm

    # The fields to be used in displaying the User model.
    # These override the definitions on the base UserAdmin
    # that reference specific fields on auth.User.
    list_display = ('email', 'display_name', 'date_joined', 'is_staff')
    list_filter = ('is_staff',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal info', {'fields': ('first_name', 'last_name', 'display_name')}),
        ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser',)}),
        ('Important dates', {'fields': ('last_login', 'date_joined')}),
    )
    # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
    # overrides get_fieldsets to use this attribute when creating a user.
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'date_joined', 'password1', 'password2')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

# Now register the new UserAdmin...
admin.site.register(get_user_model(), UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)

This code is almost entirely from the documentation, but basically we just define the fields we want to use, and the forms for adding and updating the users, along with other display attributes. And then we register it using get_user_model().

Closing

You should now have a fully integrated custom user model. Here are some helpful links.

Django's documentation - About customizing authentication.

django-registration - Library for helping setup a registration system, with some useful extra options.

django-allauth - A personal favorite library for setting up an authentication, with advanced options and social authentication.

Share your comments below and thank you for reading.

blog comments powered by Disqus