API Views
DRF provides an @api_view decorator for function-based views and an APIView class for class-based views. These provide the core functionality for handling requests and returning responses.
Function-Based Views (@api_view)
The @api_view decorator ensures your view receives instances of DRF's Request (instead of Django's HttpRequest) and allows returning a DRF Response.
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
@api_view(['GET', 'POST'])
def hello_world(request):
if request.method == 'POST':
return Response({"message": "Got some data!", "data": request.data}, status=status.HTTP_201_CREATED)
return Response({"message": "Hello, world!"})Class-Based Views (APIView)
APIView subclasses Django's View and separates each HTTP method into its own class method. Beyond that structure, it applies DRF's request/response handling to every method: incoming requests become DRF Request objects, returned Response objects run through content negotiation and raised API exceptions become proper error responses.
The BlogList class below acts as a Collection API. It handles fetching a list of multiple items and creating new ones.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Blog
from .serializers import BlogSerializer
class BlogList(APIView):
"""
List all blogs, or create a new blog.
"""
def get(self, request, format=None):
blogs = Blog.objects.all()
serializer = BlogSerializer(blogs, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = BlogSerializer(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)You can also define methods like put, patch and delete to handle operations on a single item. The BlogDetail class below acts as a Detail API. These are typically mapped to a URL that includes an identifier (like pk).
from django.http import Http404
class BlogDetail(APIView):
"""
Retrieve, update or delete a blog instance.
"""
def get_object(self, pk):
try:
return Blog.objects.get(pk=pk)
except Blog.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
blog = self.get_object(pk)
serializer = BlogSerializer(blog)
return Response(serializer.data)
def put(self, request, pk, format=None):
blog = self.get_object(pk)
serializer = BlogSerializer(blog, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, pk, format=None):
blog = self.get_object(pk)
serializer = BlogSerializer(blog, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
blog = self.get_object(pk)
blog.delete()
return Response(status=status.HTTP_204_NO_CONTENT)NOTE
put and patch differ by a single argument. PUT expects the full representation, so the serializer enforces every required field. PATCH passes partial=True, which skips validation for fields absent from request.data and updates only what was sent.
NOTE
In an APIView, you must name your methods after standard HTTP verbs like get or post. You cannot use random function names to handle requests directly. You can write custom helper functions like get_object above, but if you need custom endpoint names, you should look into DRF ViewSets instead (discussed later).
URL Routing
As mentioned above, these methods correspond directly to standard HTTP verbs. You can wire up these views to your URLs in urls.py like this:
from django.urls import path
from .views import BlogList, BlogDetail
urlpatterns = [
# Maps GET and POST requests
path('blogs/', BlogList.as_view()),
# Maps GET, PUT, PATCH and DELETE requests (requires 'pk')
path('blogs/<int:pk>/', BlogDetail.as_view())
]Writing these Collection and Detail APIs manually results in a lot of repeated code - the exact same sequence of querying, serializing, validating and saving happens across multiple methods and views. You can drastically reduce this boilerplate code by using Generic Views & Mixins.
