Django REST Framework Views - APIViews

Last updated September 27th, 2021

Django REST Framework (DRF) has its own flavor of views that inherit from Django's View class. This three-part series takes an in-depth look at all the DRF view possibilities -- from a simple view, where you have to do a lot on our own, to the ModelViewSet, where you can get a view up and running with just a few lines of code. Since views are built on top of one other, this series also explains how they intertwine.

In this article, we look at how DRF views work and get to know the most basic view, APIView.

--

Django REST Framework Views Series:

  1. APIViews (this article!)
  2. Generic Views
  3. ViewSets

Contents

Objectives

By the end of this article, you should be able to:

  1. Explain how DRF views work
  2. Explain the purpose of the APIView class and how it's different from Django's View class
  3. Use function and class-based views
  4. Utilize policy decorators (for function-based views) and policy attributes (for class-based views)

DRF Views

The essential component of DRF views is the APIView class, which subclasses Django's View class.

APIView class is a base for all the views that you might choose to use in your DRF application.

Whether it be-

  • function-based views
  • class-based views
  • mixins
  • generic view classes
  • viewsets

-they all use the APIView class.

As you can tell from the image below, the options you have at your disposal with regard to DRF views intertwine, extending from one other. You can think of the views as building blocks that form bigger building blocks. With that, you'll probably use some building blocks -- like APIViews, concrete views, and (ReadOnly)ModelViewSets -- more than others -- like mixins and GenericViewSets. This all depends on your specific application's needs, of course.

DRF Views Overview

Extending APIView offers the most freedom, but it also leaves a lot more work for you. It's a great choice if you need to have control over every aspect of the view or if you have very complicated views.

With the generic view classes, you can develop faster and still have quite a bit of control over the API endpoints.

With ModelViewSets, you can get an API stood up with five lines of code (three for your views, two for the URLS).

All of the above mentioned views can be customized as well.

There's no correct answer as to what to use. You don't even have to use the same view type in a single app; you can mix and match and combine them as you see fit. That said, it's good to be predictable so only deviate from a view type when it's absolutely necessary.

DRF Views are split into three parts in the documentation. The articles in this series follow the same organization.

  1. Documentation for APIViews (part 1 of this series)
  2. Documentation for Generic views (part 2 of this series)
  3. Documentation for ViewSets (part 3 of this series)

It's worth noting that the official documentation treats each view as a separate chapter rather than subchapters from a single Views chapter as you might expect.

Along with the API guide, you also have the official tutorial that covers all three views as well:

  1. Tutorial using APIViews
  2. Tutorial using Generic views
  3. Tutorial using ViewSets

Let's start with the most basic view, APIView, followed by an explanation of how the view works.

Class-based Views

Class-based views extend the APIView class. With them, you determine how requests will be handled and which policy attributes you're going to use.

For example, let's say you have an Item class for your shopping list API:

class Item(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=100)
    done = models.BooleanField()

Here's a view that allows users to delete all the items at once:

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

class DeleteAllItems(APIView):

    def delete(self, request):

        Item.objects.all().delete()

        return Response(status=status.HTTP_204_NO_CONTENT)

And here's a view that lists all the items:

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

class ListItems(APIView):

    def get(self, request):
        items = Item.objects.all()
        serializer = ItemSerializer(items, many=True)
        return Response(serializer.data)

As you can see, the call to the database is done inside the handler functions. They're selected according to the request's HTTP method (e.g., GET -> get, DELETE -> delete).

We'll dive into more about how these views works here shortly.

As you can see, we've set a serializer in the second view. Serializers are responsible for converting complex data (e.g., querysets and model instances) to native Python datatypes that can then be rendered into JSON, XML, or other content types.

You can learn more about DRF serializers in the Effectively Using Django REST Framework Serializers article.

Policy Attributes

If you want to override the default settings for your class-based views, you can use policy attributes.

The policy attributes that can be set are:

Attribute Usage Examples
renderer_classes determines which media types the response returns JSONRenderer, BrowsableAPIRenderer
parser_classes determines which data parsers for different media types are allowed JSONParser, FileUploadParser
authentication_classes determines which authentication schemas are allowed for identifying the user TokenAuthentication, SessionAuthentication
throttle_classes determines if a request should be authorized based on the rate of requests AnonRateThrottle, UserRateThrottle
permission_classes determines if a request should be authorized based on user credentials IsAuthenticated, DjangoModelPermissions
content_negotiation_class selects one of the multiple possible representations of the resource to return to a client (unlikely you'll want to set it up) only custom content negotiation classes

Be sure to review the Custom Permission Classes in Django REST Framework article to learn more about permission classes.

In the following example, we changed the permissions and how a response is rendered with the permission_classes and renderer_classes policy attributes:

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

class ItemsNotDone(APIView):

    permission_classes = [IsAuthenticated]  # policy attribute
    renderer_classes = [JSONRenderer]       # policy attribute

    def get(self, request):

        user_count = Item.objects.filter(done=False).count()
        content = {'not_done': user_count}

        return Response(content)

Function-based Views

There are two ways to directly implement APIView: With a function or with a class. If you're writing a view in the form of a function, you'll need to use the @api_view decorator.

@api_view is a decorator that converts a function-based view into an APIView subclass (thus providing the Response and Request classes). It takes a list of allowed methods for the view as an argument.

Curious how DRF converts function-based views into APIView subclasses?

# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/decorators.py#L16

def api_view(http_method_names=None):
    http_method_names = ['GET'] if (http_method_names is None) else http_method_names
    def decorator(func):
        WrappedAPIView = type(
            'WrappedAPIView',
            (APIView,),
            {'__doc__': func.__doc__}
        )

        # ...

        return WrappedAPIView.as_view()

Here's a function-based view that does the same thing as the previously written class-based view, for deleting all items:

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['DELETE'])
def delete_all_items(request):
    Item.objects.all().delete()
    return Response(status=status.HTTP_200_OK)

Here, we converted delete_all_items to an APIView subclass with the @api_view decorator. Only the DELETE method is allowed. Other methods will respond with "405 Method Not Allowed".

Overlooking the difference between how a class and a function are written, we have access to the same properties so both code snippets achieve the same result.

Policy Decorators

If you want to override the default settings for your function-based view, you can use policy decorators. You can use one or multiple of the following:

  • @renderer_classes
  • @parser_classes
  • @authentication_classes
  • @throttle_classes
  • @permission_classes

Those decorators correspond to APIView subclasses. Because the @api_view decorator checks if any of the following decorators are used, they need to be added below the api_view decorator.

If we use the same example that we did for the policy attributes, we can implement the decorators like so to achieve the same results:

from rest_framework.decorators import api_view, permission_classes, renderer_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes([IsAuthenticated])  # policy decorator
@renderer_classes([JSONRenderer])       # policy decorator
def items_not_done(request):
    user_count = Item.objects.filter(done=False).count()
    content = {'not_done': user_count}

    return Response(content)

How Do DRF Views Work?

When a request hits a view, the view first initializes a Request object, which is a DRF-enhanced HttpRequest from Django.

When compared to Django's HttpRequest, it has the following advantages:

  1. Content is automatically parsed according to the Content-Type header and is available as request.data.
  2. It supports PUT and PATCH methods (including file uploads). (Django only supports GET and POST methods.)
  3. By temporarily overriding the method on a request, it checks permissions against other HTTP methods.

After creating the Request instance, the view stores the accepted info in the request using the provided (or default) content negotiator and renderers. After that, the view performs authentication and then checks for permissions and any throttling.

Authentication itself doesn't return any errors. It simply determines who the user of the request is. That information is required by the permission and throttle checks. Upon checking permissions, if authentication was not successful, the NotAuthenticated exception is raised. If the request is not permitted, a PermissionDenied exception is raised. Upon checking throttling, if the request is throttled, the Throttled exception is raised and the user is notified of how long they need to wait for the request to be permitted.

Permission checking actually has two parts: check_permissions and check_object_permissions.

check_permissions, which covers general permissions, is called before the view handler is executed. If you're only extending APIView, check_object_permissions doesn't get executed unless you explicitly call it. If you're using Generic Views or ViewSets, check_object_permissions is called for the detail views.

For more on DRF permissions, check out the Permissions in Django REST Framework article.

After the authentication, authorization/permissions, and throttling checks, the view checks if the request method is one of the following:

  • get
  • post
  • put
  • patch
  • delete
  • head
  • option
  • trace

If it is, it checks if the request method corresponds to the method in your view and executes it. If either method is not allowed or is not defined in the called view, the MethodNotAllowed exception is raised.

The dispatch method in the APIView class checks the method and selects a handler based on the method name:

# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/views.py#L485

class APIView(View):

    # ...

    def dispatch(self, request, *args, **kwargs):

        # ...

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

The allowed methods are not defined in DRF but are taken from Django:

# https://github.com/django/django/blob/stable/3.2.x/django/views/generic/base.py#L36

class View:
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

Finally, instead of Django's HttpResponse, the Response object is returned. The difference between Django's HttpResponse and DRF's Response, is that Response is initialized with un-rendered data, allowing content to be rendered into multiple content types, depending on the client request.

Conclusion

There are multiple types of views in DRF. The most widely used ones are:

  1. class-based views that extends APIView class
  2. concrete views
  3. ModelViewSet

They vary in how easily customizable they are and on their ease of use. You set the policies (i.e., throttling, permissions) inside the view, for class-based views, or with decorators, with function-based views.

Extending APIView gives you the most freedom to customize what happens in the view itself.

Deep Dive into Django REST Framework Views Series:

  1. APIViews (this article!)
  2. Generic Views
  3. ViewSets

Š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

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.