What's New in Django 2.2 LTS?
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:
- 3 years of security support
- Upgrade path clarity
- Production-ready features
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:
- Django 1.11 → 2.0 (handle
on_delete, URL changes) - Django 2.0 → 2.1
- Django 2.1 → 2.2
Or jump directly if you’ve addressed all deprecations.
Deprecations to Watch
django.utils.timezone.FixedOffsetQuerySet.earliest/latest()without fieldsHttpRequest.xreadlines()
These will be removed in Django 3.0.
Should You Upgrade?
Yes, if:
- You’re on Django 1.11 LTS (approaching EOL)
- You want Check Constraints for data integrity
- You need
bulk_updatefor performance - You want 3 years of security support
Consider waiting if:
- You’re stable on Django 2.1 and not ready for QA effort
- Critical dependencies aren’t compatible yet
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.