How to Implement Multiple User Types with Django

Rules of Thumb

What you are going to read next is not written in a stone. It’s just some general recommendations that fits most cases. If you have a good reason, or if not following those recommendations will result in a better application design, go ahead and break the “rules”!

1. No matter what strategy you pick, or what is your business model, always use one, and only one Django model to handle the authentication.

You can still have multiple user types, but generally speaking it’s a bad idea to store authentication information across multiple models/tables. Treat this model as an account rather than a user. Meaning, all users need anaccount to log in.
The business logic will be implemented in a different way, so no need to have username/password spread across multiple tables. After all, all accounts should share many common resources such as login, logout, password resets, password change.

2. Never user the built-in Django User model directly, even if the built-in Django User implementation fulfill all the requirements of your application.

At least extend the AbstractUser model and switch the AUTH_USER_MODEL on your settings.
Requirements always change. You may need to customize the User model in the future, and switching theAUTH_USER_MODEL after your application is in production will be very painful. Mainly because you will need to update all the foreign keys to the User model. It can be done, but this simple measure (which, honestly, is effortless in the beginning of the project) can save you from headaches in the future.
This will give you freedom to add custom methods to the User model without having to rely on a OneToOne model or having to implement an Abstract model.
Also, the built-in Django model have some old design decisions (which are kept that way because of backwards compatibility) that are not compatible with many application requirements such as a nullable email field, the email field is not unique, the username field is case sensitive, which means you can have a user with username ana and another one with Ana, first name and last name fields which are not “internationally friendly” (some applications are better off having “full name” and “screen name” for example).

Strategies

How you are going to implement multiple user types depends on the requirements of your application. Below, a few questions you have to ask yourself:
  • How much information specific to each type of user you need to maintain?
  • Can the users have more than one role in the application? E.g. can a User be a Student and Teacher at the same time?
  • How many different types of users the application will need to manage?
A very common case is to have a regular user and an admin user. For that case, you can use the built-in is_staff flag to differentiate normal users from admin users. Actually the built-in User model has two similar fields, is_staff and is_superuser. Those flags are used in the Django Admin app, the is_staff flag designates if the user can log in the Django Admin pages. Now, what this user can or cannot do, is defined by the permissions framework (where you can add specific permissions to a given user, e.g. can create/update users but cannot delete users). The is_superuser flag is an additional flag to assign all permissions without having to add one by one. So here as you can see, the permissions are managed at two different levels.
If you need to maintain extra information related to the users, you have to ask yourself if this particular information is relevant to all users or if it is relevant to only some type of users. For example, a “student number” may only be relevant to Student users. In such cases, you are better off adding a profile model via one-to-one relationship. Now, if the extra info is relevant to all users (e.g., avatar image), the best thing to do is add an extra field directly to the User model.
If the users on your application can assume multiple roles at the same time (e.g. be a Student and Teacher), or your application will have only a few user types, you can control that information in the central User model and create flags like is_student and is_teacher:






class User(AbstractUser):
    is_student = models.BooleanField('student status', default=False)
    is_teacher = models.BooleanField('teacher status', default=False)

This is perhaps the easiest way to handle multiple user types.
Another option is, if the users can assume only one role, you could have a choices field like the example below:






class User(AbstractUser):
  USER_TYPE_CHOICES = (
      (1, 'student'),
      (2, 'teacher'),
      (3, 'secretary'),
      (4, 'supervisor'),
      (5, 'admin'),
  )

  user_type = models.PositiveSmallIntegerField(choices=USER_TYPE_CHOICES)

This way you have a central point to check what is the type of the user. Usually using boolean flags works better!
If your application handle many user types, and users can assume multiple roles, an option is to create an extra table and create a many to many relationship:






class Role(models.Model):
  '''
  The Role entries are managed by the system,
  automatically created via a Django data migration.
  '''
  STUDENT = 1
  TEACHER = 2
  SECRETARY = 3
  SUPERVISOR = 4
  ADMIN = 5
  ROLE_CHOICES = (
      (STUDENT, 'student'),
      (TEACHER, 'teacher'),
      (SECRETARY, 'secretary'),
      (SUPERVISOR, 'supervisor'),
      (ADMIN, 'admin'),
  )

  id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)

  def __str__(self):
      return self.get_id_display()


class User(AbstractUser):
  roles = models.ManyToManyField(Role)

It wouldn’t make much sense to create roles programatically because they are very tied to the business logic. If you need a flexible permission management, it’s a better idea to use Django’s permission framework, where you can create groups and define specific permissions.
In this case I created some constants inside the Role model, so you can define behavior in the application using those constant values like if role == Role.ADMIN:.
It’s worth mentioning that this strategy is not very common. Evaluate first if creating custom permission groups wouldn’t be better, because Django already provides several tolling for this kind of thing.

Picking the Right Strategy

I created the flowchart below to help you decide what’s the right strategy for you. The orange diamonds are decision points; questions that you need to answer so to define the most suitable option. The blue rectangles are actions you will need to perform based on your decisions.

This flowchart is also a nice tool to remind you of what is important to take into account when designing your application.

Authentication and Authorization

Managing multiple user types or roles in a Web application boils down to permission management. Boolean flags like is_student or is_staff usually work as a fixed and pre-defined permission rules, that are incorporated in the source code of your application.
Before we jump into the implementation, it is important to highlight those two concepts:
  • Authentication (Login)
  • Authorization (Permission)
Authentication is the process of verifying if the person is who they claims to be. In practical terms, it’s the process of verifying username and password (login).
Authorization is the process of verifying what this particular person is allowed to do in the application. This can be achieved using view decorators, Django’s built-in permissions framework, or some third-party app likedjango-guardian or django-rules.
Handling authentication with Django is fairly simple, as it’s more standardized. Also, regardless of what the user can do in the application, the process of verifying their credentials should be pretty much the same, right? That’s usually the case in most Django applications. The thing is, most Django authentication forms encompasses aspects related to both authentication and authorization. That being said, authorization always comes after authentication.
Depending on your application needs, just one authentication form and view might be enough for all user types. But in some cases you will still need to implement different forms to authorize certain users to access certain modules or pages of your application.
Now, handling the authorization, i.e., what the users can or cannot do is the main challenge of implementing multiple user types. It’s a little bit more complicated because this can be done at different levels:
  • It can be done at module or app level like Django Admin; only staff members can access the pages under /admin/.
  • Within the same module or app, only a certain type of users can see and interact with some specific pages.
  • Permission management within the same module where some users can only do certain actions (such as create or update objects) where other users can also delete objects.
  • There’s also object-level permissions, where only the user who created it can interact with it. For example on Facebook, only you can edit your own posts (probably some super user / staff member can also interact with it, but from the business logic point of view, posts belongs to the users who posted it).

Comments