# 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. - Published: 2026-04-29 - Updated: 2026-04-29 - Author: SharpSkill - Tags: django, python, middleware, signals, interview - Reading time: 8 min --- 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](/pl/technologies/django). > **Scigawka rekrutacyjna: Middleware vs Sygnaly** > > **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: ```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 ``` Metoda `__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: ```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 ``` Uzycie `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. ```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', ] ``` 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: ```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 ``` W 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. ## 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: ```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, ) ``` 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: ```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 ``` Logika 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`: ```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 ``` Metoda `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: ```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}, ) ``` 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. > **QuerySet.update() omija sygnaly** > > 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 LTS -- wsparcie dlugookresowe** > > 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()` | ## 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_response` wykonuje 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 = True` i serwera ASGI do pelnego wykorzystania - **Sygnaly `pre_save` i `post_save`** umozliwiaja 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_save`** to klasyczna pulapka -- nalezy unikac wywolywania `save()` na tym samym obiekcie wewnatrz odbiorcy tego sygnalu --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/django/django-5-2-custom-middleware-signals