Overview

Django 4.2, released on April 3, 2023, is an LTS (Long Term Support) release that will receive security updates until April 2026. Its most anticipated feature is official support for Psycopg 3, the new PostgreSQL driver for Python, which is faster and natively asynchronous.

This release also allows adding comments directly on database columns and tables, and introduces the update_conflicts parameter in bulk_create() for elegant upsert handling.

Key Features

Psycopg 3 support

Django now officially supports Psycopg 3 (psycopg) as a PostgreSQL driver, alongside Psycopg 2 (psycopg2). Psycopg 3 offers native asyncio support, improved type handling, a query pipeline, and better performance for parameterized queries.

python
# settings.py — Using Psycopg 3
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        # Psycopg 3 is used automatically if installed
        # pip install psycopg[binary]
        'NAME': 'my_project',
        'USER': 'django_user',
        'PASSWORD': 'secret',
        'HOST': 'localhost',
        'PORT': '5432',
        'OPTIONS': {
            # Psycopg 3 specific options
            'pool': {
                'min_size': 2,
                'max_size': 10,
            },
        },
    },
}

# To force Psycopg 2 (backward compatibility)
# pip install psycopg2-binary
# Django auto-detects the available version

# Check which driver is being used
from django.db import connection
print(connection.pg_version)  # e.g., 150004
print(type(connection.connection))  # psycopg.Connection or psycopg2

Comments on columns and tables

The new db_comment attribute on fields and the Meta.db_table_comment option allow adding SQL comments directly in the database schema. These comments are visible in database administration tools and make schema documentation easier for DBA teams.

python
from django.db import models

class Customer(models.Model):
    """Customer model with database comments."""
    name = models.CharField(
        max_length=100,
        db_comment='Full name of the customer (individual or company)',
    )
    tax_id = models.CharField(
        max_length=20,
        blank=True,
        db_comment='Tax identification number for businesses',
    )
    credit_score = models.IntegerField(
        default=0,
        db_comment='Internal score from 0 to 1000, computed monthly',
    )
    registration_date = models.DateTimeField(
        auto_now_add=True,
        db_comment='Automatic registration date (UTC)',
    )

    class Meta:
        db_table_comment = (
            'Active platform customers. '
            'Archived customers are in the customer_archive table.'
        )

# In SQL, this produces:
# COMMENT ON TABLE customer IS 'Active platform customers. ...'
# COMMENT ON COLUMN customer.name IS 'Full name of the customer ...'

bulk_create with update_conflicts

The bulk_create() method now accepts the update_conflicts parameter for performing upserts (INSERT ... ON CONFLICT UPDATE). This allows bulk inserting while automatically updating existing records, all in a single SQL query.

python
from django.db import models

class ProductPrice(models.Model):
    product_code = models.CharField(max_length=20, unique=True)
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)
    last_updated = models.DateTimeField(auto_now=True)

# Daily import from a supplier feed
supplier_products = [
    ProductPrice(product_code='SKU-001', name='Mechanical Keyboard', price=89.99, stock=150),
    ProductPrice(product_code='SKU-002', name='27-inch Monitor', price=349.00, stock=42),
    ProductPrice(product_code='SKU-003', name='Ergonomic Mouse', price=59.50, stock=200),
    # ... thousands of products
]

# Upsert in a single query: insert new products,
# update price and stock for existing ones
ProductPrice.objects.bulk_create(
    supplier_products,
    update_conflicts=True,
    unique_fields=['product_code'],
    update_fields=['price', 'stock'],
)

# Generated SQL:
# INSERT INTO product_price (product_code, name, price, stock)
# VALUES (...), (...), (...)
# ON CONFLICT (product_code)
# DO UPDATE SET price = EXCLUDED.price, stock = EXCLUDED.stock

Sources