Creating Multiple Custom User Types Through Inheritance in Django

Django Logo

When developing applications in Django, the need might arise to customize the user model. Specifically, you might want to create different types of users. In my case, I’m interested in creating a person user and a kit user. A person user can own multiple kit users, and both need to be able to authenticate to access an API. Luckily, Django’s authentication system is very flexible, and there are multiple ways to achieve this goal.

The standard way to implement this is to stick with the default user model, django.contrib.auth.models.User, and create a complex user profile. The profile adds the desired fields and behaviors for the various user types in a new model, and links to the model through a field reference. This can get fairly complex quickly. It is especially difficult to express ownership of kits by users, without allowing ownership of users by users. Here, we will see how we can implement this using inheritance.

Using Django’s support for model inheritance to create multiple¬†first class user types, modeling complex user relations becomes easier. There is only one caveat to this: there must be a fully functional “base user” type that Django can use. The easiest way is to start by creating your base user inheriting from django.contrib.auth.models.AbstractUser, which provides all functionality necessary for authentication and permissions, but there’s nothing stopping you inheriting from django.contrib.auth.models.AbstractBaseUser if you require more customization (or even to write the entire user model from scratch). Note that customizing the user model in such a way is best done at the start of a project, but it can be done mid-project.

# myapp/models.py

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

As django.contrib.auth.models.AbstractUser provides us we everything we need for now, we can simply leave the class as-is.

Next, we can create our custom user types. In my case, I’ll create the person and kit users.

# myapp/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models

#...

class PersonUser(User):
    class Meta:
        verbose_name = 'Person'
        verbose_name_plural = 'People'

class KitUser(User):
    type = models.CharField(max_length = 10)
    users = models.ManyToManyField(PersonUser)

    class Meta:
        verbose_name = 'Kit'
        verbose_name_plural = 'Kits'

We have now created two user models, both of which are first class users, and can be customized and related to each other as any other model can.

We now need to tell Django how it can find our users by creating a new authentication back-end. Luckily this is relatively straightforward: we can re-use the default back-end and downcast the retrieved base user to either a person or a kit as appropriate.

# myapp/auth.py

from django.contrib.auth.backends import ModelBackend
from backend.models import KitUser, PersonUser

class PersonOrKitBackend(ModelBackend):
    """
    Backend using ModelBackend, but attempts to "downcast"
    the user into a PersonUser or KitUser.
    """

    def authenticate(self, *args, **kwargs):
        return self.downcast_user_type(super().authenticate(*args, **kwargs))
        
    def get_user(self, *args, **kwargs):
        return self.downcast_user_type(super().get_user(*args, **kwargs))

    def downcast_user_type(self, user):
        try:
            kit_user = KitUser.objects.get(pk=user.pk)
            return kit_user
        except:
            pass

        try:
            person_user = PersonUser.objects.get(pk=user.pk)
            return person_user
        except:
            pass

        return user

Finally, we configure settings.py to tell Django we want to use our custom user model, and to include the new authentication back-end.

# settings.py

# Authentication
AUTHENTICATION_BACKENDS = (
    'myapp.auth.PersonOrKitBackend',
)
AUTH_USER_MODEL = 'myapp.User'

That’s all there is to it!

6 thoughts on “Creating Multiple Custom User Types Through Inheritance in Django

    1. You create a user of the correct model, and save that user. In my case, a PersonUser can register, so on registration I create a PersonUser object and save it.

    1. I think this error is unrelated to the things outlined in this post.

      It potentially could have to do with the version of Django you’re running. The post was written with version 1.11 in mind.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.