Scale a Django app — react to events with signals, speed it up with caching, run slow work in the background with Celery, write async views, and translate the UI.
When signals: you want to run code when something happens elsewhere — create a profile whenever a user is created, clear a cache when a post is saved. Note: connect the receiver in your app’s AppConfig.ready() so it loads once. Prefer overriding save() when the logic belongs to one model.
# blog/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Post
@receiver(post_save, sender=Post)
def on_post_saved(sender, instance, created, **kwargs):
if created:
print(f"New post: {instance.title}")
# blog/apps.py
class BlogConfig(AppConfig):
def ready(self):
from . import signals # noqa: register the receiverWhy: caching stores the result of slow work (a heavy query, an API call) so repeat requests are instant. When the low-level cache: wrap any value. When @cache_page: cache a whole view’s output for N seconds. Note: configure a backend (Redis or Memcached) in production.
from django.core.cache import cache
posts = cache.get("popular_posts")
if posts is None:
posts = list(Post.objects.order_by("-views")[:10])
cache.set("popular_posts", posts, timeout=300) # 5 minutesfrom django.views.decorators.cache import cache_page
@cache_page(60 * 15) # cache this view for 15 minutes
def post_list(request):
...Why: never make a user wait while you send an email or process an upload — hand slow work to Celery, a separate worker process, and return immediately. Note: Celery needs a broker (Redis or RabbitMQ). You call the task with .delay() and it runs in the background.
# blog/tasks.py — pip install celery redis
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def email_subscribers(post_id):
# slow work happens off the request/response cycle
send_mail("New post!", "...", "me@blog.com", ["fan@example.com"])# in a view — returns instantly, task runs in the worker
email_subscribers.delay(post.id)# run the worker alongside your server
celery -A config worker -l infoWhen async def: a view that mostly waits on I/O — calling external APIs, async database drivers. Django runs it on the async stack so the worker can handle other requests while it waits. Note: use the async ORM methods (aget, acreate, and async for) inside async views.
from django.http import JsonResponse
async def latest_posts(request):
posts = []
async for post in Post.objects.order_by("-created_at")[:5]:
posts.append(post.title)
return JsonResponse({"posts": posts})Why: internationalization lets one codebase serve many languages. Note: wrap user-facing strings in gettext (Python) or {% translate %} (templates), then run makemessages to extract them and compilemessages to build them. Django picks the language from the request.
from django.utils.translation import gettext as _
message = _("Welcome to my blog") # translatable string{% load i18n %}
<h1>{% translate "Welcome to my blog" %}</h1>