What's New in Django 2.2 LTS?

python backend django

Django 2.2 LTS dropped in April 2019. As a Long Term Support release, it’ll receive security updates until April 2022. Here’s what’s new and why this version matters.

Why LTS Matters

LTS releases are stability checkpoints:

If you’re on 1.11 LTS, now’s the time to upgrade to 2.2 LTS.

New Database Features

Check Constraints

Database-level validation:

from django.db import models
from django.db.models import Q

class Product(models.Model):
    price = models.DecimalField(max_digits=10, decimal_places=2)
    discount_price = models.DecimalField(max_digits=10, decimal_places=2, null=True)
    
    class Meta:
        constraints = [
            models.CheckConstraint(
                check=Q(price__gte=0),
                name='price_non_negative'
            ),
            models.CheckConstraint(
                check=Q(discount_price__lt=models.F('price')),
                name='discount_less_than_price'
            ),
        ]

Raises IntegrityError if constraints are violated.

Unique Constraints with Conditions

class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    is_active = models.BooleanField(default=True)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['user'],
                condition=Q(is_active=True),
                name='one_active_order_per_user'
            ),
        ]

Only one active order per user. Inactive orders don’t count.

Bulk Update with Computation

from django.db.models import F

# Update based on current values
Product.objects.filter(category='electronics').update(
    price=F('price') * 1.1,
    updated_at=timezone.now()
)

# bulk_update for heterogeneous updates
products_to_update = []
for product in products:
    product.price = calculate_new_price(product)
    products_to_update.append(product)

Product.objects.bulk_update(products_to_update, ['price'])

bulk_update is efficient for updating many objects with different values.

Improved Error Messages

Model validation errors now include the field name:

Before:

ValidationError: {'__all__': ['Constraint failed']}

After:

ValidationError: {'price': ['Price must be non-negative']}

Test Client Improvements

JSON by Default

# Before
response = self.client.post(
    '/api/users/',
    data=json.dumps({'name': 'Alice'}),
    content_type='application/json'
)

# Now
response = self.client.post(
    '/api/users/',
    data={'name': 'Alice'},
    content_type='application/json'
)

Force Login

# Skip authentication entirely
self.client.force_login(user)

Migration Improvements

RunSQL with State Operations

from django.db import migrations

class Migration(migrations.Migration):
    operations = [
        migrations.RunSQL(
            sql='CREATE INDEX ...',
            reverse_sql='DROP INDEX ...',
            state_operations=[
                migrations.AddIndex(
                    model_name='mymodel',
                    index=models.Index(fields=['field'], name='my_index'),
                ),
            ],
        ),
    ]

Keep migration state in sync with custom SQL.

Security Enhancements

Password Validation Improvements

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {'min_length': 12}
    },
    # Custom validator example
    {
        'NAME': 'myapp.validators.NoCommonPatternsValidator',
    },
]

SecurityMiddleware Improvements

SECURE_REFERRER_POLICY = 'same-origin'  # New in 2.2

Admin Enhancements

Dark Mode Preparation

The admin now uses CSS custom properties, making theming easier:

:root {
    --primary: #417690;
    --secondary: #79aec8;
}

Autocomplete for ForeignKey

class BookAdmin(admin.ModelAdmin):
    autocomplete_fields = ['author']  # Search-as-you-type

Query Expression Improvements

Window Functions Enhancements

from django.db.models import F, Window
from django.db.models.functions import Lag, Lead, RowNumber

queryset = Sale.objects.annotate(
    row_number=Window(
        expression=RowNumber(),
        order_by=F('date').desc()
    ),
    previous_sale=Window(
        expression=Lag('amount'),
        order_by=F('date').asc()
    ),
)

Upgrade Path

From Django 2.1

Smooth upgrade. Run deprecation warnings:

python -Wd manage.py test

From Django 1.11 LTS

Larger jump. Upgrade path:

  1. Django 1.11 → 2.0 (handle on_delete, URL changes)
  2. Django 2.0 → 2.1
  3. Django 2.1 → 2.2

Or jump directly if you’ve addressed all deprecations.

Deprecations to Watch

These will be removed in Django 3.0.

Should You Upgrade?

Yes, if:

Consider waiting if:

Final Thoughts

Django 2.2 LTS is a solid release. The new constraint features bring ORM closer to database capabilities. The improved testing tools make development smoother.

For any production Django project, upgrading to an LTS release is a smart long-term decision.


Stability matters. LTS delivers.

All posts