Django 5.2: Wlasne Middleware i Obsluga Sygnalow -- Przygotowanie do Rozmow Rekrutacyjnych
Praktyczny przewodnik po tworzeniu wlasnych middleware i obsludze sygnalow w Django 5.2. Struktura middleware, asynchroniczne middleware, sygnaly post_save i pre_save, wlasne sygnaly domenowe oraz najczesciej zadawane pytania rekrutacyjne.

Django 5.2 LTS wprowadza szereg usprawnien w zakresie asynchronicznego przetwarzania i obslugi zdarzen, ktore bezposrednio wplywaja na sposob projektowania warstwy middleware oraz implementacji sygnalow. Middleware i sygnaly stanowia dwa fundamentalne mechanizmy frameworka, ktore pojawiaja sie na niemal kazdej rozmowie kwalifikacyjnej dotyczacej Django. Middleware odpowiada za przetwarzanie requestow i response'ow na poziomie globalnym, natomiast sygnaly umozliwiaja luznie powiazanym komponentom aplikacji reagowanie na zdarzenia wewnetrzne. Niniejszy artykul omawia oba mechanizmy od podstaw, prezentuje produkcyjne przyklady kodu i przygotowuje do pytan rekrutacyjnych z zakresu Django.
Middleware przechwytuje kazdy request HTTP przechodzacy przez aplikacje -- idealny do logowania, uwierzytelniania, modyfikacji naglowkow. Wykonuje sie w scisle okreslonej kolejnosci zdefiniowanej w MIDDLEWARE.
Sygnaly reaguja na zdarzenia wewnetrzne modelu (zapis, usuwanie) lub zdarzenia domenowe -- idealny do efektow ubocznych takich jak wysylka emaili, tworzenie powiazanych obiektow, walidacja danych. Dzialaja w modelu publish-subscribe.
Jak dziala middleware w Django
Middleware w Django to klasa implementujaca wzorzec dekoratora wokol cyklu request/response. Kazda klasa middleware otrzymuje callable get_response, ktory reprezentuje nastepna warstwe w lancuchu -- moze to byc kolejne middleware lub sam widok. Wywolanie get_response(request) przekazuje request w dol lancucha i zwraca obiekt response.
Podstawowa struktura middleware wyglada nastepujaco:
# 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 responseMetoda __init__ jest wywolywana jednokrotnie przy starcie serwera. To odpowiednie miejsce na inicjalizacje zasobow, ktore maja byc wspoldzielone miedzy requestami -- na przyklad konfiguracja loggera, ladowanie ustawien z bazy czy inicjalizacja klienta cache. Metoda __call__ jest wywolywana przy kazdym requescie i to tutaj znajduje sie wlasciwa logika middleware.
Warto zwrocic uwage na symetryczna nature tego wzorca: kod przed wywolaniem get_response wykonuje sie podczas fazy request (w dol lancucha), a kod po wywolaniu -- podczas fazy response (w gore lancucha). Ta dwukierunkowa architektura umozliwia middleware jednoczesne modyfikowanie zarowno requestu, jak i response'u.
Budowanie middleware do logowania requestow
Jednym z najczestszych zastosowan wlasnego middleware jest logowanie requestow HTTP. Ponizszy przyklad rejestruje metode, sciezke, kod statusu, czas trwania i adres IP kazdego requestu. Dodatkowo implementuje metode process_exception, ktora przechwytuje wyjatki rzucone przez widoki:
# 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 continueUzycie time.monotonic() zamiast time.time() jest swiadoma decyzja -- zegar monotoniczny nie podlega korektom systemowym (np. synchronizacji NTP), co gwarantuje dokladny pomiar czasu trwania requestu nawet w przypadku korekty zegara systemowego.
Metoda process_exception jest jednym z hookow middleware Django. Zwracanie None oznacza, ze middleware nie obsluguje wyjatku samodzielnie i przekazuje go do domyslnego mechanizmu obslugi bledow Django. Gdyby metoda zwrocila obiekt HttpResponse, Django uzylby tego response'u zamiast standardowej strony bledu.
Kolejnosc middleware i ustawienie MIDDLEWARE
Kolejnosc klas w liscie MIDDLEWARE ma kluczowe znaczenie. Django przetwarza middleware od gory do dolu podczas fazy request i od dolu do gory podczas fazy response. Oznacza to, ze middleware umieszczone wyzej na liscie "widzi" request jako pierwsze, ale response jako ostatnie.
# 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',
]Umieszczenie RequestLoggingMiddleware na koncu listy oznacza, ze podczas fazy request bedzie ono wykonywane jako ostatnie (tuz przed widokiem), a podczas fazy response -- jako pierwsze (zaraz po widoku). Dzieki temu pomiar czasu obejmuje wylacznie czas przetwarzania widoku, bez narzutu pozostalych warstw middleware.
Gdyby natomiast middleware logujace zostalo umieszczone na poczatku listy (przed SecurityMiddleware), mierzyloby calkowity czas przetwarzania lancucha, wlacznie z nakladem wszystkich pozostalych middleware. Wybor pozycji zalezy od tego, co dokladnie ma byc mierzone.
Typowym bledem rekrutacyjnym jest umieszczenie wlasnego middleware przed AuthenticationMiddleware, gdy logika middleware wymaga dostepu do request.user. W takiej konfiguracji obiekt uzytkownika nie bedzie jeszcze dostepny.
Asynchroniczne middleware w Django 5.2
Django 5.2 rozszerza wsparcie dla asynchronicznego przetwarzania middleware. Atrybuty async_capable i sync_capable informuja framework, czy middleware moze dzialac w kontekscie asynchronicznym. Ustawienie sync_capable = False wymusza, aby Django wywolywalo middleware wylacznie w trybie async:
# 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 responseW kontekscie asynchronicznym get_response jest obiektem awaitable, dlatego nalezy uzyc slowa kluczowego await. Middleware dodaje naglowek X-Request-Duration do kazdego response'u, co jest przydatne do monitorowania wydajnosci po stronie klienta lub systemow APM.
Nalezy pamietac, ze pelne korzysci z asynchronicznego middleware uzyskuje sie dopiero w polaczeniu z asynchronicznymi widokami i serwerem ASGI (np. Daphne, Uvicorn). W srodowisku WSGI Django automatycznie opakowuje asynchroniczne middleware w synchroniczny wrapper, co neguje potencjalne zyski wydajnosciowe.
Gotowy na rozmowy o Django?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Sygnaly Django -- wzorzec publish-subscribe
Sygnaly w Django implementuja wzorzec publish-subscribe (obserwator), umozliwiajac luznie powiazanym komponentom aplikacji reagowanie na okreslone zdarzenia. Framework dostarcza zestaw wbudowanych sygnalow (pre_save, post_save, pre_delete, post_delete, m2m_changed, request_started, request_finished) oraz pozwala definiowac wlasne sygnaly domenowe.
Glowna zaleta sygnalow jest decouplowanie -- nadawca sygnalu nie musi wiedziec, kto na niego nasluchuje. Dzieki temu mozna dodawac nowe reakcje na zdarzenia bez modyfikacji istniejacego kodu. To szczegolnie wartosciowe w duzych projektach, gdzie rozne aplikacje Django musza reagowac na te same zdarzenia.
post_save do automatycznego tworzenia profilu
Jednym z najczestszych zastosowan sygnalu post_save jest automatyczne tworzenie powiazanych obiektow po zapisaniu modelu. Klasyczny przyklad to tworzenie profilu uzytkownika zaraz po rejestracji nowego konta:
# 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,
)Dekorator @receiver rejestruje funkcje jako odbiorce sygnalu post_save wysylanego przez model User. Parametr created pozwala odroznic tworzenie nowego rekordu (INSERT) od aktualizacji istniejacego (UPDATE). Bez tego sprawdzenia profil bylby tworzony przy kazdym zapisie uzytkownika, co prowadziloby do bledu IntegrityError z powodu naruszenia ograniczenia unikalnosci.
Parametr **kwargs jest obowiazkowy w sygnaturze odbiorcy. Django moze przekazywac dodatkowe argumenty w przyszlych wersjach, a ich brak w sygnaturze spowodowalby blad w runtime.
pre_save do walidacji i transformacji danych
Sygnal pre_save jest wysylany tuz przed zapisaniem obiektu do bazy danych, co czyni go idealnym miejscem na walidacje i automatyczne transformacje. Ponizszy przyklad automatycznie generuje unikalne sluga na podstawie tytulu artykulu:
# 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 = slugLogika generowania sluga sprawdza unikalnosc w petli, dodajac sufiks numeryczny w przypadku kolizji. Uzycie exclude(pk=instance.pk) zapobiega falszywie pozytywnym kolizjom podczas aktualizacji istniejacego artykulu -- bez tego wykluczenia obiekt zgloszowalby kolizje sam ze soba.
Warto podkreslic, ze modyfikacja instance.slug w sygnale pre_save jest skuteczna, poniewaz Django uzywa zmienionego obiektu do wykonania zapytania SQL. To rozni pre_save od post_save, gdzie modyfikacja instancji wymaga dodatkowego wywolania save(), co prowadzi do niebezpiecznej rekursji.
Rejestracja sygnalow w AppConfig.ready()
Same definicje odbiorcow sygnalow nie wystarczaja -- musza byc zaimportowane, aby Python je zarejestrowal. Zalecanym miejscem na import modulu sygnalow jest metoda ready() klasy AppConfig:
# 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: F401Metoda ready() jest wywolywana jednokrotnie po zaladowaniu wszystkich aplikacji Django. Import modulu sygnalow w tym miejscu gwarantuje, ze odbiory zostana zarejestrowane dokladnie raz, niezaleznie od liczby workerow czy watkow.
Komentarz # noqa: F401 tlumaczy linterowi, ze import bez uzycia jest celowy -- modul importowany jest wylacznie w celu wywolania efektow ubocznych (rejestracji dekoratorow @receiver). Pominiecnie tego importu jest jednym z najczestszych bledow poczatkujacych -- sygnaly sa poprawnie zdefiniowane, ale nigdy nie zostaja zarejestrowane.
Budowanie wlasnych sygnalow dla zdarzen domenowych
Oproc wbudowanych sygnalow modelowych Django pozwala definiowac wlasne sygnaly reprezentujace zdarzenia biznesowe. Jest to szczegolnie przydatne w architekturze opartej na zdarzeniach (event-driven), gdzie rozne podsystemy musza reagowac na te same zdarzenia domenowe bez bezposredniego powiazania:
# 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},
)W tym wzorcu modul orders nie importuje niczego z modulu notifications. Jedyna zaleznosc biegnie w przeciwnym kierunku: notifications importuje sygnal z orders. Dzieki temu mozna dodawac nowe reakcje na zdarzenie order_completed (np. aktualizacja statystyk, powiadomienie magazynu, rejestracja w systemie analitycznym) bez jakiejkolwiek modyfikacji logiki zamowien.
Metoda send() jest synchroniczna i wywoluje wszystkich odbiorcow sekwencyjnie w tym samym watku. Dla operacji czasochlonnych (np. wysylka emaili) zaleca sie uzycie kolejki zadan (Celery, Django-Q2) wewnatrz odbiorcy, aby nie blokowac glownego cyklu request/response.
Najczestsze pulapki rekrutacyjne
Podczas rozmow kwalifikacyjnych dotyczacych Django middleware i sygnalow regularnie pojawiaja sie pytania o subtelne zachowania, ktore prowadza do trudnych do zdiagnozowania bledow produkcyjnych.
Metody QuerySet.update() i QuerySet.delete() operuja bezposrednio na poziomie SQL i nie wywoluja sygnalow pre_save/post_save ani pre_delete/post_delete. Takze operacje bulk_create() domyslnie pomijaja sygnaly. Jesli logika biznesowa opiera sie na sygnalach, operacje masowe moga prowadzic do niespojnosci danych. Rozwiazaniem jest jawne iterowanie po obiektach i wywolywanie save() lub delete() na kazdym z nich, badz manualne emitowanie sygnalow po operacji masowej.
Django 5.2 jest wersja LTS (Long-Term Support), co oznacza, ze bedzie otrzymywac poprawki bezpieczenstwa do kwietnia 2028 roku. To sprawia, ze jest to preferowana wersja do projektow produkcyjnych. Wsparcie dla asynchronicznego middleware i widokow zostalo znaczaco rozszerzone w tej wersji, a stabilnosc API sygnalow pozostaje niezmieniona od wielu lat.
Kolejne czeste pytanie dotyczy problemu rekursji w sygnalach. Jesli odbiorca post_save wywoluje save() na tym samym obiekcie, prowadzi to do nieskonczonej petli. Standardowym rozwiazaniem jest uzycie update() na QuerySet zamiast save() na instancji, lub zastosowanie flagi ochronnej na obiekcie (instance._skip_signal = True) sprawdzanej na poczatku odbiorcy.
Warto rowniez wiedziec, ze sygnaly Django sa synchroniczne i wykonywane w ramach tej samej transakcji bazodanowej co operacja, ktora je wywolala. Oznacza to, ze blad w odbiorsygnalu moze cofnac cala transakcje. Jesli efekt uboczny nie powinien wplywac na powodzenie glownej operacji, nalezy uzyc transaction.on_commit() do odlozenia wykonania lub obsluzyc wyjatki wewnatrz odbiorcy.
Porownanie Middleware i Sygnalow
| Aspekt | Middleware | Sygnaly |
|---|---|---|
| Zakres dzialania | Request/Response HTTP | Zdarzenia wewnetrzne (model, request, domenowe) |
| Moment wykonania | Przy kazdym requescie HTTP | Przy zapisie, usuwaniu, lub zdarzeniu niestandardowym |
| Kolejnosc | Scisle zdefiniowana w MIDDLEWARE | Nieokreslona (nie nalezy polegac na kolejnosci) |
| Konfiguracja | settings.py lista MIDDLEWARE | Dekorator @receiver + import w AppConfig.ready() |
| Asynchronicznosc | async_capable = True od Django 5.2 | Synchroniczne (uzywac Celery dla zadan async) |
| Typowe zastosowania | Logowanie, CORS, bezpieczenstwo, cache | Tworzenie powiazanych obiektow, walidacja, powiadomienia |
| Testowanie | Testy integracyjne HTTP | Testy jednostkowe z Signal.send() |
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Podsumowanie
Middleware i sygnaly stanowia dwa komplementarne mechanizmy rozszerzania zachowania aplikacji Django. Kluczowe wnioski do zapamietania przed rozmowa rekrutacyjna:
- Middleware przetwarza kazdy request HTTP w scisle okreslonej kolejnosci -- kod przed
get_responsewykonuje sie w fazie request, kod po nim w fazie response - Kolejnosc w liscie MIDDLEWARE determinuje zachowanie aplikacji -- wlasne middleware powinno byc umieszczone po wbudowanych warstwach Django, od ktorych zalezy
- Asynchroniczne middleware w Django 5.2 wymaga atrybutow
async_capable = Truei serwera ASGI do pelnego wykorzystania - Sygnaly
pre_saveipost_saveumozliwiaja automatyczna walidacje, transformacje i tworzenie powiazanych obiektow bez modyfikacji logiki widokow - Rejestracja sygnalow w
AppConfig.ready()jest obowiazkowa -- bez importu modulu sygnalow odbiory nigdy nie zostana aktywowane - Operacje masowe (
QuerySet.update(),bulk_create()) omijaja sygnaly -- jest to najczestsza przyczyna niespojnosci danych w aplikacjach produkcyjnych - Wlasne sygnaly domenowe pozwalaja budowac architekture event-driven z luznie powiazanymi komponentami
- Rekursja w
post_saveto klasyczna pulapka -- nalezy unikac wywolywaniasave()na tym samym obiekcie wewnatrz odbiorcy tego sygnalu
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Pytania rekrutacyjne Django: ORM, Middleware i DRF -- szczegółowa analiza
Pytania rekrutacyjne Django obejmujące optymalizację ORM z select_related i prefetch_related, architekturę middleware oraz wydajność serializerów Django REST Framework, uprawnienia i paginację.

Pytania rekrutacyjne Django i Python: Top 25 w 2026
25 najczesciej zadawanych pytan rekrutacyjnych z Django i Pythona. ORM, widoki, middleware, DRF, sygnaly i optymalizacja z szczegolowymi odpowiedziami i przykladami kodu.

Django ORM: optymalizacja zapytań dla maksymalnej wydajności
Kompletny przewodnik po optymalizacji zapytań Django ORM. select_related, prefetch_related, indeksy, analiza problemu N+1 oraz zaawansowane techniki dla aplikacji o wysokiej wydajności.