Django 5.2: Власна Middleware та Обробка Сигналів для Технічних Співбесід
Повний посібник з Django 5.2: побудова власної middleware, використання сигналів post_save та pre_save, асинхронна middleware, користувацькі сигнали та типові питання для технічних співбесід з прикладами коду.

Django 5.2 LTS надає розробникам зрілий та перевірений набір інструментів для побудови веб-застосунків промислового рівня. Серед ключових архітектурних компонентів фреймворку два механізми заслуговують на особливу увагу при підготовці до технічних співбесід: middleware та сигнали. Middleware контролює потік HTTP-запитів та відповідей, тоді як сигнали забезпечують слабкозв'язану комунікацію між компонентами застосунку. Глибоке розуміння обох механізмів є обов'язковою компетенцією для Django-розробників будь-якого рівня.
Middleware обробляє кожен HTTP-запит до та після представлення (view). Сигнали реагують на внутрішні події моделей (збереження, видалення). Middleware працює на рівні HTTP, сигнали — на рівні ORM. Обидва механізми підтримують розширення без зміни базового коду.
Як працює middleware у Django
Middleware у Django реалізується як клас із двома обов'язковими методами: __init__ та __call__. Конструктор викликається один раз при запуску сервера та отримує посилання на наступний обробник у ланцюжку. Метод __call__ викликається при кожному запиті та відповідає за передачу управління далі по ланцюжку.
Ланцюжок middleware працює за принципом вкладеності. Кожна middleware огортає наступну, формуючи структуру, подібну до матрьошки. Код перед викликом get_response виконується на шляху запиту до представлення, а код після виклику — на шляху відповіді назад до клієнта. Ця модель забезпечує повний контроль над обома фазами обробки HTTP-запиту.
# 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Метод __init__ приймає get_response — виклик наступної middleware або самого представлення, якщо це остання ланка ланцюжка. Ініціалізація відбувається лише один раз при старті сервера, тому тут доцільно розміщувати конфігурацію, ініціалізацію логерів чи підключення до зовнішніх сервісів.
Побудова middleware для логування запитів
Практичне застосування middleware демонструє клас для структурованого логування HTTP-запитів. Кожен запит отримує мітку часу на вході, а на виході фіксується тривалість обробки, HTTP-метод, шлях, статус відповіді та IP-адреса клієнта.
Особливу увагу варто звернути на метод process_exception. Django викликає його автоматично, коли представлення генерує необроблений виняток. Цей хук дозволяє зафіксувати помилку з повним контекстом запиту до того, як стандартний обробник Django перетворить виняток на HTTP-відповідь 500.
# 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Використання time.monotonic() замість time.time() є принциповим рішенням. Монотонний годинник не залежить від корекцій системного часу (NTP-синхронізація, переведення годинників), що гарантує коректне вимірювання інтервалів навіть на серверах із нестабільним системним часом.
Порядок middleware та налаштування MIDDLEWARE
Порядок middleware у списку MIDDLEWARE визначає послідовність обробки. Для запитів middleware виконуються зверху вниз, для відповідей — знизу вгору. Це означає, що middleware, оголошена першою, є зовнішнім шаром матрьошки: вона першою отримує запит і останньою обробляє відповідь.
Власну middleware рекомендовано розміщувати після стандартного стеку Django. SecurityMiddleware повинна залишатися першою для забезпечення HTTPS-перенаправлень та заголовків безпеки. AuthenticationMiddleware має передувати будь-якій middleware, що звертається до request.user.
# 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',
]Порушення порядку є поширеною причиною помилок на продакшні. Наприклад, розміщення CsrfViewMiddleware перед SessionMiddleware призведе до некоректної роботи CSRF-захисту, оскільки CSRF-токен зберігається у сесії. На технічних співбесідах кандидатів часто просять пояснити наслідки зміни порядку конкретних middleware.
Асинхронна middleware у Django 5.2
Django 5.2 повноцінно підтримує асинхронну middleware через атрибути async_capable та sync_capable. Позначення middleware як виключно асинхронної (sync_capable = False) гарантує, що Django використовуватиме асинхронний контекст виконання, що усуває накладні витрати на перемикання між синхронним та асинхронним режимами.
Асинхронна middleware є особливо ефективною для операцій, що включають введення-виведення: запити до зовнішніх API, перевірка кешу, взаємодія з Redis. У синхронному режимі такі операції блокують робочий потік, тоді як у асинхронному — потік повертається в пул для обробки інших запитів.
# 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Атрибут async_capable = True повідомляє Django, що middleware підтримує асинхронний виклик. sync_capable = False вказує, що middleware не може працювати у синхронному контексті. При такій конфігурації Django гарантовано виконуватиме __call__ як корутину, і get_response повертатиме awaitable об'єкт.
Готовий до співбесід з Django?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Сигнали Django: патерн publish-subscribe
Сигнали реалізують патерн publish-subscribe (видавець-підписник) у Django ORM. Коли модель зберігається, видаляється або ініціалізується, Django генерує відповідний сигнал, на який можуть підписатися довільні функції-обробники. Це дозволяє реагувати на події моделей без зміни коду самих моделей.
Вбудовані сигнали Django включають pre_save (перед збереженням), post_save (після збереження), pre_delete (перед видаленням), post_delete (після видалення) та m2m_changed (зміна зв'язків many-to-many). Кожен сигнал передає набір аргументів, специфічний для типу події: sender, instance, created (для post_save), using (база даних).
Ключова перевага сигналів полягає у розділенні відповідальності. Модель User не повинна знати про створення профілю, надсилання вітального листа чи ініціалізацію налаштувань. Кожна з цих дій оформлюється як окремий обробник сигналу у відповідному модулі застосунку.
post_save для автоматичного створення профілю
Сигнал post_save є одним з найчастіше використовуваних у Django-проєктах. Типовий приклад — автоматичне створення пов'язаного об'єкта при збереженні нового запису. Аргумент created дозволяє розрізнити операцію INSERT (створення нового запису) від UPDATE (оновлення існуючого).
Декоратор @receiver реєструє функцію як обробник конкретного сигналу від конкретного відправника. Параметр sender=User обмежує спрацювання лише збереженнями моделі 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,
)Важливо завжди приймати **kwargs у обробниках сигналів. Django може розширити набір аргументів у майбутніх версіях, і відсутність **kwargs призведе до помилки TypeError при оновленні фреймворку. Ця деталь часто перевіряється на технічних співбесідах як індикатор досвіду роботи з Django у продакшні.
pre_save для валідації та трансформації даних
Сигнал pre_save спрацьовує безпосередньо перед записом у базу даних, що робить його ідеальним для автоматичної трансформації даних: генерація слагів, нормалізація полів, обчислення похідних значень.
У наведеному прикладі обробник автоматично генерує URL-сумісний слаг з назви статті, якщо слаг не було встановлено вручну. Цикл while забезпечує унікальність слагу, додаючи числовий суфікс при виявленні конфлікту. Виключення поточного об'єкта через exclude(pk=instance.pk) запобігає хибному спрацюванню при оновленні існуючого запису.
# 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Модифікація instance у pre_save автоматично відображається у збереженому записі, оскільки Django використовує той самий об'єкт для запису у базу. Немає потреби викликати instance.save() — це спричинило б рекурсію, оскільки save() знову ініціює pre_save.
Реєстрація сигналів у AppConfig.ready()
Обробники сигналів повинні бути імпортовані для реєстрації у диспетчері Django. Рекомендований підхід — імпорт модуля сигналів у методі ready() класу AppConfig. Цей метод викликається один раз після повної ініціалізації застосунку.
Коментар # noqa: F401 вказує лінтеру ігнорувати попередження про невикористаний імпорт. Імпорт необхідний для побічного ефекту — реєстрації обробників через декоратори @receiver, а не для прямого використання модуля у коді.
# 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Поширеною помилкою є реєстрація сигналів безпосередньо у models.py або на верхньому рівні модуля. Це може спричинити проблеми з циклічними імпортами та подвійною реєстрацією обробників. Метод ready() гарантує, що імпорт відбувається у правильний момент життєвого циклу застосунку.
Побудова користувацьких сигналів для доменних подій
Вбудовані сигнали покривають події ORM, але бізнес-логіка часто вимагає власних подій. Django дозволяє створювати користувацькі сигнали через клас Signal. Це забезпечує слабке зв'язування між модулями: сервіс замовлень не залежить від сервісу сповіщень, а лише генерує подію, на яку підписуються зацікавлені обробники.
Такий підхід спрощує тестування (обробники можна відключити в тестах), розширення (нові реакції додаються без зміни базового коду) та підтримку (кожен обробник виконує одну функцію).
# 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},
)Метод send() є синхронним та виконує всі обробники послідовно у тому самому потоці. Для ресурсомістких операцій (надсилання email, виклик зовнішніх API) обробник повинен делегувати роботу черзі завдань (Celery, Django-Q) замість виконання безпосередньо у сигналі.
Типові помилки на технічних співбесідах
Досвідчені інтерв'юери часто перевіряють знання граничних випадків, а не лише загальне розуміння механізмів. Нижче наведено дві найпоширеніші пастки.
Методи QuerySet.update() та QuerySet.delete() виконують SQL-операції безпосередньо на рівні бази даних, обходячи ORM-методи save() та delete() окремих об'єктів. Як наслідок, сигнали pre_save, post_save, pre_delete та post_delete не генеруються. Це критично важливо для коректності бізнес-логіки, побудованої на сигналах.
Django 5.2 є версією з довгостроковою підтримкою (LTS), що гарантує оновлення безпеки та виправлення помилок до квітня 2028 року. Для продакшн-проєктів рекомендовано використовувати саме LTS-версії. Серед ключових покращень: повноцінна підтримка асинхронної middleware, покращена продуктивність ORM та розширені можливості міграцій.
Порівняння middleware та сигналів
| Характеристика | Middleware | Сигнали | |---|---|---| | Рівень роботи | HTTP-запит/відповідь | Внутрішні події ORM та застосунку | | Виклик | Кожен HTTP-запит | Подія моделі (save, delete) або користувацький сигнал | | Порядок виконання | Визначений списком MIDDLEWARE | Невизначений (залежить від порядку реєстрації) | | Асинхронна підтримка | async_capable / sync_capable | Лише синхронні обробники | | Доступ до request | Повний доступ | Відсутній (працює на рівні моделей) | | Типове використання | Логування, аутентифікація, CORS, кешування | Автоматичне створення пов'язаних об'єктів, аудит змін, нотифікації | | Вплив на продуктивність | Виконується при кожному запиті | Виконується лише при конкретних подіях |
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Підсумки
Middleware та сигнали є фундаментальними механізмами розширення Django, кожен з яких обслуговує чітко визначений архітектурний рівень. Основні висновки для підготовки до технічних співбесід:
- Middleware обробляє кожен HTTP-запит за принципом вкладеності: код перед
get_responseвиконується на шляху запиту, після — на шляху відповіді - Порядок middleware у
MIDDLEWAREє критичним:SecurityMiddlewareпершою,AuthenticationMiddlewareперед будь-якою middleware, що потребуєrequest.user - Django 5.2 підтримує повноцінну асинхронну middleware через атрибути
async_capableтаsync_capable - Сигнали
post_saveтаpre_saveдозволяють реагувати на збереження моделей без зміни коду моделі - Обробники сигналів реєструються у
AppConfig.ready()для уникнення циклічних імпортів та подвійної реєстрації QuerySet.update()таQuerySet.delete()обходять сигнали, оскільки працюють безпосередньо на рівні SQL- Користувацькі сигнали забезпечують слабке зв'язування між модулями застосунку за патерном publish-subscribe
- Middleware працює на рівні HTTP, сигнали — на рівні ORM; обидва механізми доповнюють один одного, але не замінюють
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

Питання на співбесіді з Django: ORM, Middleware та DRF -- поглиблений розбір
Питання на співбесіді з Django: оптимізація ORM з select_related та prefetch_related, архітектура middleware, продуктивність серіалізаторів Django REST Framework, дозволи та пагінація.

Питання на співбесіді з Django та Python: Топ 25 у 2026 році
25 найпоширеніших питань на співбесіді з Django та Python. ORM, представлення, middleware, DRF, сигнали та оптимізація з детальними відповідями та прикладами коду.

Django ORM: оптимізація запитів для максимальної продуктивності
Повний посібник з оптимізації запитів Django ORM. select_related, prefetch_related, індекси, аналіз проблеми N+1 та просунуті техніки для високопродуктивних застосунків.