Learn to customize the REST API responses per the user roles in Django and Django-rest-framework.

Introduction

In modern web development, it’s common to use REST APIs to exchange data between the front end and back end of a web application. In this article, we’ll learn how to customize REST API responses per user in Django.

For this article, let’s consider an example where we have two user roles in our application- student and teacher We have a REST API endpoint that returns information about courses. We want to return different information for each user type — while the admin user should see all information, the student user should see only the courses they are enrolled in. The teacher user should see the courses they are teaching.

Models

We will start by defining two models, Course and User. The Course model will contain information about the courses available in our system. The User model will extend Django's built-in AbstractUser model and will contain information about the users in our system, including the user's role.

For the sake of simplicity and understanding the underlying logic, we are taking the simplest example.

The Course Model

from django.db import models 
 
class Course(models.Model): 
    name = models.CharField(max_length=255) 
    description = models.TextField() 
    students = models.ManyToManyField(User, related_name='courses') 
    teacher = models.ForeignKey(User, on_delete=models.CASCADE, related_name='teacher_courses') 
    created_at = models.DateTimeField(auto_now_add=True) 
    updated_at = models.DateTimeField(auto_now=True)

In the Course model, we have defined four fields, name, description, created_at, and updated_at. The name field is a character field with a maximum length of 255 characters. The description field is a text field that contains a description of the course. The created_at and updated_at fields are date-time fields automatically set to the current date and time when the course is created or updated.

The User Model

from django.contrib.auth.models import AbstractUser 
 
class User(AbstractUser): 
    ROLE_CHOICES = ( 
        ('student', 'Student'), 
        ('teacher', 'Teacher'), 
    ) 
    role = models.CharField(max_length=10, choices=ROLE_CHOICES)

In the User model we have added a role field to the AbstractUser model. The role field is a character field with a maximum length of 10 characters and a set of choices defined in the ROLE_CHOICES tuple. The available roles are 'student' and 'teacher'.

Serializers

Let’s define a serializer for our `Course` model.

from rest_framework import serializers 
 
class CourseSerializer(serializers.ModelSerializer): 
    class Meta: 
        model = Course 
        fields = '__all__'

ModelViewSet

Django Rest Framework (DRF) provides several generic view sets that make creating REST APIs for your models easier. One of these generic view sets is the ModelViewSet.

A ModelViewSet is a pre-built class that handles all the standard operations for a model, such as a list, create, retrieve, update, and delete. You must inherit from ModelViewSet and provide the required settings, such as the model, serializer, and permission classes.

Here’s an example of a simple ModelViewSet for ourCourse model:

from rest_framework import viewsets 
from rest_framework.permissions import AllowAny 
from .models import Course 
from .serializers import CourseSerializer 
 
class CourseViewSet(viewsets.ModelViewSet): 
    permission_classes = (AllowAny,) 
    queryset = Course.objects.all() 
    serializer_class = CourseSerializer

permission_classes

In the code above, we’ve created a ViewSet called CourseViewSet that uses the AllowAny permission class. This means anyone, including unauthenticated users, can access the API endpoint.

queryset

We’ve also set the queryset serializer_class attributes to Course.objects.all() and CourseSerializer, respectively. The attribute defines the objects the ViewSet will use for the API endpoint.

serializer_class

In contrast, the serializer_class attribute defines the serializer that the ViewSet will use to serialize the objects.

With this simple ViewSet, we can now access the Course Model through a REST API, allowing us to perform all the standard operations, such as list, create, retrieve, update, and delete.

In addition to the ModelViewSet, DRF provides several other generic view sets, such as ReadOnlyModelViewSet and ListAPIView, each of which provides different levels of functionality and customization. You can use these generic view sets as building blocks to create your custom view sets.

Significance of the queryset, serializer_class in the ModelViewSet

The queryset and serializer_class attributes in a ModelViewSet are two of the most significant attributes determine the behavior of the ViewSet.

The queryset attribute defines the set of objects that the ViewSet will use for the API endpoint. The queryset attribute is used by the ViewSet to determine which objects should be used for the list, retrieve, update, and delete operations. You can customize the queryset attribute to return a specific set of objects, such as a filtered list, based on specific conditions.

The serializer_class attribute defines the serializer that the ViewSet will use to serialize the objects. The serializer is responsible for converting the objects into a format that can be transmitted over the network and vice versa. By specifying the serializer_class, you can control the structure of the data that is returned by the API, including which fields are included and how the data is formatted.

By customizing the queryset and serializer_class attributes, you can control the behavior of your ViewSet to meet the specific needs of your application. For example, you might want to return only a specific set of fields for a particular type of user, or you might want to return a different data structure based on the type of request.

In conclusion, the queryset and serializer_class attributes in a ModelViewSet are crucial to the behavior of the ViewSet and play a critical role in determining the structure of the data returned by the API. By customizing these attributes, you can control the behavior of your ViewSet and create an API that meets the specific needs of your application.

The Modified ModelViewset

Finally, we will create a ViewSet to handle the API requests for our Course model. The ViewSet will use the appropriate serializer for each type of user and return the appropriate queryset for each type of user.

Modifying the get_queryset Method:

In the code above, we’ve overridden the get_queryset method to return a different queryset based on the user's role. If the user is an admin, the Course.objects.all() queryset is returned, giving the admin access to all courses. If the user is a student, the Course.objects.filter(students__in=user) queryset is returned, giving the student access only to the courses they are enrolled in. If the user is a teacher, the Course.objects.filter(teacher=user) queryset is returned, giving the teacher access only to the courses they teach.

Here’s how to override the get_queryset method in Django:

from rest_framework import viewsets 
from rest_framework.permissions import IsAuthenticated 
 
class CourseViewSet(viewsets.ModelViewSet): 
    permission_classes = (IsAuthenticated,) 
    def get_queryset(self): 
        user = self.request.user 
        if user.is_superuser: 
            return Course.objects.all() 
        elif user.role == 'student': 
            return Course.objects.filter(students__in=user) 
        elif user.role == 'teacher': 
            return Course.objects.filter(teacher=user)

Modifying the get_serializer_class Method:

The get_serializer_class method is used to determine which serializer class should be used to serialize the queryset. It's a flexible way to change the serialization based on different conditions, such as the request method, user role, or anything else.

Here’s an example of how you could modify the get_serializer_class method in the CourseViewSet to return different serializers for different user roles:

def get_serializer_class(self): 
    user = self.request.user 
    if user.is_superuser: 
        return AdminCourseSerializer 
    elif user.role == 'student': 
        return StudentCourseSerializer 
    elif user.role == 'teacher': 
        return TeacherCourseSerializer

In this example, the method first retrieves the user from the request object using self.request.user. Then, it checks the user's role using the user.is_superuser and user.role attributes. Based on the role, it returns one of the following serializers: AdminCourseSerializer, StudentCourseSerializer, or TeacherCourseSerializer.

New Serializers

from rest_framework import serializers 
 
class AdminCourseSerializer(serializers.ModelSerializer): 
    class Meta: 
        model = Course 
        fields = '__all__' 
 
class StudentCourseSerializer(serializers.ModelSerializer): 
    class Meta: 
        model = Course 
        fields = ('name', 'description') 
 
class TeacherCourseSerializer(serializers.ModelSerializer): 
    students = serializers.StringRelatedField(many=True) 
     
    class Meta: 
        model = Course 
        fields = ('name', 'description', 'students')

Final Viewset

from rest_framework import viewsets 
from rest_framework.permissions import IsAuthenticated 
 
class CourseViewSet(viewsets.ModelViewSet): 
    permission_classes = (IsAuthenticated,) 
 
    def get_serializer_class(self): 
        if self.request.user.role == 'student': 
            return StudentCourseSerializer 
        elif self.request.user.role == 'teacher': 
            return TeacherCourseSerializer 
        return AdminCourseSerializer 
 
    def get_queryset(self): 
        user = self.request.user 
        if user.is_superuser: 
            return Course.objects.all() 
        elif user.role == 'student': 
            return Course.objects.filter(students__in=[user]) 
        elif user.role == 'teacher': 
            return Course.objects.filter(teacher=user)

Conclusion

In conclusion, the Django REST framework provides several powerful tools for customizing an API response. Using the ModelViewSet class, we can easily control the query set used to retrieve the data for the response and the serializer class used to format the response data. With the ability to override the get_queryset and get_serializer_class methods, we can provide different responses for different types of users based on their roles.

Whether you are building a public API for external users or an internal API for your team, using the Django REST framework can significantly simplify the process and allow you to customize the responses to meet your needs quickly.