Django 4.0: What's New

django python

Django 4.0 is here. While not a revolutionary release, it brings practical improvements that make Django development smoother. Let’s explore.

Major Changes

Redis Cache Backend

Native Redis support—no third-party package needed:

# settings.py
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}

Multiple servers:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": [
            "redis://127.0.0.1:6379",
            "redis://127.0.0.1:6380",
        ],
    }
}

This was one of the most requested features.

Template-Based Form Rendering

Forms now use templates instead of Python strings:

# Old approach (still works)
{{ form.as_p }}

# New approach: Template-based
{{ form }}  # Uses default template

# Or specify custom template
{{ form.render }}

Built-in template packs:

Customize per-form:

class MyForm(forms.Form):
    template_name = "my_forms/custom.html"
    # or
    template_name_label = "my_forms/label.html"

Timezone Changes

USE_L10N is now True by default, and USE_TZ must be True.

# Django 3.x
USE_TZ = True  # Optional

# Django 4.0
USE_TZ = True  # Required for timezone support

If you don’t want timezone support, explicitly set it.

Improved startproject Template

New projects include:

# settings.py improvements
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# .gitignore included
# Better directory structure

New Features

scrypt Password Hasher

More secure password hashing:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.ScryptPasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    # ...
]

Scrypt is memory-hard, making brute-force attacks more expensive.

Functional Unique Constraints

Unique constraints on expressions:

from django.db import models
from django.db.models.functions import Lower

class Author(models.Model):
    name = models.CharField(max_length=100)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(
                Lower('name'),
                name='unique_lower_name'
            ),
        ]

Case-insensitive unique names!

UniqueConstraint Improvements

Conditional unique constraints:

class Article(models.Model):
    title = models.CharField(max_length=200)
    is_published = models.BooleanField(default=False)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['title'],
                condition=models.Q(is_published=True),
                name='unique_published_title'
            ),
        ]

Only published articles need unique titles.

asgiref Updates

Better async support:

from asgiref.sync import sync_to_async, async_to_sync

# Wrap sync function for async context
@sync_to_async
def get_user_sync(user_id):
    return User.objects.get(id=user_id)

# Use in async view
async def my_view(request):
    user = await get_user_sync(1)
    return JsonResponse({'name': user.name})

Prefetch Improvements

# Filter prefetched related objects
from django.db.models import Prefetch

authors = Author.objects.prefetch_related(
    Prefetch(
        'books',
        queryset=Book.objects.filter(published=True).order_by('-pub_date')[:5]
    )
)

aget, acreate, adelete

Async ORM methods (still limited):

# Async get
user = await User.objects.aget(id=1)

# Async create
article = await Article.objects.acreate(title="Hello", author=user)

# Async delete
await article.adelete()

Note: Full async ORM is still in progress.

Breaking Changes

Python 3.8+ Required

Django 4.0 drops Python 3.6 and 3.7:

# Minimum versions
Python >= 3.8
PostgreSQL >= 10
MySQL >= 5.7
SQLite >= 3.9.0

Removed Features

Removed after deprecation period:

# Old (removed)
if request.is_ajax():
    # ...

# New
if request.accepts("application/json"):
    # ...

URL Patterns

# Old (still works but discouraged)
from django.conf.urls import url

# New (preferred)
from django.urls import path, re_path

Upgrade Guide

Step 1: Python Version

python --version  # Must be 3.8+

Step 2: Check Deprecation Warnings

python -Wd manage.py test

Step 3: Update Dependencies

pip install Django==4.0
pip install --upgrade psycopg2 celery djangorestframework

Step 4: Run Tests

python manage.py test

Step 5: Address Breaking Changes

# Update request.is_ajax() usage
# Update JSONField imports
# Ensure USE_TZ is set

Should You Upgrade?

From Django 3.2 LTS

Not urgent. 3.2 is supported until April 2024.

Django 3.2 LTS → Stable, supported
Django 4.0    → Latest features
Django 4.2 LTS → Wait for LTS (April 2023)

For New Projects

Yes, start with 4.0:

For Existing Projects

Consider:

Final Thoughts

Django 4.0 is evolutionary, not revolutionary. Native Redis caching and template-based forms are the highlights. The async story continues to improve.

If you’re on 3.2 LTS, no rush. If starting new, use 4.0.


Incremental progress, stable foundations.

All posts