Django Rest Framework Tutorial – A beginners guide

Django rest framework (DRF) is a powerful and perfectly maintained framework for Django to implement Web APIs. It provides a wide range of built-in classes and utility functions to quickly set up a whole project with Rest APIs. Even having many built-in classes to make life easier for developers, it also provides a deep level of flexibility with which one can change its default behavior according to his context. One of the amazing features it has is the Django rest framework authentication. It provides multiple methods for authentication from Basic to Session-based and from Token-based to remote user authentication. Not only this, there are many third-party packages that provide different types of authentication integrated with DRF.

DRF uses serialization for ORM and non-ORM queries. It also has built-in Viewsets that we can use to quickly implement a basic or even complex CRUD API. This blog will serve as a basic Django rest framework tutorial for beginners who want to get started with one of the best backend development frameworks out there.

Let’s Discuss what is Django First:

Django is a high-level Python web framework that enables developers to quickly build powerful web applications. It follows the Model-View-Controller (MVC) architectural pattern and emphasizes the principle of “Don’t Repeat Yourself” (DRY) to ensure efficient and maintainable code. With its built-in libraries and support for third-party packages, Django provides a robust and flexible platform for web development. Whether you’re building a small personal project or a large-scale enterprise application, Django can help you streamline the development process and create high-quality web experiences.

What is Rest API

A REST API, also known as a Representational State Transfer API, is a way for different software systems to communicate with each other via the internet. In simpler terms, it acts as a bridge between two applications, allowing them to send requests and receive responses.

One of the key benefits of a REST API is its simplicity and standardization. It follows a set of principles and constraints that make it easy to use and scale, while still allowing for flexibility and customization. This makes it a popular choice in web development for creating web services and other networked applications.

Overall, a REST API is a powerful tool that enables seamless integration between different software systems. It simplifies the process of accessing and manipulating resources on the web, making it easier to build complex applications and services that meet a wide range of needs. Whether you’re a developer or an end user, understanding the basics of REST APIs can help you navigate the world of web-based applications and services with greater ease and confidence.

Why Django Rest Framework:

Are you considering building a web API for your next project? Look no further than Django REST framework! Here are three reasons why it’s the go-to toolkit for developers:

  1. Streamlined Development: With Django REST framework, you can build a robust and scalable API in no time. Its user-friendly interface and built-in authentication and validation features make development a breeze, so you can focus on delivering your app’s core functionality.
  2. Customizability: Django REST framework is highly customizable, allowing you to tailor its features to your specific needs. It supports a range of data formats, such as JSON and XML, and has powerful tools for handling complex data structures.
  3. Strong Community Support: With a vibrant community of developers, you’ll never be alone in your journey with Django REST framework. From extensive documentation to third-party packages, the community provides all the resources you need to get started and stay up-to-date.

Setting up Django REST framework

Installation

This blog for Django rest framework assumes that you have the basic knowledge of Django and Django class-based views. First, we will install the Django rest framework using Python’s package installer (pip).

 pip install djangorestframework

After installation, add rest_framework in your installed_apps in settings.py file.

INSTALLED_APPS = [
 ...
 'rest_framework'
      ]

Voilah! You will have the Django rest framework installed in its full glory.

Serializer

DRF uses serializers to convert Django’s ORM querysets or objects to JSON format and vice-versa. Serializers act as backbone of your application. Just like Django Forms, serializers have fields and functions to perform operations on respective models. Serializers have their own fields just like Django Forms for proper validation and data manipulation. DRF serializers are the most powerful module of the Django rest framework. In order to write serializers, the convention is that you should first make a Python file in your app named serializers.py same as models.py and on the same directory hierarchy as models.py. It’s best to have a separate serializers.py file for each app.

Fields

Serializer fields help in converting the model’s data to Python’s internal data types. They also provide methods for input validations, getting and setting values to their respective model fields. Django rest framework provides a wide variety of fields that we can use like CharField, EmailField, etc. And if there is some complex functionality or some conditions involved to get a specific field’s value from a model instance then DRF provides SerializerMethodField that you can use to call a function that returns a value. By default, you just have to create a function in the serializer class with the same name as the field’s name but prefix it with get_.


from rest_framework import serializers


class TestSerializer(serializers.Serializer):
	test = serializers.SerializerMethodField()
	...
	
	def get_test(self, instance):
		if instance.x:
			return 'x'
		return 'y'

Notice that get_test accepts an argument named instance. This argument contains the current instance of the model. But you can also change the function’s name by passing the method_name argument in the SerializerMethodField constructor.

Validation in Fields

For basic validation, DRF serializer fields accept the following arguments to validate the input data.

  1. read_only: On setting it to True, the field will only be readable and you can’t change its data
  2. write_only: Upon setting this to True, the field won’t return anything. It will only be writable.
  3. required: If set to True, the field will be a required field and its value should always be set.
  4. default: It contains the value that DRF uses if no value is given in the data

 

You can add custom validations to your field just by writing a method in the serializer class that has the prefix validate_ followed by the field name as the method’s name. Just like we do in the case of SerializerMethodField. You can get the value that is to be validated as an argument to the function. A validator should raise serializers.ValidationError when the value is not validated.

from rest_framework import serializers


class TestSerializer(serializers.Serializer):
	test = serializers.CharField(required=True)
	...
	
	def validate_test(self, value):
		if value is None:
			raise serializers.ValidationError('Value invalid')
		return value

Validation

Just like field validations, serializers also provide a way to perform general validation on data. To perform validation on data you can override the validate method of the serializer class. You will get the input data as an argument to this function.

from rest_framework import serializers


class TestSerializer(serializers.Serializer):
	test = serializers.CharField(required=True)
	...
	
	def validate(self, data):
		if len(data) > 5:
			raise serializers.ValidationError(Data invalid')
		return value

Serializing objects

In order to serialize a model object or a queryset, you just simply have to pass the object or queryset to the serializer class’s constructor while creating the serializer object. 

from django.db import models
from rest_framework import serializers

class Test(models.Model):
	test = models.CharField(null=False, blank=False)

...

class TestSerializer(serializers.Serializer):
	test = serializers.CharField(required=True)

...

obj = Test.objects.create(test='xyz')
ser = TestSerializer(obj)
print(ser.data)

# {"test": "xyz"}

 

Similarly if you want to pass a queryset to the serializer you can do so by the above method. But remember queryset is a type of list of objects so you have to pass many=True with the queryset.

 
qs = Test.objects.all()

ser = TestSerializer(qs, many=True)

print(ser.data)

# [{"test": "abc"}, {"test": "xyz"}, ...]

 

Deserializing objects

Just like serializing objects, you can pass the incoming input data to the serializer, and then the serializer will do the work for you.

 
data = ‘{"test": "abc"}’

ser = TestSerializer(data=data)

ser.is_valid()

print(ser.validated_data)

# {"test": "abc"}

 

Remember, while passing JSON data, you have to pass it to the data argument in the serializer like data=input_data. You have noticed the is_valid function that is called after making the serializer object. The purpose of this function is to validate the data. When this function is called, it performs a validation check on fields and non-fields value. This function returns boolean based on if the data was valid or not. It accepts an argument raise_exception if set to True, the function will raise serializers.ValidationError upon invalid data.

After the validations are successful, you will get all your validated data in the validated_data variable of the class. Remember that on creating or updating the model objects, serializers get data from validated_data.

 

Saving objects

For saving model objects in the database, serializers provide built-in create and update methods. You can override these methods to perform necessary operations. Both methods get validated_data as an argument to the functions. update function will get an instance as first argument to the function. Instance contains the instance of the model that needs to be updated.

from rest_framework import serializers

class TestSerializer(serializers.Serializer):
	test = serializers.CharField(required=True)
	...
	
	def create(self, validated_data):
		return Test.objects.create(**validated_data)

	def update(self, instance, validated_data):
		instance.test = validated_data.get(test)
		instance.save()
		return instance

 

After overriding these functions, you can call serializer.save() to perform creation or updation.

The serializer knows it’s a creation request if only the data is passed on to the save method, otherwise if you pass a model instance to the serializer, then the update method is called for that instance.

 
ser = TestSerializer(data=data)
ser.is_valid(raise_exception=True# if data is invalid, ValidationError will be raised
new_obj = ser.save()  # create will be called

# new_obj will have Test object

ser = TestSerializer(new_obj, data=input_data)
if ser.is_valid():
 updated_object = ser.save()  # update will be called now

 

Model serializers

Just like ModelForms, DRF’s model serializers have many built-in functionalities that you can use to quickly set your application up and running. ModelSerializers by default have updated and create functions written to create and update model objects. ModelSerializers also get validations from model fields. For example, if a model field is set to be null=False and blank=False, ModelSerializers will automatically make that field required. To create a model serializer for your model, you need to do this:

from rest_framework import serializers

class TestSerializer(serializers.ModelSerializer):
	class Meta:
		model = Test
		fields = '__all__'

 

Notice that the model class has been passed in the Meta class of the serializer just like Django ModelForms. In the fields attribute, you can pass a tuple of strings containing the model’s field names that you want to add to the serializer. On the contrary to that, you can pass ‘__all__’ To have all fields available to the serializer. You can change the existing model’s field definition in the serializer. You just have to create a new field in the serializer with the same name.

You can also add some extra fields in the serializer. You just have to define the field in the serializer class just like you do in the normal serializer. If you are not using ‘__all__’ in the fields attribute, then you have to give that field’s name in the fields tuple.

 

Relationships in The Serializers

If you are using foreign keys in the models and you have created a model serializer of the model, then you will get the id of the foreign referenced model instance in the serializer response. If getting the id is your requirement, then this is good for you but if you want some columns from that model too then you have to create that referenced model’s serializer and pass the serializer into the parent serializer as a field.

Consider this structure:

class Foo(models.Model):
	a = models.IntegerField(default=0)

class Bar(models.Model):
	foo = models.ForeignKey(Foo, ...)

 

If you create a model serializer for Bar then you will get Foo object’s id in response to Bar’s serializer.

class FooSerializer(serializers.ModelSerializer):
	class Meta:
		model = Foo
		fields = '__all__'

class BarSerializer(serializers.ModelSerializer):
	class Meta:
		model = Bar
		fields = '__all__'

foo = Foo.objects.create(a = 'test')
bar = Bar.objects.create(foo=foo)

ser = BarSerializer(bar)
print(ser.data)

# {"foo": 1}

 

Notice the response of BarSerializer, it is returning Foo’s id. But if you do something like this:

class BarSerializer(serializers.ModelSerializer):
     Foo = FooSerializer()
	class Meta:
		model = Bar
		fields = '__all__'

ser = BarSerializer(bar)
print(ser.data)

# {"foo": {"a": "test"}}

 

Now you can get the Foo object’s data. Similarly, if you are dealing with many-to-many relationships, you just have to pass many=True while creating the serializer’s field. Remember if you are creating a Bar instance through BarSerializer, the serializer will expect all the fields of FooSerializer in the input data.

data = "{"foo": 1}"

ser = BarSerializer(data=data)
ser.is_valid(raise_exception=True)
ser.save()

# this will raise ValidationError
# but if you pass it like this "{"foo": {"a": "abc"}}", this will be the valid data. Now the serializer will create both Foo and Bar instances.

 

Viewsets

DRF Viewsets are the same as Django class-based views. The only difference is that viewsets don’t provide methods like get or post. Instead, they provide actions like create, update, retrieve and list. In DRF, serializers and viewsets go hand-in-hand. For example, create action will pass all the data to a serializer, and the serializer will create the object, return it in JSON format and the viewset will return it in HTTP response. Similarly in a retrieve action, the viewset will get the pk of the instance, get an instance from that pk and pass it to the serializer. Serializer will then serialize that object in JSON format and viewset will return in HTTP response.

from rest_framework import viewsets
from rest_framework.responses import Response

class FooViewset(viewsets.Viewset):
	def list(self, request, *args, **kwargs):
		queryset = Foo.objects.all()
		serializer = FooSerializer(queryset, many=True)
		return Response(serializer.data)

	def create(self, request, *args, **kwargs):
		Serializer = FooSerializer(data=request.data)
		serializer.is_valid(raise_exception=True)
		serializer.save()
		Return Response(serializer)

 

This viewset will performs  list and create actions. In order to register viewsets in urls, you have to use DRF Routers. In your application’s urls.py you have to create a router:

 
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'foo', FooViewset, basename='foo')
urlpatterns = router.urls

 

Now the router will forward the requests of /foo/ to appropriate actions in the FooViewset.

In DRF we don’t typically create viewsets like above. ModelViewset provides the CRUD operations for a model in your application.

 

Model viewset

Model viewset are the same like normal viewsets but have all the actions implemented. You just have to create a ModelSerializer for a Model and then pass that serializer to a ModelViewset and you will have a CRUD setup for the model with all the validations and paginations etc.

from rest_framework import viewsets

class FooViewset(viewsets.ModelViewSet):
	queryset = Foo.objects.all()
	serializer_class = FooSerializer

 

Register this viewset in the router as explained above. Just like this, you will have a fully functional CRUD implemented for the model. That’s the beauty and power of the Django Rest Framework!

There are certain limitations of HTTP methods in the viewsets. Based on http methods, the viewset actions are executed. For example if we request the url with POST method, then the request will land on create action. If we request with GET, the list action will be called. All other actions like retrieve, update, partial_update and delete required the model instance’s pk to be passed. For example if you want to update the instance with id 1, then you have to make a PUT request to the url /foo/1/ with the data to be updated. If you want to delete the instance, then you have to make a DELETE request to /foo/1. Now if you know how Django’s url configuration works, you can accept parameters in the url and those parameters will be available to you in the view’s methods. DRF routers work the same. They will generate urls based on the same configuration as Django requires. 

ModelViewSets are not just limited to basic CRUD; you can make your own custom actions to perform certain operations. For example

from rest_framework import viewsets
from rest_framework.decorators import action


class FooViewset(viewsets.ModelViewSet):
	queryset = Foo.objects.all()
	serializer_class = FooSerializer

	@action(detail=False, methods=['get'])
	def get_bars(self, request, *args, **kwargs):	
		# do anything
		# but it should return an HTTP response

 

Notice that I have created a method get_bars with an action decorator. This decorator is used to create a custom action. Let’s discuss some of the arguments that this decorator expects:

  1. detail: It’s a boolean type argument that tells the router to accept a pk in the URL or not. If set to True, the router will create its URL with pk required.
  2. methods: it is a list type argument that accepts a list of HTTP methods that are valid for this action. If the method is only set to [‘get’] then all the HTTP requests other than GET will get a 405 (Method not allowed) response.

If you have registered the viewset with the router using ‘foo’ expression, then the URL of the custom action will be /foo/get_bars.

 

Look to Hire Django Development Team?

Share the details of your request and we will provide you with a full-cycle team under one roof.

Get an Estimate

 

Django Rest Framework Authentication

Authentication is the process of identifying credentials of an incoming request such as a session key or a token to the user in the database. If you know Django’s authentication system, you will know that after successful authentication, you will get the authenticated user in request.user. The process is the same in DRF’s authentication. DRF provides several ways of authenticating a user such as session-based, token-based or basic authentication. 

For the authentication system that you want to use in the application, you have to define a list of authentication classes in your settings.py. DRF will call the authenticate function of each of the classes and return the user as request.user when any of the classes authenticates the user. 

DRF will not process authentication further when a class successfully authenticates the user. If neither of the classes authenticates the user then request.user will have an instance of django.contrib.auth.models.AnonymousUser. This value can be changed by setting UNAUTHENTICATE_USER.

This is how you will be defining the authentication class in your settings.py:

 
REST_FRAMEWORK = {
 'DEFAULT_AUTHENTICATION_CLASSES' = [
     'rest_framework.authentication.BasicAuthentication',
     'rest_framework.authentication.SessionAuthentication'
      ]
}

 

If in the above defined classes, a request is authenticated by the BasicAuthentication class, DRF will not pass the request to the SessionAuthentication. You can also change the authentication on a per view basis by providing authentication_classes.

from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication, BasicAuthentication


class FooViewSet(viewsets.ModelViewSet):
	...
	authentication_classes = [SessionAuthentication, BasicAuthentication]

 

The process of authentication by defining a class like this is the same as setting classes in settings.py file. For example in the above example, the SessionAuthentication will first authenticate the request, if the authentication is successful, BasicAuthentication will not authenticate the request.

 

Let’s discuss some of the methods of authentication is DRF:

 

Basic Authentication

This authentication system uses HTTP Basic Authentication signed by the user’s username and password. If successfully authenticated, the BasicAuthentication class will provide a user instance in request.user. On unsuccessful authentication, it will raise a 401 (Unauthorized) error. This method should only be used for testing purposes. You can use it as a general authentication class for all requests or set it on a per-view basis as explained above.

 

Token Authentication

Django rest framework’s Token-based authentication uses a simple token-based authentication mechanism. Token-based authentication is appropriate for the client-server architecture. So the basic process is that the client requests the server for a token to authenticate requests. The server generates the token and sends it in an HTTP response. Now on each request, the client will always send this token in the authorization header of the request

 
Authorization: Token <http_token_obtained_from_server>

 

Remember that the Token written in the above example is a keyword and it will stay as it is. So if your token is lak3i273hrhi2723yif7hu2i37f23 then your authorization header will be something like this:

 
Authorization: Token lak3i273hrhi2723yif7hu2i37f23

 

You can change this keyword by creating your own class inherited from the TokenAuthentication class and changing the keyword class variable to your desired keyword. 

 
from rest_framework.authentication import TokenAuthentication


class CustomTokenAuth(TokenAuthentication):
 keyword = 'Bearer'

 

Now you will use the CustomTokenAuth class instead of the TokenAuthentication class in your application. Now your request header will be:

Authorization: Bearer lak3i273hrhi2723yif7hu2i37f23

In order to use token-based authentication, you have to either add the TokenAuthentication class in the rest framework’s DEFAULT_AUTHENTICATION_CLASSES list or you can do it on a per-view basis as explained above. But you have to register rest_framework.authtoken in your installed_apps list.

installed_apps = [
	...
	'rest_framework.authtoken'
]
...
REST_FRAMEWORK = {
	'DEFAULT_AUTHENTICATION_CLASSES' = [
		...
		'rest_framework.authentication.TokenAuthentication'
      ]
}

For creating a token for a user, you have to do something like this:

from rest_framework.authtoken.models import Token

def generate_token(user):
	token = Token.objects.create(user=...)
	Return token.key

 

Now you can call generate_token with a user instance in your views and pass the token returned by the function in your response. On successful authentication, this class will also provide a user instance in request.user and raise a 401 (Unauthorized) on authentication failure.

 

Session Authentication

This authentication mechanism uses Django’s default authentication backend. This is suitable for those clients that are using AJAX calls and are running in the same context as your API server. On authentication failure, this mechanism returns a 403 (Forbidden) response and on successful authentication, populates the request.user property. 

If you are using AJAX calls to the API, you must pass a CSRF token in non-safe HTTP calls like post, put or delete, etc. 

For security purposes, you must use Django’s standard login views while using SessionAuthentication.

 

Custom Authentication

You can write your own authentication class by subclassing it with rest_framework.authentication.BaseAuthentication. You should override the authenticate method and write your authentication logic in it. On successful authentication, your method should return a tuple of a user object and None. If authentication is unsuccessful, the method should return None. You can also raise rest_framework.exceptions.AuthenticationFailed exception on failed authentication.

Conventionally, if you are unable to authenticate the user, then you should return None from the method for request to be passed to the next defined class for authentication. If you are able to authenticate but authentication has failed, you should raise the  AuthenticationFailed exception. In that case, the request will not be passed to next classes instead a 401 (Unauthorized) response is returned. On successful authentication, you should return a tuple of a user object and None.

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authtoken.models import Token


class CustomAuthentication(BaseAuthentication):
	def authenticate(self, request):
		token = request.META.get('Authorization')
		if not token:
			return None  # request will be passed to the next class
		token = token.split(' ')[-1]
		try:
			token = Token.objects.get(key=token)
		except Token.DoesNotExist:
			raise AuthenticationFailed('invalid token') #  401 will be returned

		return token.user, None

 

Now you can use the CustomAuthentication class in your settings or per-view basis.

 

Django Rest Framework permissions

In the Django rest framework, authentication and permissions go hand-in-hand. You can check if the user has specific permissions to perform certain actions in a view or on a model after the authentication. Permissions are the second most top thing that is checked before a request is entered in the view. First thing is authentication. The permissions are used to grant access to any api or any model to a user. The most basic permission that you can place on a view is whether the user is authenticated or not. That is rest_framework.permissions.IsAuthenticated.

Or you can allow access to a view to any user by giving it rest_framework.permissions.AllowAny.

In DRF, you can set permission classes just as you set authentication classes in your settings file or you can set-up permissions on a per-view basis.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

# or you can do this

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class FooView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)

If a user does not have permission for a view the 403 (Forbidden) response is returned. When DRF checks for the permissions, first authentication on the user is performed. If authentication fails then 401 (Unauthorized) is returned. Else the permissions are checked on request.user.

 

Object-level Permissions

DRF also provides object-level permissions on an object. This is used to restrict access of a model’s object to a user. Object-level permissions are checked in the get_object function of generic APIView after the object is retrieved. If you are overriding get_object then if you want to check for permissions, you should call self.check_object_permissions(request, obj) after you have retrieved the object. This function will return PermissionDenied or NotAuthenticated exceptions based on the failures.

 
def get_object(self):
    obj = get_object_or_404(self.get_queryset(),pk=self.kwargs["pk"])
    self.check_object_permissions(self.request, obj)
    return obj

 

Custom Permissions

To implement custom permissions, you should create a subclass of rest_framework.permissions.BasePermission. You should override has_permission(self, request, view) or has_object_permission(self, request, view, obj) based on the context. If you want to check for general permissions or permissions on a view, you should do it in has_permissions and if you want to check object-level permissions, you will do it in has_object_permission method. These methods will return True if permissions are passed and False if permissions are failed.

from rest_framework.permissions import BasePermission

class AuthenticationPermission(BasePermission):
	def has_permissions(self, request, view):
	    return request.user.is_authenticated()

	def has_object_permission(self, request, view, obj):
	    return request.user.is_authenticated()

 

Conclusion

Summing up this blog, DRF is Django’s one of the most powerful tools to implement web APIs. It comes with many built-in functionalities out of the box which greatly enhances the web development experience for beginners as well as experienced developers. 

DRF makes serialization and deserialization a piece of cake. The main motivation for using Django Rest Framework is to allow developers to build the business logic of their app without worrying about implementing the low-level functionalities and let’s not forget that it’s one of the most secure frameworks out there! For someone who is new to web development, Django Rest Framework is a fun and great way, to begin with.

Share this article