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:
- APIViews (this article!)
- Generic Views
- ViewSets
Contents
Objectives
By the end of this article, you should be able to:
- Explain how DRF views work
- Explain the purpose of the
APIView
class and how it's different from Django'sView
class - Use function and class-based views
- 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.
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 ModelViewSet
s, 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.
- Documentation for APIViews (part 1 of this series)
- Documentation for Generic views (part 2 of this series)
- 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:
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:
- Content is automatically parsed according to the
Content-Type
header and is available asrequest.data
. - It supports PUT and PATCH methods (including file uploads). (Django only supports
GET
andPOST
methods.) - 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:
- class-based views that extends
APIView
class - concrete views
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:
- APIViews (this article!)
- Generic Views
- ViewSets