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!
what if a want to register a user using my backend?
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.
Hi,
downcast_user_type – it is not a ModelBackend’s method. I mean you are not overriding it.
How it is used?
It is used internally in PersonOrKitBackend to cast the User object to a PersonUser or KitUser object.
Hi,
Error : AttributeError: ‘AnonymousUser’ object has no attribute ‘_meta’
why?
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.