Skip to main content

Advanced Features

Quick reference for Django's advanced features including middleware, signals, caching, and more.

🔄 Middleware

Custom Middleware

# middleware.py
class CustomMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
# Process request
self.process_request(request)

response = self.get_response(request)

# Process response
self.process_response(request, response)
return response

def process_request(self, request):
# Add custom headers
request.custom_header = 'Custom Value'

def process_response(self, request, response):
# Add response headers
response['X-Custom-Header'] = 'Custom Response'
return response

# settings.py
MIDDLEWARE = [
'myapp.middleware.CustomMiddleware',
# ... other middleware
]

Common Middleware Examples

# Request logging middleware
import logging
import time

class RequestLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.logger = logging.getLogger('requests')

def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time

self.logger.info(
f"{request.method} {request.path} - "
f"{response.status_code} - {duration:.2f}s"
)
return response

# CORS middleware
class CORSMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
return response

📡 Signals

Built-in Signals

from django.db.models.signals import post_save, pre_delete, m2m_changed
from django.dispatch import receiver
from django.contrib.auth.models import User

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)

@receiver(pre_delete, sender=Post)
def backup_post_before_delete(sender, instance, **kwargs):
# Backup logic before deletion
backup_post(instance)

@receiver(m2m_changed, sender=Post.tags.through)
def post_tags_changed(sender, instance, action, pk_set, **kwargs):
if action == 'post_add':
# Handle tags added
update_post_search_index(instance)

Custom Signals

# signals.py
import django.dispatch

# Define custom signal
post_published = django.dispatch.Signal()
user_logged_out = django.dispatch.Signal()

# Send signal
def publish_post(post):
post.is_published = True
post.save()
post_published.send(sender=post.__class__, post=post)

# Connect to signal
@receiver(post_published)
def handle_post_published(sender, post, **kwargs):
# Send notification email
send_publication_email(post)
# Update search index
update_search_index(post)

💾 Caching

Cache Configuration

# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
},
'sessions': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/2',
}
}

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'sessions'

Cache Usage

from django.core.cache import cache
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator

# Low-level cache API
def get_popular_posts():
posts = cache.get('popular_posts')
if posts is None:
posts = Post.objects.filter(view_count__gte=1000)[:10]
cache.set('popular_posts', posts, 300) # 5 minutes
return posts

# View caching
@cache_page(60 * 15) # 15 minutes
def post_list(request):
posts = Post.objects.all()
return render(request, 'posts.html', {'posts': posts})

# Class-based view caching
@method_decorator(cache_page(60 * 15), name='dispatch')
class PostListView(ListView):
model = Post
template_name = 'posts.html'

# Template fragment caching
"""
{% load cache %}
{% cache 500 post_list %}
<!-- Expensive template rendering -->
{% endcache %}
"""

Cache Invalidation

from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key

def invalidate_post_cache(post):
# Clear specific cache keys
cache.delete(f'post_{post.id}')
cache.delete('popular_posts')

# Clear template fragment cache
key = make_template_fragment_key('post_detail', [post.id])
cache.delete(key)

# Clear pattern-based cache (requires django-redis)
cache.delete_pattern('post_*')

🔍 Custom Managers & QuerySets

Custom QuerySet

class PostQuerySet(models.QuerySet):
def published(self):
return self.filter(is_published=True)

def by_author(self, author):
return self.filter(author=author)

def recent(self, days=7):
from django.utils import timezone
cutoff = timezone.now() - timedelta(days=days)
return self.filter(created_at__gte=cutoff)

def with_comments(self):
return self.prefetch_related('comments')

def popular(self):
return self.filter(view_count__gte=100).order_by('-view_count')

class PostManager(models.Manager):
def get_queryset(self):
return PostQuerySet(self.model, using=self._db)

def published(self):
return self.get_queryset().published()

def popular_published(self):
return self.get_queryset().published().popular()

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
is_published = models.BooleanField(default=False)
view_count = models.IntegerField(default=0)

objects = PostManager()

# Usage: Post.objects.published().recent().popular()

Multiple Managers

class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_published=True)

class DraftManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_published=False)

class Post(models.Model):
title = models.CharField(max_length=200)
is_published = models.BooleanField(default=False)

objects = models.Manager() # Default manager
published = PublishedManager()
drafts = DraftManager()

# Usage:
# Post.objects.all() # All posts
# Post.published.all() # Only published
# Post.drafts.all() # Only drafts

🗄️ Database Optimization

Query Optimization

# Select related (ForeignKey, OneToOne)
posts = Post.objects.select_related('author', 'category').all()

# Prefetch related (ManyToMany, reverse ForeignKey)
posts = Post.objects.prefetch_related('tags', 'comments').all()

# Only fetch needed fields
posts = Post.objects.only('title', 'slug').all()

# Exclude expensive fields
posts = Post.objects.defer('content').all()

# Complex prefetch
from django.db.models import Prefetch
posts = Post.objects.prefetch_related(
Prefetch('comments', queryset=Comment.objects.filter(is_approved=True))
).all()

# Bulk operations
Post.objects.bulk_create([
Post(title='Post 1', content='Content 1'),
Post(title='Post 2', content='Content 2'),
])

Post.objects.bulk_update(posts, ['view_count'], batch_size=100)

Database Indexes

class Post(models.Model):
title = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True) # Automatically indexed
published_date = models.DateTimeField(db_index=True)

class Meta:
indexes = [
models.Index(fields=['title', 'published_date']),
models.Index(fields=['-published_date']), # Descending
models.Index(fields=['author', 'category']),
]

🔄 Async Views & Database

Async Views

import asyncio
from django.http import JsonResponse
from asgiref.sync import sync_to_async

async def async_view(request):
# Async database query
posts = await sync_to_async(list)(
Post.objects.filter(is_published=True)
)

# Async external API call
async with aiohttp.ClientSession() as session:
async with session.get('https://api.example.com/data') as resp:
external_data = await resp.json()

return JsonResponse({
'posts': [post.title for post in posts],
'external_data': external_data
})

# Async class-based view
from django.views import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

@method_decorator(csrf_exempt, name='dispatch')
class AsyncAPIView(View):
async def get(self, request):
data = await self.get_data()
return JsonResponse(data)

async def get_data(self):
# Async data fetching
return {'message': 'Hello from async view'}

📊 Custom Template Tags & Filters

Template Tags

# templatetags/custom_tags.py
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.simple_tag
def current_time(format_string):
from datetime import datetime
return datetime.now().strftime(format_string)

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
"""Replace URL parameters"""
query = context['request'].GET.copy()
for key, value in kwargs.items():
query[key] = value
return query.urlencode()

@register.inclusion_tag('tags/show_posts.html')
def show_recent_posts(count=5):
posts = Post.objects.published()[:count]
return {'posts': posts}

# Usage in templates:
# {% load custom_tags %}
# {% current_time "%Y-%m-%d %H:%M" %}
# {% url_replace page=2 %}
# {% show_recent_posts 10 %}

Template Filters

@register.filter
def truncate_words(value, length):
"""Truncate text to specified word count"""
words = value.split()
if len(words) <= length:
return value
return ' '.join(words[:length]) + '...'

@register.filter
def currency(value):
"""Format number as currency"""
return f"${value:,.2f}"

@register.filter(is_safe=True)
def highlight(text, search):
"""Highlight search terms in text"""
if not search:
return text
highlighted = text.replace(
search,
f'<mark>{search}</mark>'
)
return mark_safe(highlighted)

# Usage:
# {{ post.content|truncate_words:20 }}
# {{ price|currency }}
# {{ content|highlight:query }}

🌐 Internationalization (i18n)

Setup

# settings.py
USE_I18N = True
USE_L10N = True
USE_TZ = True

LANGUAGES = [
('en', 'English'),
('es', 'Spanish'),
('fr', 'French'),
]

LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]

MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware',
# ... other middleware
]

Translation in Code

from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _lazy

# In views
def my_view(request):
message = _("Welcome to our website!")
return render(request, 'template.html', {'message': message})

# In models (use lazy)
class Post(models.Model):
title = models.CharField(_lazy("Title"), max_length=200)
content = models.TextField(_lazy("Content"))

class Meta:
verbose_name = _lazy("Post")
verbose_name_plural = _lazy("Posts")

# Pluralization
from django.utils.translation import ngettext

def post_count_message(count):
return ngettext(
'There is %(count)d post',
'There are %(count)d posts',
count
) % {'count': count}

Translation in Templates

{% load i18n %}

<!-- Simple translation -->
<h1>{% trans "Welcome" %}</h1>

<!-- With variables -->
<p>{% blocktrans with name=user.name %}Hello {{ name }}!{% endblocktrans %}</p>

<!-- Pluralization -->
{% blocktrans count counter=posts.count %} There is {{ counter }} post. {%
plural %} There are {{ counter }} posts. {% endblocktrans %}

<!-- Language switching -->
{% get_current_language as LANGUAGE_CODE %} {% get_available_languages as
LANGUAGES %} {% for lang_code, lang_name in LANGUAGES %}
<a href="{% url 'set_language' %}?language={{ lang_code }}">{{ lang_name }}</a>
{% endfor %}

🔧 Custom Management Commands

# management/commands/cleanup_posts.py
from django.core.management.base import BaseCommand
from django.utils import timezone
from myapp.models import Post

class Command(BaseCommand):
help = 'Delete old draft posts'

def add_arguments(self, parser):
parser.add_argument(
'--days',
type=int,
default=30,
help='Delete drafts older than this many days'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be deleted without actually deleting'
)

def handle(self, *args, **options):
days = options['days']
dry_run = options['dry_run']

cutoff_date = timezone.now() - timezone.timedelta(days=days)
old_drafts = Post.objects.filter(
is_published=False,
created_at__lt=cutoff_date
)

count = old_drafts.count()

if dry_run:
self.stdout.write(
self.style.WARNING(f'Would delete {count} draft posts')
)
else:
old_drafts.delete()
self.stdout.write(
self.style.SUCCESS(f'Successfully deleted {count} draft posts')
)

# Usage: python manage.py cleanup_posts --days=60 --dry-run

This cheatsheet covers Django's advanced features for building sophisticated web applications with performance optimization and extensibility.