# 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. - Published: 2026-04-29 - Updated: 2026-04-29 - Author: SharpSkill - Tags: django, python, middleware, signals, interview - Reading time: 9 min --- 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. > **Riferimento rapido per il colloquio** > > 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. ```python # 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 response ``` Il 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`. ```python # 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 continue ``` L'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. ```python # 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`. ```python # 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 response ``` La 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. ## 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`. ```python # 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. ```python # 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 = slug ``` La 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. ```python # 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: F401 ``` Il 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. ```python # 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 > **QuerySet.update() non attiva i segnali** > > 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 `AuthenticationMiddleware` prima di `SessionMiddleware` impedisce 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 `created` e `update`:** non verificare il parametro `created` in `post_save` causa tentativi di creazione duplicata ad ogni salvataggio - **Dipendenze circolari nei segnali:** un segnale `post_save` che 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 utilizzare `Signal.asend()` per la compatibilità asincrona - **Eccezioni non gestite nei receiver:** un'eccezione in un receiver `post_save` viene propagata al codice chiamante, potenzialmente interrompendo il flusso dell'applicazione; `Signal.send_robust()` cattura le eccezioni e le restituisce come risultato > **Django 5.2 LTS** > > 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. ## 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 `MIDDLEWARE` determina 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_capable` e `sync_capable` - I segnali `pre_save` e `post_save` operano a livello di singola istanza del modello, non su operazioni bulk come `QuerySet.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()` e `Signal.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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/django/django-5-2-custom-middleware-signals