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 theModelViewSet
, DRF provides several other generic view sets, such asReadOnlyModelViewSet
andListAPIView
, 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.
Comments