Django async capabilities mature. Django 3.1 enables async views beyond middleware, improves JSON field operations, and enhances admin customization. At ZIRA Software, async Django powers real-time healthcare applications.
Async Views
Function-based async views:
import asyncio
import httpx
from django.http import JsonResponse
async def async_dashboard(request):
async with httpx.AsyncClient() as client:
# Fetch multiple APIs concurrently
responses = await asyncio.gather(
client.get('https://api.service1.com/data'),
client.get('https://api.service2.com/stats'),
client.get('https://api.service3.com/metrics'),
)
return JsonResponse({
'service1': responses[0].json(),
'service2': responses[1].json(),
'service3': responses[2].json(),
})
Class-based async views:
from django.views import View
from django.http import JsonResponse
import asyncio
class AsyncApiView(View):
async def get(self, request):
data = await self.fetch_external_data()
return JsonResponse({'data': data})
async def post(self, request):
result = await self.process_data(request.POST)
return JsonResponse({'result': result})
async def fetch_external_data(self):
await asyncio.sleep(0.1) # Simulate API call
return {'status': 'success'}
async def process_data(self, data):
await asyncio.sleep(0.1) # Simulate processing
return {'processed': True}
JSON Field Improvements
Enhanced JSON field querying:
from django.db import models
from django.contrib.postgres.fields import JSONField
class Product(models.Model):
name = models.CharField(max_length=200)
metadata = JSONField(default=dict)
# New in Django 3.1: Better JSON operations
products = Product.objects.filter(
metadata__color='blue',
metadata__size__in=['S', 'M', 'L']
)
# JSON array operations
products = Product.objects.filter(
metadata__tags__contains=['electronics']
)
# Nested JSON queries
products = Product.objects.filter(
metadata__specifications__dimensions__width__gte=100
)
JSON aggregation:
from django.db.models import JSONField
from django.contrib.postgres.aggregates import JSONBAgg
# Aggregate JSON data
categories = Category.objects.annotate(
product_metadata=JSONBAgg('products__metadata')
)
for category in categories:
print(category.product_metadata)
Admin Interface Improvements
Customizable admin actions:
from django.contrib import admin
from django.utils.html import format_html
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'stock_status', 'colored_status']
list_filter = ['category', 'created_at']
search_fields = ['name', 'description']
@admin.display(description='Status')
def colored_status(self, obj):
if obj.stock > 0:
color = 'green'
text = 'In Stock'
else:
color = 'red'
text = 'Out of Stock'
return format_html(
'<span style="color: {};">{}</span>',
color,
text
)
@admin.action(description='Mark as featured')
def make_featured(self, request, queryset):
queryset.update(featured=True)
self.message_user(request, f'{queryset.count()} products marked as featured.')
actions = [make_featured]
Inline formset improvements:
class OrderItemInline(admin.TabularInline):
model = OrderItem
extra = 1
fields = ['product', 'quantity', 'price']
readonly_fields = ['price']
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
# Custom formset logic
return formset
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
inlines = [OrderItemInline]
list_display = ['order_number', 'customer', 'total', 'status']
Path Converters Enhancement
Custom path converters:
# converters.py
class UUIDConverter:
regex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
def to_python(self, value):
import uuid
return uuid.UUID(value)
def to_url(self, value):
return str(value)
# urls.py
from django.urls import path, register_converter
from . import converters, views
register_converter(converters.UUIDConverter, 'uuid')
urlpatterns = [
path('objects/<uuid:id>/', views.object_detail),
]
Model Constraints
Check constraints:
from django.db import models
from django.db.models import CheckConstraint, Q
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
discount_price = models.DecimalField(max_digits=10, decimal_places=2, null=True)
class Meta:
constraints = [
CheckConstraint(
check=Q(price__gt=0),
name='price_positive'
),
CheckConstraint(
check=Q(discount_price__lt=models.F('price')) | Q(discount_price__isnull=True),
name='discount_less_than_price'
),
]
Async ORM Operations
Still need sync_to_async wrapper:
from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
@sync_to_async
def get_user(user_id):
return User.objects.get(id=user_id)
@sync_to_async
def get_users():
return list(User.objects.filter(is_active=True))
async def user_view(request):
user = await get_user(1)
users = await get_users()
return JsonResponse({
'user': {'id': user.id, 'username': user.username},
'users_count': len(users)
})
Middleware Compatibility
Async middleware:
class AsyncMiddleware:
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
async def __call__(self, request):
# Async processing before view
await self.process_request(request)
response = await self.get_response(request)
# Async processing after view
await self.process_response(request, response)
return response
async def process_request(self, request):
# Custom async logic
pass
async def process_response(self, request, response):
# Custom async logic
pass
Performance Considerations
When to use async:
# Good use case: Multiple external API calls
async def dashboard(request):
async with httpx.AsyncClient() as client:
weather, news, stocks = await asyncio.gather(
client.get('https://api.weather.com/data'),
client.get('https://api.news.com/headlines'),
client.get('https://api.stocks.com/prices'),
)
return render(request, 'dashboard.html', {
'weather': weather.json(),
'news': news.json(),
'stocks': stocks.json(),
})
# Bad use case: Database-heavy operations
# ORM is still synchronous, so async provides no benefit
async def products_list(request):
# This doesn't benefit from async
products = await sync_to_async(
lambda: list(Product.objects.all())
)()
return render(request, 'products.html', {'products': products})
Testing Async Views
from django.test import AsyncClient, TestCase
class AsyncViewTests(TestCase):
async def test_async_dashboard(self):
client = AsyncClient()
response = await client.get('/dashboard/')
self.assertEqual(response.status_code, 200)
self.assertIn('data', response.json())
async def test_async_api_post(self):
client = AsyncClient()
response = await client.post('/api/process/', {
'data': 'test'
})
self.assertEqual(response.status_code, 200)
self.assertTrue(response.json()['success'])
Migration to Async
Gradual migration:
# Step 1: Identify I/O-bound views
# - External API calls
# - Long-running operations
# - Multiple concurrent operations
# Step 2: Convert to async
# - Add async keyword to function
# - Use await for async operations
# - Wrap sync operations with sync_to_async
# Step 3: Test thoroughly
# - Use AsyncClient for testing
# - Monitor performance
# - Check for blocking operations
Conclusion
Django 3.1 advances async capabilities with async views and improved JSON operations. Enhanced admin interface and model constraints improve developer experience.
Building async Django applications? Contact ZIRA Software for Python/Django expertise.