© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
V. GagliardiDecoupled Django https://doi.org/10.1007/978-1-4842-7144-5_3

3. Modern Django and the Django REST Framework

Valentino Gagliardi1  
(1)
Colle di Val D’Elsa, Italy
 
This chapter covers:
  • The Django REST framework and Django side by side

  • Asynchronous Django

I guess all Django developers share a common story. They built a lot of stuff and tried the mini-framework approach à la Flask, but in the end, they always returned to Django simply because it is opinionated, and it offers all the tools for building full-stack web applications with Python. The Django REST Framework is a Django package that follows the same pragmatic approach. In this chapter, we compare the Django REST Framework to Django, and we explore the asynchronous Django landscape.

What Is the Django REST Framework?

The Django REST Framework (DRF for short) is a Django package for building Web APIs.

Despite the rapid spread of GraphQL and the emergence of asynchronous micro-frameworks like Starlette and FastAPI, the DRF still powers thousands of web services. The DRF integrates seamlessly with Django to complement its features for building REST APIs. In particular it offers an array of ready-made components:
  • Class-based REST views

  • Viewsets

  • Serializers

This chapter isn’t intended as a guide to the DRF for beginners, but it is worth spending some words to go over the main building blocks of this package. In the next sections, we explore these components, since they will be the Lego blocks for the first part of our decoupled Django project.

Class-Based Views in Django and the DRF

When building web applications, some common patterns for handling data insertion and data listing repeat over and over.

Consider an HTML form for example. There are three distinct phases to take account of:
  • Displaying the form, either empty or with initial data

  • Validating the user input and showing eventual errors

  • Saving the data to the database

It would be foolish to copy-paste the same code again and again in our projects. For this reason, Django offers a convenient abstraction around common patterns in web development. These classes go under the name of class-based view, or CBV for short. Some examples of CBV in Django are CreateView, ListView, DeleteView, UpdateView, and DetailView. As you might have noticed, the naming of these classes goes hand in hand with the CRUD pattern, so common in REST APIs and in traditional web applications. In particular:
  • CreateView and UpdateView for POST requests

  • ListView and DetailView for GET requests

  • DeleteView for DELETE requests

The Django REST Framework follows the same convention and offers a wide toolbox of class-based views for REST API development:
  • CreateAPIView for POST requests

  • ListAPIView and RetrieveAPIView for GET requests

  • DestroyAPIView for DELETE requests

  • UpdateAPIView for PUT and PATCH requests

In addition, you can peruse a combination of CBVs for retrieve/delete operations like RetrieveDestroyAPIView, or for retrieve/update/destroy like RetrieveUpdateDestroyAPIView. You will use a lot of these CBVs in your decoupled Django projects to speed up development of the most common tasks, although the DRF offers a more powerful layer on top of CBVs, called viewsets.

Tip

For a complete list of class-based views in Django, see ccbv.co.uk. For the Django REST Framework, see cdrf.co.

CRUD Viewsets in DRF

In Chapter 1, we reviewed the concept of resources as one of the main building blocks of REST.

In the MVC Framework, operations on resources are handled by a controller that exposes methods for CRUD verbs. We also clarified that Django is an MVT Framework, rather than MVC. In Django and the DRF, we use class-based views to expose common CRUD operations in terms of GET, POST, PUT, and so on. Nevertheless, the Django REST Framework offers a clever abstraction over class-based views, called viewsets , which make the DRF look more “resourceful” than ever. Listing 3-1 shows a viewset, specifically a ModelViewSet.
from rest_framework import viewsets
from .models import Blog, BlogSerializer
class BlogViewSet(viewsets.ModelViewSet):
   queryset = Blog.objects.all()
   serializer_class = BlogSerializer
Listing 3-1

A ModelViewSet in DRF

Such a viewset gives you all the methods for handling common CRUD operations for free. Table 3-1 summarizes the relationship between viewset methods, HTTP methods, and CRUD operations.
Table 3-1

Relationship Between Viewset Methods, HTTP Methods, and CRUD Operations

Viewset Methods

HTTP Method

CRUD Operation

create()

POST

Create resource

list() / retrieve()

GET

Retrieve resource(s)

update()

PUT

Update resource

destroy()

DELETE

Delete resource

update()

PATCH

Partial update resource

Once you have a viewset, it’s only a matter of wiring up the class with an urlpatterns. Listing 3-2 shows the urls.py for the previous viewset.
from .views import BlogViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r"blog", BlogViewSet, basename="blog")
urlpatterns = router.urls
Listing 3-2

Viewset and Urlpatterns in Django REST

As you can see, with a minimal amount of code you have the complete collection of CRUD operations, with the corresponding URLs.

Models, Forms, and Serializers

The ability to create pages and forms with little or no code at all is what makes Django shine.

Thanks to model forms, for example, it takes a couple of lines of code to create a form starting from a Django model, complete with validation and error handling, ready to be included in a view. When you are in a hurry, you can even assemble a CreateView, which takes exactly three lines of code (at least) to produce the HTML form for a model, attached to the corresponding template. If Django model forms are the bridge between the end user and the database, serializers in the Django REST Framework are the bridge between the end user, our REST API, and Django models. Serializers are in charge of serialization and deserialization of Python objects, and they can be thought of as model forms for JSON. Consider the model shown in Listing 3-3.
class Quote(models.Model):
   client = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
   proposal_text = models.TextField(blank=True)
Listing 3-3

A Django Model

From this model, we can make a DRF model serializer, shown in Listing 3-4.
class QuoteSerializer(serializers.ModelSerializer):
   class Meta:
       model = Quote
       fields = ["client", "proposal_text"]
Listing 3-4

A DRF Serializer

When we hit a DRF endpoint, the serializer converts the underlying model instances to JSON before any output is shown to the user. Vice versa, when we make a POST request against a DRF view, the serializer converts our JSON to the corresponding Python object, not before validating the input. Serializers can also express model relationships. In Listing 3-4, Quote is connected to the custom user model through a many-to-one relationship. In our serializer we can expose this relationship as an hyperlink, as shown in Listing 3-5 (remember hypermedia APIs?).
class QuoteSerializer(serializers.ModelSerializer):
   client = serializers.HyperlinkedRelatedField(
       read_only=True, view_name="users-detail"
   )
   class Meta:
       model = Quote
       fields = ["client", "proposal_text"]
Listing 3-5

A DRF Serializer

This will produce the JSON output shown in Listing 3-6.
[
   {
     "client": "https://api.example/api/users/1",
     "proposal_text": "Django quotation system"
   },
   {
     "client": "https://api.example/api/users/2",
     "proposal_text": "Django school management system"
   }
]
Listing 3-6

A JSON Response with Relationships

In Chapter 6, we use serializers to decouple our Django project. Having outlined the building blocks of the DRF, let’s now explore the wonderful world of asynchronous Django.

From WSGI to ASGI

WSGI is the lingua franca of web servers to Python communication, that is, a protocol that enables the back and forth between web servers such as Gunicorn, and the underlying Python application.

As anticipated in Chapter 2, Django needs a web server to run efficiently in production. Usually, a reverse proxy such as NGINX acts as the main entry point for the end user. A Python WSGI server listens for requests behind NGINX and acts as a bridge between the HTTP request and the Django application. Everything happens synchronously in WSGI, and there was no way to rewrite the protocol without introducing breaking changes. That led the community (for this tremendous work we must thank Andrew Godwin) to write a new protocol, called ASGI, for running asynchronous Python applications under ASGI-capable web servers. To run Django asynchronously, and we are going to see what that means in the next section, we need an asynchronous-capable server. You can choose Daphne, Hypercorn, or Uvicorn. In our example, we will use Uvicorn.

Getting Started with Asynchronous Django

Asynchronous code is all about non-blocking execution. This is the magic behind platforms like Node.js, which predated the realm of high throughput services for years.

The asynchronous Python landscape instead has always been fragmented, with many PEPs and competing implementations before the arrival of async/await in Python 3.5 (2015). Asynchronicity in Django was a dream, until Django 3.0, when seminal support for the aforementioned ASGI found its way into the core. Asynchronous views (Django 3.1) are one of the most exciting additions to Django in recent years. To understand what problem asynchronicity solves in Django, and in Python in general, consider a simple Django view. When the user reaches this view, we fetch a list of links from an external service, as shown in Listing 3-7.
from django.http import JsonResponse
import httpx
client = httpx.Client(base_url="https://api.valentinog.com/demo")
def list_links(_request):
    links = client.get("/sleep/").json()
    json_response = {"links": links}
    return JsonResponse(data=json_response)
Listing 3-7

A Synchronous View Doing Network Calls

This should immediately raise a red flag. It can run fast, really fast, or take forever to complete, leaving the browser hanging. Due to the single-threaded nature of the Python interpreter, our code runs in sequential steps. In our view, we can’t return the response to the user until the API call completes. In fact my link, https://api.valentinog.com/demo/sleep/, is configured to sleep for 10 seconds before returning the result. In other words, our view is blocking. Here httpx, the Python HTTP client I use to make requests, is configured with a safe timeout and will raise an exception after a few seconds, but not every library has this sort of security in place.

Any IO-bound operation can potentially starve resources or block the whole execution. Traditionally, to overcome this problem in Django, we would use a task queue , a component that runs in the background, picks up tasks to execute, and returns the result later. The most popular task queues for Django are Celery and Django Q. Task queues are highly suggested for IO-bound operations like sending emails, running scheduled jobs, HTTP requests, or for CPU-bound operations that need to run on multiple cores. Asynchronous views in Django don’t completely replace task queues, especially for CPU-bound operations. Django Q for example uses Python multiprocessing. For non-critical IO-bound operations instead, like HTTP calls or sending emails, Django asynchronous views are great. In the most simple case, you can send out an email or call an external API without incurring the risk of blocking the user interface. So what’s in an asynchronous Django view? Let’s rewrite the previous example with an asynchronous view in a way that the httpx client retrieves data in the background; see Listing 3-8.
from django.http import HttpResponse
import httpx
import asyncio
async def get_links():
   base_url = "https://api.valentinog.com/demo"
   client = httpx.AsyncClient(base_url=base_url, timeout=15)
   response = await client.get("/sleep")
   json_response = response.json()
   # Do something with the response or with the json
   await client.aclose()
async def list_links(_request):
   asyncio.create_task(get_links())
   response = "<p>Fetching links in background</p>"
   return HttpResponse(response)
Listing 3-8

An Asynchronous View Doing Network Calls, This Time Safely

If you never worked with asynchronous Python and Django, there are a few new concepts worth clarifying in this code. First of all, we import asyncio, the bridge between us and the asynchronous Python world. We then declare a first asynchronous function with async def. In this first function, get_links(), we use the asynchronous httpx client with a timeout of 15 seconds. Since we are going to run this call in the background, we can safely increase the timeout. Next up, we use await in front of client.get(). Finally, we close the client with client.aclose(). To avoid leaving resources open, you can also use the asynchronous client with an asynchronous context manager. In this case, we can refactor to async with, as shown in Listing 3-9.
async def get_links():
   base_url = "https://api.valentinog.com/demo"
   async with httpx.AsyncClient(base_url=base_url, timeout=15) as client:
       response = await client.get("/sleep")
       json_response = response.json()
       # Do something with the json ...
Listing 3-9

Using an Asynchronous Context Manager

Tip

An asynchronous context manager is one that implements __aenter__ and __aexit__ instead of __enter__ and __exit__.

In the second asynchronous function list_links() , our Django view, we use asyncio.create_task() to run get_links() in the background. This is the real news. async def in a Django view is the most notable change from a developer’s perspective. For users instead, the most evident benefit is that they don’t have to wait to see the HTML if the execution takes longer than expected. In the scenario we imagined previously, for example, we can send the results to the user later with an email message. This is one of the most compelling use cases for asynchronous views in Django. But it doesn’t stop here. To recap, things you can do now that asynchronous Django is a thing:
  • Efficiently execute multiple HTTP requests in parallel in a view

  • Schedule long-running tasks

  • Interact safely with external systems

There are still things missing before Django and the DRF become 100% asynchronous—the ORM and the Django REST views are not asynchronous—but we will use asynchronous Django capabilities here and there in our decoupled project to practice.

Competing Asynchronous Frameworks and the DRF

At the time of writing, the Django REST Framework has no support for asynchronous views.

In light of this, wouldn’t it be better to use something like FastAPI or Starlette for building asynchronous web services? Starlette is an ASGI framework built by Tom Christie, the DRF creator. FastAPI instead builds on top of Starlette and offers a stellar developer tooling for building asynchronous Web APIs. Both are excellent choices for greenfield projects, and luckily you don’t have to choose, because FastAPI can run within Django itself, thanks to experimental projects like django-ninja, while we wait for asynchronous DRF.

Summary

This chapter reviewed the fundamentals of the Django REST Framework and covered how to run a simple asynchronous Django view. You learned:
  • What are the DRF class-based views, viewsets, and serializers

  • How to create and asynchronous Django view

  • How to run Django under Uvicorn

In the next chapter, we analyze in detail the patterns for decoupling Django, while in Chapter 6, we finally get hands on with Django and JavaScript frontends.

Additional Resource

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.204.34.64