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. The code samples are for Django 1.11.
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 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!