Django 4.2 LTS: Psycopg3 and Model Comments

django python

Django 4.2 is the new LTS release, supported until April 2026. Notable additions include Psycopg 3 support, model field comments, and transaction hooks. Here’s what you need to know.

Psycopg 3 Support

Django now supports both Psycopg 2 and 3:

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',  # Works with both
        'NAME': 'mydb',
        # Psycopg 3 used automatically if installed
    }
}

Why Psycopg 3

FeaturePsycopg 2Psycopg 3
Async supportLimitedNative
Connection poolingExternalBuilt-in
Type adaptationComplexSimpler
MaintenanceLegacyActive

Migration

# Replace psycopg2
pip uninstall psycopg2-binary
pip install psycopg[binary]  # or psycopg[c] for compiled

Most code works unchanged.

Database Field Comments

Document your schema in the database:

class Product(models.Model):
    name = models.CharField(
        max_length=255,
        db_comment="Product display name"
    )
    price = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        db_comment="Price in default currency (USD)"
    )
    sku = models.CharField(
        max_length=50,
        unique=True,
        db_comment="Stock Keeping Unit - unique product identifier"
    )
    
    class Meta:
        db_table = "products"
        db_table_comment = "Product catalog including pricing and inventory"
-- Generated SQL
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,  -- COMMENT 'Product display name'
    ...
);
COMMENT ON TABLE products IS 'Product catalog including pricing and inventory';

Comments appear in database tools and documentation.

Transaction Hooks

Execute code when transactions complete:

from django.db import transaction

def create_user_with_welcome_email(name, email):
    with transaction.atomic():
        user = User.objects.create(name=name, email=email)
        Profile.objects.create(user=user)
        
        # Only send email if transaction commits
        transaction.on_commit(lambda: send_welcome_email(user))
        
    # Email sent after the with block exits (if no exception)

Robust Hooks

transaction.on_commit(
    lambda: send_notification(user_id),
    robust=True  # Continue even if callback fails
)

Composite Primary Keys (Preview)

Experimental support for multi-column primary keys:

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    
    class Meta:
        constraints = [
            models.CompoundPrimaryKey('order', 'product'),  # Experimental
        ]

Generate-Only Fields

Fields populated by the database:

class Article(models.Model):
    title = models.CharField(max_length=255)
    created_at = models.DateTimeField(
        db_default=Now()  # Database generates this
    )
    slug = models.GeneratedField(
        expression=Lower(F("title")),
        output_field=models.CharField(max_length=255),
        db_persist=True  # Stored, not computed on read
    )

Validation Improvements

StepValueValidator

from django.core.validators import StepValueValidator

class Measurement(models.Model):
    # Value must be a multiple of 0.5
    temperature = models.DecimalField(
        max_digits=5,
        decimal_places=1,
        validators=[StepValueValidator(0.5)]
    )

Constraint Validation Messages

class Reservation(models.Model):
    start = models.DateTimeField()
    end = models.DateTimeField()
    
    class Meta:
        constraints = [
            models.CheckConstraint(
                check=Q(end__gt=F('start')),
                name='valid_date_range',
                violation_error_message="End must be after start"
            )
        ]

Admin Improvements

Dark Mode

Automatic dark mode based on system preference:

# No configuration needed - follows prefers-color-scheme

Faceted Filters

class ArticleAdmin(admin.ModelAdmin):
    list_filter = [
        ('status', admin.EmptyFieldListFilter),  # Filter by empty/non-empty
        ('category', admin.RelatedFieldListFilter),  # With counts
    ]

Async QuerySet Methods

Continued async ORM expansion:

# New in 4.2
await Article.objects.acontains(article)
await Article.objects.aexists()

# Async iteration with generator
async for article in Article.objects.filter(published=True):
    await process(article)

Migration Squashing

Improved support for squashing migrations:

python manage.py squashmigrations myapp 0001 0050

Better handling of:

Other Notable Changes

URL Dispatcher

# Path converters can now be complex
path('items/<list:ids>/', views.items)  # Custom converter for lists

Template Engine

# Improved render speed for complex templates
# No code changes required

Security

# CSRF token masking to prevent BREACH
CSRF_COOKIE_MASKED = True  # Default in 4.2

Upgrade Path

From 4.0/4.1

Straightforward:

pip install Django>=4.2,<4.3
python manage.py migrate

From 3.2 LTS

More changes. Check deprecation warnings:

python -W error::DeprecationWarning manage.py check

Key breaking changes:

LTS Timeline

Django 3.2 LTS: Supported until April 2024
Django 4.2 LTS: Supported until April 2026
Django 5.2 LTS: Expected April 2025

For production apps, 4.2 is the target.

Final Thoughts

Django 4.2 is a solid LTS release. Psycopg 3 support is the headline, but the quality-of-life improvements (comments, transaction hooks, validation) make daily development smoother.

If you’re on 3.2, now’s a good time to plan the upgrade.


Django 4.2: Three years of stability and support.

All posts