Django 5.2: Custom Middleware e Gestione dei Segnali per Colloqui Tecnici
Padroneggiare middleware e segnali in Django 5.2: pipeline middleware, middleware asincrono, pre_save/post_save signals e pattern comuni nei colloqui tecnici.

Django 5.2 rappresenta una release LTS (Long Term Support) che consolida il sistema dei middleware e dei segnali con miglioramenti significativi, in particolare il supporto completo alla modalità asincrona. Nei colloqui tecnici per posizioni backend Python, la padronanza di questi due meccanismi distingue i candidati che comprendono l'architettura di Django da quelli che si limitano a utilizzarne le funzionalità superficiali. Il middleware controlla il flusso delle richieste HTTP a livello globale, mentre i segnali implementano un pattern publish-subscribe che disaccoppia la logica di business tra componenti indipendenti.
Questo articolo analizza in profondità entrambi i meccanismi, dalla struttura fondamentale fino ai pattern avanzati con supporto asincrono introdotto in Django 5.2, fornendo esempi di codice pronti per la produzione e le risposte attese nelle domande di colloquio Django.
Le tre domande ricorrenti nei colloqui tecnici su Django middleware e signals: (1) spiegare il ciclo di vita di una richiesta attraverso lo stack middleware, (2) descrivere quando utilizzare pre_save rispetto a post_save, (3) illustrare perché QuerySet.update() non attiva i segnali del modello. Padroneggiare questi concetti copre oltre il 90% delle domande su middleware e segnali nei colloqui Django.
Come funziona il middleware in Django
Il middleware in Django opera come una catena di componenti che avvolgono la funzione di vista (view). Ogni middleware riceve la richiesta HTTP in ingresso, la elabora o la modifica, la passa al middleware successivo nella catena e infine processa la risposta restituita dalla view. La struttura segue il pattern "onion" (a cipolla): la richiesta attraversa ogni strato dall'esterno verso l'interno, raggiunge la view, e la risposta risale attraverso gli stessi strati in ordine inverso.
A partire da Django 1.10, il middleware utilizza un'interfaccia basata su classi con il metodo __call__. Il costruttore __init__ riceve la funzione get_response, che rappresenta il middleware successivo nella catena oppure la view stessa nel caso dell'ultimo middleware. Questa architettura consente di eseguire logica sia prima che dopo l'elaborazione della vista.
# middleware.py - Basic middleware structure
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration happens here at server start
def __call__(self, request):
# Code executed BEFORE the view (and later middleware)
response = self.get_response(request)
# Code executed AFTER the view (on the way back)
return responseIl metodo __init__ viene invocato una sola volta all'avvio del server, rendendolo il punto ideale per operazioni di configurazione costose come l'inizializzazione di connessioni a servizi esterni o il caricamento di file di configurazione. Il metodo __call__ viene invece eseguito ad ogni richiesta HTTP, e tutto il codice precedente alla chiamata self.get_response(request) opera nella fase di richiesta, mentre il codice successivo opera nella fase di risposta.
Costruire un middleware di logging delle richieste
Un caso d'uso classico del middleware, e una domanda frequente nei colloqui tecnici, riguarda il logging strutturato delle richieste HTTP. Il middleware seguente misura la durata di ogni richiesta, registra il metodo HTTP, il percorso, lo status code e l'indirizzo IP del client, e gestisce le eccezioni non catturate tramite il metodo hook process_exception.
# apps/core/middleware.py
import logging
import time
logger = logging.getLogger('django.request')
class RequestLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start_time = time.monotonic()
# Attach metadata before view processing
request.start_time = start_time
response = self.get_response(request)
duration = time.monotonic() - start_time
logger.info(
'method=%s path=%s status=%d duration=%.3fs ip=%s',
request.method,
request.get_full_path(),
response.status_code,
duration,
request.META.get('REMOTE_ADDR'),
)
return response
def process_exception(self, request, exception):
# Called only when the view raises an exception
duration = time.monotonic() - getattr(request, 'start_time', 0)
logger.error(
'method=%s path=%s exception=%s duration=%.3fs',
request.method,
request.get_full_path(),
str(exception),
duration,
)
return None # Let Django's default exception handling continueL'utilizzo di time.monotonic() anziché time.time() garantisce misurazioni corrette anche in presenza di aggiustamenti dell'orologio di sistema (NTP sync). L'attributo start_time viene agganciato direttamente all'oggetto request, una tecnica idiomatica in Django per trasportare metadati tra middleware e view. Il metodo process_exception viene invocato esclusivamente quando la view solleva un'eccezione non gestita; restituendo None, il middleware consente alla gestione standard delle eccezioni di Django di proseguire.
Ordinamento del middleware
L'ordine di dichiarazione dei middleware nella lista MIDDLEWARE del file settings.py determina l'ordine di esecuzione. La richiesta attraversa i middleware dall'alto verso il basso, mentre la risposta li attraversa dal basso verso l'alto. Posizionare un middleware personalizzato nel punto sbagliato della catena costituisce un errore comune che può causare comportamenti inattesi e difficili da diagnosticare.
# settings.py - Middleware ordering matters
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Custom middleware placed after Django's core stack
'apps.core.middleware.RequestLoggingMiddleware',
]SecurityMiddleware occupa la prima posizione perché deve gestire redirect HTTPS e header di sicurezza prima di qualsiasi altra elaborazione. SessionMiddleware precede AuthenticationMiddleware perché il sistema di autenticazione dipende dalla sessione per identificare l'utente corrente. Un middleware di logging personalizzato viene tipicamente posizionato alla fine della catena, in modo da poter registrare le informazioni sull'utente autenticato e sullo stato della sessione già disponibili.
Nei colloqui tecnici, una domanda tipica chiede di spiegare cosa accade se AuthenticationMiddleware viene posizionato prima di SessionMiddleware: la risposta è che request.user risulterebbe un utente anonimo per tutte le richieste, poiché la sessione non sarebbe ancora stata caricata al momento del tentativo di autenticazione.
Middleware asincrono in Django 5.2
Django 5.2 estende il supporto ai middleware asincroni, consentendo di sfruttare appieno le view asincrone e le operazioni I/O non bloccanti. Un middleware asincrono dichiara async_capable = True e sync_capable = False, e il metodo __call__ diventa una coroutine che utilizza await per invocare get_response.
# apps/core/middleware.py
import asyncio
import time
class AsyncTimingMiddleware:
# Mark this middleware as async-capable
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
async def __call__(self, request):
start = time.monotonic()
# get_response is awaitable in async context
response = await self.get_response(request)
duration = time.monotonic() - start
response['X-Request-Duration'] = f'{duration:.4f}s'
return responseLa distinzione tra async_capable e sync_capable è cruciale: impostando sync_capable = False, il middleware comunica a Django che non è in grado di gestire view sincrone. Se una view sincrona attraversa questo middleware, Django eseguirà automaticamente il wrapping in un contesto asincrono tramite sync_to_async. Impostando invece entrambi a True, il middleware deve essere in grado di funzionare in entrambe le modalità. In Django 5.2, questa flessibilità consente di costruire stack middleware ibridi dove componenti sincroni e asincroni coesistono senza interventi manuali.
Pronto a superare i tuoi colloqui su Django?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Segnali in Django: il pattern Publish-Subscribe
I segnali (signals) in Django implementano il pattern publish-subscribe a livello di framework, consentendo a componenti disaccoppiati di reagire a eventi specifici senza dipendenze dirette. A differenza del middleware, che opera a livello di richiesta HTTP, i segnali operano a livello di eventi interni del framework: salvataggio di modelli, cancellazione di record, esecuzione di migrazioni, elaborazione di richieste.
Django fornisce segnali predefiniti per le operazioni più comuni sui modelli: pre_save e post_save (prima e dopo il salvataggio), pre_delete e post_delete (prima e dopo la cancellazione), m2m_changed (modifica di relazioni many-to-many). Ogni segnale trasporta informazioni contestuali specifiche tramite argomenti keyword.
post_save per la creazione automatica del profilo
Il caso d'uso più classico di post_save, e il più frequente nelle domande di colloquio, riguarda la creazione automatica di un profilo utente quando viene registrato un nuovo utente. Il decoratore @receiver collega la funzione handler al segnale post_save filtrato per il modello User.
# apps/accounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from apps.accounts.models import UserProfile
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
# 'created' is True only on INSERT, False on UPDATE
if created:
UserProfile.objects.create(
user=instance,
display_name=instance.get_full_name() or instance.username,
)Il parametro created rappresenta un dettaglio critico: vale True esclusivamente quando l'operazione è un INSERT (creazione di un nuovo record), e False per le operazioni UPDATE. Omettere il controllo su created causerebbe un tentativo di creare un profilo duplicato ad ogni salvataggio dell'utente, generando un'eccezione IntegrityError. Nei colloqui tecnici, questa distinzione viene frequentemente verificata per valutare la comprensione del candidato del ciclo di vita dei segnali Django.
pre_save per validazione e trasformazione
Il segnale pre_save viene emesso prima che Django esegua la query SQL di salvataggio, rendendolo ideale per operazioni di validazione, normalizzazione o trasformazione dei dati. Un esempio classico riguarda la generazione automatica dello slug a partire dal titolo di un articolo.
# apps/blog/signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.text import slugify
from apps.blog.models import Article
@receiver(pre_save, sender=Article)
def auto_generate_slug(sender, instance, **kwargs):
if not instance.slug:
base_slug = slugify(instance.title)
slug = base_slug
counter = 1
# Ensure slug uniqueness
while Article.objects.filter(slug=slug).exclude(pk=instance.pk).exists():
slug = f'{base_slug}-{counter}'
counter += 1
instance.slug = slugLa logica di unicità dello slug merita attenzione: il ciclo while verifica l'esistenza di slug duplicati escludendo il record corrente (tramite exclude(pk=instance.pk)), garantendo che un aggiornamento del record non entri in conflitto con sé stesso. Questo pattern è robusto ma presenta un rischio di race condition in ambienti con alta concorrenza; in produzione, l'aggiunta di un vincolo unique a livello di database con gestione dell'eccezione IntegrityError fornisce una garanzia più solida.
Registrazione dei segnali in AppConfig.ready()
I segnali devono essere registrati durante l'inizializzazione dell'applicazione Django. Il metodo raccomandato consiste nell'importare il modulo dei segnali all'interno del metodo ready() della classe AppConfig. Questo garantisce che i receiver vengano collegati esattamente una volta, al momento dell'avvio dell'applicazione.
# apps/accounts/apps.py
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.accounts'
def ready(self):
# Import signals module to register receivers
import apps.accounts.signals # noqa: F401Il commento # noqa: F401 sopprime l'avvertimento del linter riguardo all'import inutilizzato, poiché l'importazione ha l'effetto collaterale di registrare i decorator @receiver. Un errore comune nei colloqui consiste nel dimenticare questa registrazione: senza l'import nel metodo ready(), i segnali vengono definiti ma mai collegati, e gli handler non vengono mai eseguiti. Django non emette alcun avvertimento in questo caso, rendendo il problema particolarmente insidioso.
Segnali personalizzati per eventi di dominio
Oltre ai segnali predefiniti di Django, il framework consente di definire segnali personalizzati per modellare eventi specifici del dominio applicativo. Questo pattern disaccoppia efficacemente la logica di business: il componente che emette l'evento non conosce i sottoscrittori, e i sottoscrittori possono essere aggiunti o rimossi senza modificare il codice emittente.
# apps/orders/signals.py
from django.dispatch import Signal
# Define custom signals with documentation
order_completed = Signal() # Sent after payment confirmation
order_refunded = Signal() # Sent after refund processing
# apps/orders/services.py
from apps.orders.signals import order_completed
def complete_order(order):
order.status = 'completed'
order.save()
# Dispatch signal with relevant context
order_completed.send(
sender=order.__class__,
order=order,
total=order.total_amount,
)
# apps/notifications/receivers.py
from django.dispatch import receiver
from apps.orders.signals import order_completed
@receiver(order_completed)
def send_order_confirmation_email(sender, order, **kwargs):
# Email logic decoupled from order processing
from apps.notifications.services import send_email
send_email(
to=order.customer.email,
template='order_confirmation',
context={'order': order},
)La separazione in tre file distinti illustra il principio di disaccoppiamento: signals.py definisce gli eventi, services.py li emette e receivers.py reagisce ad essi. Il modulo degli ordini non ha alcuna conoscenza del sistema di notifica, e il sistema di notifica può essere rimosso o sostituito senza toccare una sola riga di codice nel modulo degli ordini. Nei colloqui, questo pattern dimostra una comprensione matura dell'architettura Django e dei principi SOLID.
Errori comuni nei colloqui tecnici
Il metodo QuerySet.update() esegue un UPDATE SQL diretto a livello di database, bypassando completamente il metodo save() del modello e di conseguenza i segnali pre_save e post_save. Lo stesso vale per QuerySet.delete() rispetto a pre_delete e post_delete. Nei colloqui, questa distinzione rappresenta una delle domande trabocchetto più frequenti su Django.
I candidati nei colloqui tecnici Django cadono frequentemente su questi aspetti legati a middleware e segnali:
- Ordine del middleware invertito: posizionare
AuthenticationMiddlewareprima diSessionMiddlewareimpedisce il funzionamento dell'autenticazione basata su sessione - Segnali non registrati: definire i receiver senza importare il modulo in
AppConfig.ready()produce handler che non vengono mai eseguiti - Confusione tra
createdeupdate: non verificare il parametrocreatedinpost_savecausa tentativi di creazione duplicata ad ogni salvataggio - Dipendenze circolari nei segnali: un segnale
post_saveche invoca.save()sullo stesso modello genera una ricorsione infinita - Segnali sincroni in contesti asincroni: in Django 5.2, l'invocazione di
Signal.send()in una view asincrona può causare blocchi; occorre utilizzareSignal.asend()per la compatibilità asincrona - Eccezioni non gestite nei receiver: un'eccezione in un receiver
post_saveviene propagata al codice chiamante, potenzialmente interrompendo il flusso dell'applicazione;Signal.send_robust()cattura le eccezioni e le restituisce come risultato
Django 5.2, rilasciato ad aprile 2025, è una versione Long Term Support con supporto garantito fino ad aprile 2028. Le principali novità includono il supporto completo ai middleware e segnali asincroni (asend, asend_robust), il composite primary key, le migrazioni automatiche e i miglioramenti alle performance del query compiler. Per la preparazione ai colloqui tecnici, concentrarsi sulle funzionalità LTS garantisce di studiare una versione con rilevanza a lungo termine.
Middleware vs Segnali: tabella comparativa
La scelta tra middleware e segnali dipende dal livello di astrazione e dal tipo di evento da gestire. La tabella seguente sintetizza le differenze fondamentali.
| Aspetto | Middleware | Signals | |---|---|---| | Modifica request/response | Sì | No | | Autenticazione e autorizzazione | Sì | No | | Hook del ciclo di vita del modello | No | Sì | | Aspetti trasversali (logging, timing) | Sì | Possibile ma non ideale | | Logica di business disaccoppiata | No | Sì | | Supporto async in Django 5.2 | Completo | Completo (asend/asend_robust) | | Ambito di esecuzione | Ogni richiesta HTTP | Eventi specifici (save, delete, custom) |
La regola pratica: il middleware gestisce aspetti trasversali a livello di richiesta HTTP (sicurezza, logging, autenticazione, compressione), mentre i segnali gestiscono reazioni a eventi interni del framework (creazione di record, modifica di stato, eventi di dominio). Utilizzare un middleware per reagire al salvataggio di un modello, o un segnale per modificare una risposta HTTP, rappresenta un antipattern architetturale.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Conclusione
Il middleware e i segnali costituiscono due pilastri architetturali di Django che operano a livelli distinti ma complementari. I punti chiave per i colloqui tecnici si possono riassumere come segue:
- Il middleware opera a livello di richiesta HTTP seguendo il pattern a cipolla: la richiesta attraversa lo stack dall'alto verso il basso, la risposta dal basso verso l'alto
- L'ordine di dichiarazione nella lista
MIDDLEWAREdetermina l'ordine di esecuzione e può causare errori sottili se non rispettato - Django 5.2 introduce il supporto completo ai middleware asincroni tramite gli attributi
async_capableesync_capable - I segnali
pre_saveepost_saveoperano a livello di singola istanza del modello, non su operazioni bulk comeQuerySet.update() - La registrazione dei segnali nel metodo
AppConfig.ready()è obbligatoria per garantire il collegamento dei receiver all'avvio dell'applicazione - I segnali personalizzati disaccoppiano efficacemente la logica di business seguendo il principio di inversione delle dipendenze
- In Django 5.2,
Signal.asend()eSignal.asend_robust()consentono l'invio asincrono dei segnali, evitando blocchi nelle view asincrone - Il middleware gestisce aspetti trasversali a livello HTTP, i segnali reagiscono a eventi interni: confondere i due ambiti rappresenta un antipattern
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

Domande Colloquio Django: ORM, Middleware e DRF in Profondità
Guida completa alle domande colloquio Django 2026: ottimizzazione ORM, architettura middleware e pattern Django REST Framework con esempi di codice.

Domande per colloqui Django e Python: Le Top 25 nel 2026
Le 25 domande piu comuni nei colloqui su Django e Python. ORM, view, middleware, DRF, signal e ottimizzazione con risposte dettagliate ed esempi di codice.

Django ORM: ottimizzare le query per prestazioni massime
Guida completa all'ottimizzazione delle query con il Django ORM. select_related, prefetch_related, indici, analisi del problema N+1 e tecniche avanzate per applicazioni ad alte prestazioni.