Computer scienceBackendDjangoAPI

Views and routing in DRF

5 minutes read

When we interact with a website, our requests are captured by a URL pattern in Django and then forwarded to Django views to handle. After that, the view determines what to return as a response to the user. Yes, the DRF API views are very similar to Django views. The main difference is that we return HTML data in Django views, while in DRF API views, we return JSON data. As you probably know, URLs and Views are the most critical parts of a Django project. We can have perfect API endpoints without models and serializers. Models and serializers are crucial, but there's no getting around URLs and Views. Alongside regular URLs and Views in DRF, we also will learn about different URL configurations and view classes; we'll refer to them as routing and viewsets, respectively.

Views

When building APIs with Django, one cannot help but appreciate the elegance and power of Class-Based Views (CBVs). Before diving into the more sophisticated world of Django Rest Framework (DRF), it is worthwhile to understand how you can employ basic CBVs to add API endpoints to your application. Let's take a simple example to illustrate this. Suppose you have a Django project and must create a basic API for a list of products. Using a CBV approach without DRF, you can define a view class, inherit from Django's View class, and write methods for HTTP verbs like GET and POST to handle requests and return responses. Let's create simple function-based and class-based views as an example:

# exmaple function-based view
from rest_framework.decorators import api_view
from rest_framework.response import Response


@api_view()
def hello_world(request):
    return Response({"message": "Hello, world!"})
# example class-based view
from rest_framework.views import APIView
from rest_framework.response import Response



class HelloWorldView(APIView):
    
    def get(self, request, *args, **kwargs):
        """
        implemented get() method of the APIView to return a response to GET requests.
        """
        return Response({"message": "Hello, world!"})

The above examples show how we can make simple views without models and serializers. Implementing views is not going to be always that simple. While this approach certainly works, it can be more manual and require additional code to handle serialization, validation, and URL routing. We may want to show all our article lists to our users.

# views.py
from django.views.generic import View
from django.http import JsonResponse
from .models import Article

class ArticleListView(View):
    def get(self, request):
        articles = Article.objects.all()
        article_list = []
        for article in articles:
            article_list.append({
                'title': article.title,
                'content': article.content,
            })
        return JsonResponse({'articles': article_list})

This code will work, but it doesn't provide the flexibility, automatic data serialization, or ease of creating APIs that the Django Rest Framework provides. This is where DRF comes to the rescue, streamlining the process and providing powerful tools for building APIs efficiently. With the help of DRF generic views, we can quickly create views that do regular things, such as returning a list of objects and object details and create/update/delete objects. For example, to return a list of Article objects, we can create a ListAPIView by extending the generics.ListAPIView class:

# views.py
from rest_framework import generics
from rest_framework.permissions import AllowAny

from .models import Article
from .serializers import ArticleSerializer  # created in Serializers topic

class ArticleListAPIView(generics.ListAPIView):
    serializer_class = ArticleSerializer
    permission_class = [AllowAny]  # AllowAny provides public access to this APIView
    queryset = Article.objects.all()  # We need to specify queryset to retrieve data from database

Similarly, we can also define APIs to create, update, delete, or retrieve a single object as follows:

# retrieve a single Article object using object id; it accepts GET http method
class ArticleRetrieveAPIView(generics.RetrieveAPIView):
    serializer_class = ArticleSerializer
    permission_class = [AllowAny]  
    queryset = Article.objects.all()


# create an Article object(will be stored in database); it accepts POST http method
class ArticleCreateAPIView(generics.CreateAPIView):
    serializer_class = ArticleSerializer
    permission_class = [AllowAny]  
    # we don't need a queryset here as we're not returning any existing objects


# update an Article object; it accepts PUT and PATCH http methods
class ArticleUpdateAPIView(generics.UpdateAPIView):
    serializer_class = ArticleSerializer
    permission_class = [AllowAny]  
    queryset = Article.objects.all()


# delete an Article object; it accepts DELETE http method
class ArticleDeleteAPIView(generics.DeleteAPIView):
    serializer_class = ArticleSerializer
    permission_class = [AllowAny]  
    queryset = Article.objects.all()

URLs

We need to connect the above views with the URL patterns to accept HTTP requests from clients. We use Django's URL patterns to connect the above views with URLs.

from django.urls import path

from . import views


urlpatterns = [
    path('articles/', views.ArticleListAPIView.as_view()),
    path('articles/<int:id>', views.ArticleRetrieveAPIView.as_view()),
    path('articles/<int:id>', views.ArticleCreateAPIView.as_view()),
    path('articles/<int:id>', views.ArticleUpdateAPIView.as_view()),
    path('articles/<int:id>', views.ArticleDeleteAPIView.as_view()),
]

Viewsets and routers

We can refer to DRF's viewsets and routers to allow users to do all kinds of operations. Let's see how we can create all the above functionalities with them:

# viewsets.py
from rest_framework import viewsets
from rest_framework.permissions import AllowAny

from .models import Article
from .serializers import ArticleSerializer  # created in Serializers topic


class ArticleViewset(viewsets.ModelViewSet):
    serializer_class = ArticleSerializer
    permission_class = [AllowAny] 
    queryset = Article.objects.all()

ModelViewSet can reduce the code repetition significantly and speed up our development (note that errors are reduced, too). Viewset model implements list, retrieve, create, update, partial_update, delete calls; we've defined them as separate classes previously. Usually, we use routers to create URL patterns for viewsets to remove repetition:

# urls.py or routers.py (add this URL patterns with project's URL patterns)
from rest_framework import routers

from .viewsets import 

router = routers.SimpleRouter()  # created a SimpleRouter object where we can register viewsets
router.register('articles', ArticleViewSet)

urlpatterns = router.urls  # routers.urls returns a URLs pattern

As we can see, generic views and model viewsets are convenient. We can choose the ones that suit us best based on our endpoint requirements. What if we need APIs that do not interact with models? We can use the two other generic views for this API type — generics.GenericAPIView and viewsets.GenericViewSet. You can learn more about those in the documentation. They're great! And the last one — what if we need to change any portion of the behaviors of model-based views? We can always override the methods we want to change!

Conclusion

This topic taught us about API Views, function-based and class-based views, generic views, viewsets, and routers. With these topics, we've introduced you to Django Rest Framework and provided you with the basics and a complete guide to getting started. There's always more to learn; we'll continue doing it with new Django Rest Framework topics.

4 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo