Django 4.1: Async ORM and Form Rendering

django python

Django 4.1 continues the async journey, adding more ORM methods and introducing a new form rendering system. Here’s what’s new.

Async ORM Expansion

New Async Methods

Django 4.1 adds async versions of common QuerySet methods:

# Previously available
await Model.objects.aget(pk=1)
await Model.objects.acreate(name='test')
await Model.objects.adelete()

# New in 4.1
await Model.objects.acontains(obj)
await Model.objects.aexists()
await Model.objects.abulk_create([...])
await Model.objects.abulk_update([...])

# Iteration
async for item in Model.objects.filter(active=True):
    process(item)

Async Context Managers

async def my_view(request):
    async with transaction.atomic():
        obj = await Model.objects.acreate(name='test')
        await AnotherModel.objects.acreate(ref=obj)
    
    return JsonResponse({'id': obj.id})

Async Iteration

async def fetch_all_users():
    users = []
    async for user in User.objects.filter(is_active=True):
        users.append({
            'id': user.id,
            'email': user.email,
        })
    return users

Form Rendering Improvements

Template-Based Rendering

Forms now render via templates instead of Python code:

# Built-in templates:
# - django/forms/div.html (new default)
# - django/forms/p.html
# - django/forms/table.html

Custom Form Templates

class ContactForm(forms.Form):
    template_name = 'forms/contact.html'
    
    name = forms.CharField()
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)
<!-- forms/contact.html -->
<form method="post" class="my-form">
    {% csrf_token %}
    {% for field in form %}
        <div class="form-group{% if field.errors %} has-error{% endif %}">
            {{ field.label_tag }}
            {{ field }}
            {% if field.help_text %}
                <small class="help-text">{{ field.help_text }}</small>
            {% endif %}
            {% for error in field.errors %}
                <span class="error">{{ error }}</span>
            {% endfor %}
        </div>
    {% endfor %}
    <button type="submit">Send</button>
</form>

Field Template Customization

class StyledCharField(forms.CharField):
    template_name = 'forms/fields/styled_input.html'

Form Group Templates

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

# Add to TEMPLATES
TEMPLATES = [{
    ...
    'DIRS': [BASE_DIR / 'templates'],
    ...
}]

Validation Improvements

Model Constraint Validation

class Reservation(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()
    
    class Meta:
        constraints = [
            models.CheckConstraint(
                check=models.Q(end_date__gt=models.F('start_date')),
                name='end_after_start',
                violation_error_message='End date must be after start date.'
            ),
        ]

Now constraint violations show your custom message instead of a generic error.

Async Validation

class AsyncValidator:
    async def __call__(self, value):
        exists = await User.objects.filter(email=value).aexists()
        if exists:
            raise ValidationError('Email already in use')

Admin Improvements

Dark Mode Support

Django admin now respects prefers-color-scheme:

/* admin/css/base.css */
@media (prefers-color-scheme: dark) {
    :root {
        --body-bg: #121212;
        --body-fg: #ffffff;
        /* ... */
    }
}

Works automatically. No configuration needed.

Improved List Filters

class ArticleAdmin(admin.ModelAdmin):
    list_filter = [
        ('created_at', admin.DateFieldListFilter),
        ('status', admin.ChoicesFieldListFilter),
        ('author', admin.RelatedOnlyFieldListFilter),
    ]

InlineModelAdmin Improvements

class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1
    show_change_link = True  # Link to full edit form

Security Updates

# More secure defaults
CSRF_COOKIE_MASKED = True  # New in 4.1

CSRF tokens are now masked to prevent BREACH attacks.

Password Hasher Updates

# New Argon2id hasher
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    ...
]

Database Features

Collation Support

from django.db.models import CharField
from django.db.models.functions import Collate

# Case-insensitive comparison
Author.objects.annotate(
    name_ci=Collate('name', 'nocase')
).filter(name_ci='john')

More Expression Support

from django.db.models import F, Value
from django.db.models.functions import Concat

# Concat with F expressions improved
Article.objects.annotate(
    full_title=Concat(
        F('category__name'),
        Value(': '),
        F('title')
    )
)

Migration Notes

Breaking Changes

# CSRF_TRUSTED_ORIGINS now requires scheme
CSRF_TRUSTED_ORIGINS = [
    'https://example.com',  # Include https://
]

# Form rendering changed
# Old Python-based rendering may need updates if customized

Deprecations

# Using positional arguments in some functions deprecated
# These will be removed in Django 5.0

Upgrade Checklist

  1. Check CSRF_TRUSTED_ORIGINS: Add schemes (https://)
  2. Test form rendering: Custom form templates may need updates
  3. Update dependencies: Ensure compatibility
  4. Test async code: New async methods may change behavior

Should You Upgrade?

From 4.0

Yes. Straightforward upgrade with good improvements.

From 3.2 LTS

Consider waiting. 3.2 is supported until April 2024. 4.2 LTS comes in April 2023.

Django 3.2 LTS  → Stable until 2024
Django 4.0      → Supported until April 2023
Django 4.1      → Current release
Django 4.2 LTS  → Coming April 2023 (wait for this if patient)

Final Thoughts

Django 4.1 is iterative progress. The async ORM expansion is welcome, form template rendering is long overdue, and dark mode admin is a nice touch.

Not revolutionary, but solid.


Incremental improvements that make the framework better.

All posts