Managing Multiple User Types With Django And Django Rest Framework

Managing Multiple User Types With Django And Django Rest Framework

ยท

6 min read

When creating a Django program you might want to have different users with varied permissions and features accessible to them. I've seen that this is a typical difficulty that many developers face in the early phases of development, therefore I've decided to offer my expertise on the subject in the most straightforward and accessible manner possible.
Note: This tutorial isn't for absolute beginners, you must have a basic understanding of Python and Django or you will find it difficult to follow along.

There are different ways of managing multiple types of users, one thing that often comes to mind when developers think about this feature is to have different user models to manage different types of users but this isn't what you want to do, you want to use only one Django model to handle authentication regardless of your strategy or business model. In this article we are going to separate users by making use of flags.

Let's take into consideration the default Django model, you are already managing two types of users which are the normal users and the superuser. These two types of users are not managed by two separate models instead they manage these users with flags. For a clearer picture consider the is_staff flag and the is_superuser flag, the is_staff flag grants a user permissions to login the admin panel and the is_superuser flag grants a user all permissions so they can create and delete any database data that is registered to the admin panel.

Now you should have a clearer understanding of how we can manage multiple user types in one user model using flags. We are now going to create a custom user model to implement this feature and the respective view where users can sign up to be an admin or a normal user.
To follow this tutorial you should have a virtual environment running, must have Django and Django Rest Framework installed with the commands pip install django and pip install djangorestframework, started a new project with django-admin startproject projectname and also created an app inside of your project.

Step 1: Create the user model

In your user's app models.py file, we inherit from the AbstractBaseUser and the BaseUserManager. Basically, they are classes we could use to extend the generic Django user model and create a custom user model.

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin


class Account(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(verbose_name="email", max_length=60, unique=True)
    name = models.CharField(max_length=30)
    date_joined = models.DateTimeField(verbose_name='date_joined', auto_now_add=True)
    last_login = models.DateTimeField(verbose_name='last_login', auto_now=True)
    is_admin = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']
    objects = MyAccountManager()

    def __str__(self):
        return self.email

In the code snippet above you can see that by default the is_admin, is_staff and is_superuser fields are set to false, this implies that by default all registered users should be normal users. To create admin users or a superuser we must then apply the use of flags to separate these users when created, this would be done in your user model manager class which inherits from the BaseUserManager class.

Step 2: Create the User model manager

class MyAccountManager(BaseUserManager):
    def create_user(self, email, name, password=None):
        if not email:
            return ValueError("Users must have an email address")
        if not name:
            return ValueError("Users must have a name")

        user = self.model(
            email=self.normalize_email(email).lower(),
            name=name,
            password = password,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_admin(self, email, name, password=None):
        user = self.create_user(
            email=self.normalize_email(email),
            password = password,
            name = name,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

    def create_superuser(self, email, name, password=None):
        user = self.create_user(
            email=self.normalize_email(email),
            password = password,
            name = name,
        )
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user

In the above code snippet, the def create_admin function will be executed only when the is_admin flag is set to the boolean value True and then an admin user would be created. The class MyAccountManager could be named anything really but I think it makes more sense since the name of our user model is Account. Now you can create multiple admin users and a superuser.

Step 3: Setting the model you just created

In the settings.py file of our project's base directory set AUTH_USER_MODEL = 'users.Account', here users is the name of the user app and Account is the model we just created. We are overriding the default user model Django provides and making reference to the one we just built.

Step 4: We migrate

we go to our terminal and the location of the current working file, activating the required virtual environment and run python manage.py makemigrations and python manage.py migrate. The python manage.py makemigrations prepares the created model to be converted to sql format and the python manage.py migrate commit those changes.

Step 5: Creating the user registration APIViews

Here we are going to create the APIViews which we would use to register users. In our case we want to register either a normal user or an admin user, by now it should all be coming together, we want the boolean value True to be passed when creating admin users and False when creating normal users. Let us see how we can accomplish this In our users app views.py file using the Django rest framework.

from django.contrib.auth import get_user_model
User = get_user_model()
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status


class RegisterView(APIView):
    permission_classes = (AllowAny, )

    def post(self, request):
        try:
            data = request.data

            name = data['name']
            email = data['email']
            email = email.lower()
            password = data['password']
            re_password = data['re_password']
            is_admin = data['is_admin']

            if is_admin == 'True':
                is_admin = True
            else:
                is_admin = False

            if password == re_password:
                if len(password) >= 8:
                    if not User.objects.filter(email=email).exists():
                        if not is_realtor:
                            User.objects.create_user(name = name, email = email, password = password)
                            return Response(
                                {'success': 'User created Sucessfully.'},
                                status=status.HTTP_201_CREATED
                            )
                        else:
                            User.objects.create_admin(name = name, email = email, password = password)
                            return Response(
                                {'success': 'Admin user created sucessfully.'},
                                status=status.HTTP_201_CREATED
                            )
                    else:
                        return Response(
                {'error': 'User with this email already exists.'},
                status = status.HTTP_400_BAD_REQUEST
                )

                else:
                    return Response(
                {'error': 'Password must be at least 8 characters.'},
                status = status.HTTP_400_BAD_REQUEST
                )
            else:
                return Response(
                {'error': 'Passwords do not match.'},
                status = status.HTTP_400_BAD_REQUEST
            )
        except:
            return Response(
                {'error': 'Something went wrong while registering.'},
                status = status.HTTP_500_INTERNAL_SERVER_ERROR
            )
  • Imports

    Above we are using the get_user_model function to get our user model, we then set it to the variable User and execute it. We import APIView to make this function an api view, AllowAny to give permissions to any user to access the registration endpoint , Response and status to return a response and the status respectively.

  • Body

    First we retrieve the request data and store it in the value data data=request.data, data will be a dictionary and the values of data will be strings. We can then retrieve the string values of data respectively. If the string value of the flag is_admin passed is "True" then we set is_admin to the boolean value True and vice versa. Just with one flag we can now separate normal users from admin users.

Step 6: We create our url endpoints

from django.urls import path
from . views import RegisterView

urlpatterns = [
    path('register', RegisterView.as_view(), name='api-register'),
]

Finally, we can now use this url endpoint to make requests to either create a normal user or an admin user. We can also set different roles for the different users and permissions to enforce the roles.

Conclusion:

You should never user more than one user model to handle authentication regardless of your strategy or business model. The use of flags which was covered in this tutorial is an effective way to separate user types although there are other ways this can be accomplished.

Thank you for reading and I hope you find this article useful and informative. ๐Ÿ˜Š