Overview

Django 3.1, released on August 4, 2020, continues the async transition initiated by Django 3.0. Views, middlewares, and tests now natively support async/await. The other major feature is JSONField, now available for all supported databases, not just PostgreSQL. This release also adds pathlib.Path support in settings.

Key Features

JSONField for all databases

The JSONField from django.db.models now works with SQLite, MySQL, MariaDB, and PostgreSQL. Previously, only django.contrib.postgres.fields.JSONField was available. The new universal field supports lookups like __contains, __has_key, and key/index path transforms.

python
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    specs = models.JSONField(default=dict)
    tags = models.JSONField(default=list)

# Create with JSON data
monitor = Product.objects.create(
    name='27-inch Monitor',
    specs={
        'resolution': '2560x1440',
        'size': 27,
        'ports': ['HDMI', 'DisplayPort', 'USB-C'],
        'panel': {'type': 'IPS', 'refresh_rate': 144},
    },
    tags=['electronics', 'office', 'sale'],
)

# Filter by nested key
ips_monitors = Product.objects.filter(
    specs__panel__type='IPS'
)

# Check for key existence
with_ports = Product.objects.filter(
    specs__has_key='ports'
)

# Filter by list content
on_sale = Product.objects.filter(
    tags__contains=['sale']
)

Async views and middleware

Django 3.1 allows defining views with async def. The framework automatically detects whether a view is synchronous or asynchronous and adapts its execution. Middleware can also define async methods. This paves the way for non-blocking HTTP calls and WebSocket connections directly in Django views.

python
import httpx
from django.http import JsonResponse

# Async view
async def city_weather(request, city):
    """Fetch weather via an external API without blocking."""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f'https://api.weather.example/v1/{city}'
        )
    data = resp.json()
    return JsonResponse({
        'city': city,
        'temperature': data['temp'],
        'description': data['description'],
    })

# Async middleware
class AsyncLoggingMiddleware:
    async_capable = True

    def __init__(self, get_response):
        self.get_response = get_response

    async def __call__(self, request):
        import time
        start = time.monotonic()
        response = await self.get_response(request)
        duration = time.monotonic() - start
        print(f"{request.path}: {duration:.3f}s")
        return response

pathlib.Path support

Django settings now accept pathlib.Path objects wherever a file path is expected. The generated settings.py file uses Path instead of os.path for BASE_DIR.

python
# settings.py (generated by Django 3.1+)
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# No more os.path.join needed
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_ROOT = BASE_DIR / 'media'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
    },
]

Sources