Django REST Framework Basics

Last updated April 14th, 2023

This article serves as an introduction to Django REST Framework, focusing specifically on the following core concepts:

  1. Serializers
  2. Views and ViewSets
  3. Routers
  4. Authentication and Authorization

It also provides relevant links to help you advance and deepen your knowledge of Django REST Framework.

Contents

Django REST Framework

Django REST Framework (DRF) is a widely-used, full-featured API framework designed for building RESTful APIs with Django. At its core, DRF integrates with Django's core features -- models, views, and URLs -- making it simple and seamless to create a RESTful API.

Want to learn more about RESTful APIs? Check out What is a RESTful API?.

DRF is composed of the following components:

  1. Serializers are used to convert Django QuerySets and model instances to (serialization) and from (deserialization) JSON (and a number of other data rendering formats like XML and YAML).
  2. Views (along with ViewSets), which are similar to traditional Django views, handle RESTful HTTP requests and responses. The view itself uses serializers to validate incoming payloads and contains the necessary logic to return the response. Viewsets are coupled with routers, which map the views back to the exposed URLs.

Serializers

What is a Serializer?

What is a serializer in Django REST Framework?

Again, serializers are used to convert Django QuerySets and model instances to and from JSON. Also, before deserializing the data, for incoming payloads, serializers validate the shape of the data.

Why does the data need to be (de)serialized?

Django QuerySets and model instances are Django-specific and, as such, not universal. In other words, the data structure needs to be converted into a simplified structure before it can be communicated over a RESTful API.

Remember: RESTful APIs are meant to be consumed by other computers, so the communication needs to be uniformed and universal. What is a RESTful API? goes over all this and more. Be sure to check it out!

Quick Examples

To specify how incoming and outgoing data gets serialized and deserialized, you create a [SomeResource]Serializer class. So, if you have a Task model, you'd create a TaskSerializer class.

For example:

# model
class Task(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    completed = models.BooleanField(default=False)


# basic serializer
class TaskSerializer(serializers.Serializer):
    title = serializers.CharField()
    description = serializers.CharField()
    completed = serializers.BooleanField()

Similarly to how Django forms are created, when the serialization is closely coupled to the model, you can extend from ModelSerializer:

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = "__all__"

You can easily adapt a ModelSerializer to your needs:

class TaskSerializer(serializers.ModelSerializer):
    short_description = serializers.SerializerMethodField()

    class Meta:
        model = Task
        fields = ["title", "description", "completed", "short_description"]
        read_only_fields = ['completed']

    def get_short_description(self, obj):
        return obj.description[:50]

Here, we-

  1. Explicitly defined the fields the serializer has access to via the fields attribute
  2. Set the completed field to read-only
  3. Added additional data -- short_description

DRF also allows you to create a hypertext-driven API:

class TaskSerializer(serializers.HyperlinkedModelSerializer):
    subtasks = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='subtask-detail'
    )

    class Meta:
        model = Task
        fields = ['name', 'subtasks']

While these are just basic examples, they should give you a good idea of how serializers work and why they're necessary.

Resources

You can find more about serializers from the official docs:

  1. Documentation on Serializers, Serializer fields, and Serializer relations.
  2. Official tutorial on Serialization and on Hyperlinked APIs.

If you're curious in general about the structure of the DRF documentation, refer to DRF Documentation.

For more advanced material, check out the following resources:

  1. Effectively Using Django REST Framework Serializers
  2. Why do I need a serializer?
  3. Improve Serialization Performance in Django Rest Framework
  4. Custom validations for serializer fields Django Rest Framework
  5. How to Save Extra Data to a Django REST Framework Serializer
  6. Specifying different serializers for input and output discussion

Views and ViewSets

What are Views and ViewSets?

What are Django REST Framework views and how are they different from Django views?

Django REST Framework views:

  1. Change incoming requests into Request instances
  2. Handle authentication and authorization
  3. Perform some sort of action (create, read, update, delete)
  4. Return a Response object

While Django views typically serve up HTML templates, DRF views return a JSON response.

DRF has three different types of views:

  1. APIView
  2. Generic View
  3. ViewSet

All DRF views extend from the basic APIView class (which extends from Django's View class). Although DRF supports function-based views, they are typically class-based (CBV).

What's the difference between DRF views and ViewSets?

ViewSets allow you to combine related views into a single class.

Instead of method handlers, like .get() and .post(), ViewSets provide actions, like .list() and .create().

The advantage of using a ViewSet over a view is that they minimize the amount of code you need to write and keep your URLs consistent. Keep in mind that ViewSets can make your code less readable since there's quite a lot happening under the hood.

Quick Examples

APIView

We'll use the same use case to best represent different types of views -- an endpoint for listing all tasks and creating a new one.

Simple class-based view:

class ListTasks(APIView):
    def get(self, request):
        tasks = Task.objects.all()
        serializer = TaskSerializer(tasks, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = TaskSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The same functionality can be achieved with a function-based view like so:

@api_view(['GET', 'POST'])
def list_tasks(request):
    if request.method == 'GET':
        tasks = Task.objects.all()
        serializer = TaskSerializer(tasks, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = TaskSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Generic View

Generic views are meant to handle common use cases without much hassle. For example, to handle the above use case, ListCreateAPIView should be used:

class RetrieveUpdateDeleteTask(ListCreateAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

ViewSet

You can combine related views in a single class -- a ViewSet. For example, the following ViewSet combines listing all tasks and retrieving a single task:

class TaskViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = Task.objects.all()
        serializer = TaskSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = Task.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = TaskSerializer(user)
        return Response(serializer.data)

Note that one URL (not including pk) will be needed for a list action and another (including pk) for a retrieve action.

We can achieve the same result, using the ReadOnlyModelViewSet:

class TaskModelViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = TaskSerializer
    queryset = Task.objects.all()

To cover all possible actions (create, read, update, delete), you can use ModelViewSet instead of ReadOnlyModelViewSet:

class TaskModelViewSet(viewsets.ModelViewSet):
    serializer_class = TaskSerializer
    queryset = Task.objects.all()

It's also worth mentioning that you can create custom actions in a ViewSet.

Resources

You can find information on views in both the API guide and the official DRF tutorial:

  1. Documentation and Tutorial for function based views.
  2. Documentation and Tutorial for class-based views. Generic CBVs have a separate page.
  3. Documentation and Tutorial for ViewSets.

For more advanced material, check out the following resources:

  1. DRF Views series
  2. Manual testing ModelViewSet with Postman
  3. ModelViewSet attributes and methods
  4. How to Disable a method in a ViewSet discussion
  5. How to use different serializers in the same ModelViewSet discussion

Routers

What is a router in Django REST Framework?

Unlike DRF views, that need similar URLconf as you're used to from Django views, DRF ViewSets require multiple URLs. While it is technically possible to achieve this with regular urlpatterns, it's best to use routers. Routers automatically generate standardized URL patterns and associate them to a given ViewSet.

DRF comes with two routers out of the box: SimpleRouter and DefaultRouter.

Quick examples

A router needs to be defined and included in the urlpatterns. You can register multiple ViewSets to a single router instance:

router = routers.DefaultRouter()
router.register(r'tasks', TaskViewSet)
router.register(r'work-hours', WorkHoursViewSet)


urlpatterns = [
    path('', include(router.urls)),
]

Resources

  1. Documentation
  2. Official tutorial
  3. Handling URLs

Authentication and Authorization

Authentication vs Authorization?

What are authentication and authorization?

Authentication and authorization together determine if a request should be granted access to an endpoint:

  1. First, the user that made the request is identified (authentication).
  2. After that, you check if the request user has the necessary permissions for executing the request (authorization).

What is authentication?

Authentication is a process of verifying the identity of a user executing a request, and it doesn't in any way limit access to the API. Authentication can be performed with either username and password, tokens, or sessions. DRF also supports remote user authentication.

What is authorization?

In DRF, authorization has two parts, throttling and permissions:

  1. Throttling controls the rate of the requests that clients can make to an API. After the rate per user is exceeded, the request will be (temporarily) denied.
  2. Unlike throttling, which indicates a temporary state, permissions indicate a permanent one. Permissions can control global access (e.g., only authenticated users), access to an endpoint (e.g., only administrators), or even access per object (e.g., only the creator).

Quick Examples

Authentication, permissions, and throttling can be set either globally or per view.

For example, you can set all three globally like so in your Django settings file:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'shopping_list.api.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '10/hour',
        'user': '1000/hour',
    },
}

In this example, the user is authenticated with a token, and only authenticated users get access to the API. Anonymous requests are throttled after the 10th request in an hour, while authenticated users are permitted 1000 requests per hour.

Setting authentication, permissions, and throttling per view looks like this:

class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

    authentication_classes = [TokenAuthentication]
    throttle_classes = [UserRateThrottle, AnonRateThrottle]
    permission_classes = [IsAuthenticated]

This example has the same authentication and authorization requirements as the previous one, except now they are enforced only for that specific view. Notice that you can only set throttle_classes, not throttle_rates. The rates will be taken from the settings.

Resources

You can find more about authentication and authorization from the official docs:

  1. Documentation for Authentication, Permissions, and Throttling.
  2. Tutorial on authentication and permissions.

For more advanced material, check out the following resources:

  1. Permissions in Django REST Framework series
  2. Django Session-based Auth for Single Page Apps article
  3. DRF authorization with Auth0
  4. Finally Understand Authentication in Django REST Framework (video)
  5. Django Rest Framework Token Authentication (video)

Conclusion

This article looked at the basics of Django REST Framework. You should now have a basic idea of how the core concepts -- serializers, views and ViewSets, routers, authentication and authorization -- can be used to be build a RESTful API. If you feel intrigued by any of the subjects, I encourage you to check out the official documentation as well as any of the additional resources mentioned above.

If you'd like to learn how to create APIs with Django REST Framework, check our the Developing RESTful APIs with Django REST Framework course. The course takes you through the real-life development cycle of an application, building your knowledge of DRF from the ground up along the way. If you already know DRF and are looking for more, you can check Test-Driven Development with Django, Django REST Framework, and Docker, which focuses less on DRF features and more on how to get your API out in the real world.

Špela Giacomelli (aka GirlLovesToCode)

Špela Giacomelli (aka GirlLovesToCode)

GirlThatLovesToCode is passionate about learning new things -- both for herself and for teaching others. She's a fan of Python and Django and wants to know everything there is about those two. When she’s not writing code or a blog, she’s probably trying something new, reading, or spending time outside with her family.

Share this tutorial

Featured Course

Developing RESTful APIs with Django REST Framework

Learn how to build RESTFul APIs with Django and Django REST Framework.

Featured Course

Developing RESTful APIs with Django REST Framework

Learn how to build RESTFul APIs with Django and Django REST Framework.