Django 4 Best Practices: Leveraging Asynchronous Handlers for Class-Based Views


By squashlabs, Last Updated: April 20, 2023

Django 4 Best Practices: Leveraging Asynchronous Handlers for Class-Based Views


Django has consistently evolved to provide developers with new features and improvements. With the release of Django 4, developers now have access to even more powerful tools for building high-performance web applications. One such feature is asynchronous handlers for class-based views, which allows developers to optimize their views for scalability and efficiency. In this post, we will explore the best practices for leveraging asynchronous handlers in Django 4 class-based views to unlock their full potential and take your web development to the next level.

Understanding Asynchronous Handlers:

Asynchronous handlers, also known as async views, are a feature introduced in Django 3.1 and improved in Django 4 that allow developers to write asynchronous code in their views. Asynchronous code allows for concurrent execution of tasks, which can lead to improved performance and responsiveness in web applications. Django’s async views leverage Python’s asyncio library, which provides a powerful way to write asynchronous code using coroutines, event loops, and other async-related concepts.

Why Use Asynchronous Handlers for Class-Based Views?

Asynchronous handlers can provide several benefits when used in class-based views in Django 4. Some of the main advantages are:

  1. Improved Scalability: Asynchronous handlers allow for concurrent execution of tasks, which can greatly improve the scalability of your web application. This means that your application can handle more requests simultaneously, resulting in better performance and responsiveness, especially when dealing with a high volume of concurrent requests.
  2. Enhanced Efficiency: Asynchronous handlers can help reduce resource consumption by allowing your views to perform tasks concurrently without blocking the event loop. This can result in more efficient resource utilization and improved performance, especially when dealing with tasks that involve I/O-bound operations, such as making API requests, querying databases, or fetching data from external services.
  3. Better User Experience: Asynchronous handlers can improve the overall user experience of your web application by reducing latency and response times. Asynchronous code can allow your views to perform tasks concurrently in the background, without blocking the main thread, which can lead to faster response times and a more responsive user interface.

Best Practices for Leveraging Asynchronous Handlers in Django 4 Class-Based Views

Now that we understand the benefits of using asynchronous handlers in class-based views in Django 4, let’s explore some best practices for leveraging this powerful feature:

  1. Use Asynchronous Handlers Only When Necessary: Asynchronous handlers are most beneficial when dealing with I/O-bound operations that can benefit from concurrent execution. It’s important to carefully analyze your application’s requirements and determine where asynchronous handlers can provide the most value. Avoid using asynchronous handlers for CPU-bound tasks or when they are not needed, as they may add unnecessary complexity to your code.
  2. Choose the Right Asynchronous Library: Django 4 supports multiple asynchronous libraries, such as asyncio, uvloop, and daphne, among others. It’s important to choose the right library that fits the requirements of your application and aligns with your development workflow. Consider factors such as performance, ease of use, community support, and documentation when selecting an asynchronous library for your Django project.
  3. Follow Best Practices for Writing Asynchronous Code: Writing asynchronous code requires careful consideration of async-related concepts, such as coroutines, event loops, and concurrency. It’s important to familiarize yourself with best practices for writing asynchronous code in Python and Django, such as using the async and await keywords, handling exceptions, managing event loops, and avoiding blocking operations.
  4. Optimize Database Access: Database access can be a potential bottleneck in web applications, even when using asynchronous handlers. It’s crucial to optimize your database access to ensure optimal performance. Consider using asynchronous

A better way to build and deploy Web Apps

  Cloud Dev Environments
  Test/QA enviroments

One-click preview environments for each branch of code.

Code examples

Let’s say we want to build a small web app where users can upload files, and then the app will process the files and display some results. We want to use Django 4.1’s new asynchronous handlers for class-based views to make the file processing more efficient.

Django App

First, we need to create a Django project and app:

# Create a new Django project
django-admin startproject file_processor

# Create a new Django app inside the project
cd file_processor
python startapp file_upload

Next, we’ll define a model to represent the uploaded files:

from django.db import models

class UploadedFile(models.Model):
    file = models.FileField(upload_to='uploads/')
    processed = models.BooleanField(default=False)

Now, let’s create a view to handle file uploads. We’ll use an asynchronous handler to simulate a long-running task (file processing) and mark the file as processed in the database when the task is complete:

from django.views.generic import FormView
from django.urls import reverse_lazy
from .models import UploadedFile
import asyncio

class UploadFileView(FormView):
    template_name = 'file_upload.html'
    form_class = UploadFileForm
    success_url = reverse_lazy('file_upload')

    async def form_valid(self, form):
        # Get the uploaded file
        uploaded_file = form.cleaned_data['file']

        # Save the file to the database
        uploaded_file_obj = UploadedFile.objects.create(file=uploaded_file)

        # Simulate file processing
        await asyncio.sleep(10)

        # Mark the file as processed in the database
        uploaded_file_obj.processed = True

        return super().form_valid(form)

In this example, we’re using the asyncio.sleep() function to simulate a 10-second file processing task. In a real-world project, this task might involve running some complex algorithms or interacting with external APIs.

Finally, we’ll create a view to display the processed files:

from django.views.generic import ListView
from .models import UploadedFile

class ProcessedFileView(ListView):
    template_name = 'processed_files.html'
    queryset = UploadedFile.objects.filter(processed=True)
    context_object_name = 'processed_files'

This view simply lists all the processed files in the database.

That’s it! With these class-based views, we can efficiently handle file uploads and processing in our web app.

Docker, Nginx, PostgreSQL and Gunicorn

If you want to take this to the next step and proceed to an actual production deployment, then you need the following examples below.

Here’s an example of a requirements.txt file that includes Django 4.1 and other required dependencies for the project:


And here’s an example of a Docker Compose file that sets up a Django project with asynchronous handlers for class-based views:

version: '3'

    build: .
    command: python runserver
      - "8000:8000"
      - .:/code
      - db
    image: postgres
      POSTGRES_DB: myproject
      POSTGRES_USER: myprojectuser
      POSTGRES_PASSWORD: myprojectpassword

Note that you will likely need to customize the example above to fit your specific project requirements.

Here is an example Nginx configuration file (nginx.conf):

events {
    worker_connections 1024;

http {
    upstream django {
        server web:8000;

    server {
        listen 80;

        location / {
            proxy_pass http://django;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        location /static/ {
            alias /code/static/;

This configuration file sets up a reverse proxy to the Gunicorn server running on the web container, and serves static files directly from the code/static directory. You can customize this file to fit your specific needs.

Gunicorn and Supervisor

Here is an example of a file:

import multiprocessing

bind = ""
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"

And here’s an example of a supervisor.conf file:

command=/path/to/gunicorn myproject.wsgi:application -c /path/to/

You’ll need to replace /path/to/gunicorn and /path/to/myproject with the appropriate paths for your project. The command line in the supervisor.conf file should match the command you use to start Gunicorn.

You can save these files in the same directory as your Django project or in a separate directory. Just make sure to update the paths in the supervisor.conf file accordingly.


Leveraging asynchronous handlers in class-based views involve careful consideration of your application’s requirements, choosing the right asynchronous library, following best practices for writing asynchronous code, and optimizing database access.

By incorporating these best practices into your Django development workflow, you can unlock the full potential of asynchronous handlers and build high-performance web applications that deliver an exceptional user experience.

Related Articles: