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

Питання на співбесіді з Django залишаються одним із найскладніших етапів відбору Python-розробників у 2026 році. Три ключові напрямки, що відрізняють досвідчених кандидатів від решти -- оптимізація ORM, архітектура middleware та паттерни проєктування в Django REST Framework (DRF). Цей матеріал розбирає конкретні запитання, які ставлять технічні інтерв'юери, з прикладами production-рівня на базі Django 5.2 LTS та DRF 3.17.
Сучасні Django-співбесіди рідко зводяться до базового CRUD. Фокус змістився на оптимізацію QuerySet (select_related проти prefetch_related), розробку кастомного middleware та продуктивність DRF-серіалізаторів. Кандидати, здатні пояснити проблему N+1 запитів та написати ефективні ViewSet, стабільно випереджають тих, хто знає лише generic views.
Оптимізація Django ORM: select_related та prefetch_related
Найпоширеніше запитання з Django ORM на технічних співбесідах стосується проблеми N+1 запитів. За наявності моделі зі зв'язками типу ForeignKey та ManyToMany кандидат повинен продемонструвати, коли застосовувати select_related, а коли -- prefetch_related.
# models.py
from django.db import models
class Company(models.Model):
name = models.CharField(max_length=200)
founded_year = models.IntegerField()
class Developer(models.Model):
name = models.CharField(max_length=200)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="developers")
skills = models.ManyToManyField("Skill", related_name="developers")
class Skill(models.Model):
name = models.CharField(max_length=100)
category = models.CharField(max_length=50)Різниця між цими двома методами визначається типом зв'язку, який потрібно завантажити.
# queries.py — Correct approach for ForeignKey (single object)
developers = Developer.objects.select_related("company").all()
# Generates ONE SQL query with JOIN
# SELECT developer.*, company.* FROM developer INNER JOIN company ...
# Correct approach for ManyToMany (multiple objects)
developers = Developer.objects.prefetch_related("skills").all()
# Generates TWO SQL queries:
# 1. SELECT * FROM developer
# 2. SELECT * FROM skill INNER JOIN developer_skills WHERE developer_id IN (...)select_related виконує SQL JOIN і працює з ForeignKey та OneToOneField -- результат потрапляє в один SQL-запит. prefetch_related виконує окремий запит і підходить для ManyToManyField та зворотних ForeignKey-зв'язків. Плутанина між ними призводить або до надлишкових JOIN на великих наборах даних, або до класичної проблеми N+1.
Кастомні менеджери та ланцюжкові QuerySet-запити
Інтерв'юери часто просять написати кастомний менеджер, що інкапсулює бізнес-логіку. Мета -- переконатися, що кандидат розуміє паттерн Manager/QuerySet в Django та вміє створювати повторно використовувані інтерфейси запитів.
# managers.py
from django.db import models
from django.utils import timezone
class ActiveDeveloperQuerySet(models.QuerySet):
def active(self):
"""Filter developers who logged in within the last 30 days."""
cutoff = timezone.now() - timezone.timedelta(days=30)
return self.filter(last_login__gte=cutoff)
def senior(self):
"""Filter developers with 5+ years of experience."""
return self.filter(years_experience__gte=5)
def by_skill(self, skill_name):
"""Filter developers by a specific skill."""
return self.filter(skills__name=skill_name)
class ActiveDeveloperManager(models.Manager):
def get_queryset(self):
return ActiveDeveloperQuerySet(self.model, using=self._db)
def active(self):
return self.get_queryset().active()Типове уточнювальне запитання: "Чому варто використовувати кастомний QuerySet замість простого Manager?" Відповідь -- можливість ланцюжкового виклику (chaining). Методи кастомного QuerySet можна поєднувати в ланцюжки, тоді як методи Manager не підтримують ланцюжкування після першого виклику.
# usage.py — QuerySet chaining in action
# This works because each method returns a QuerySet
senior_python_devs = (
Developer.active_objects # custom manager
.active() # ActiveDeveloperQuerySet method
.senior() # chains another QuerySet method
.by_skill("Python") # chains a third method
.select_related("company") # standard QuerySet method still works
)Django 5.2 запровадив CompositePrimaryKey -- довгоочікувану можливість для моделей, яким потрібні багатоколонкові первинні ключі. Запитання про цю функцію на співбесідах стають дедалі частішими, особливо для кандидатів, що працюють з legacy-базами даних або схемами сховищ даних (data warehouse).
Middleware-конвеєр Django: обробка запитів та відповідей
Запитання про middleware перевіряють розуміння кандидатом життєвого циклу запит-відповідь у Django. Стандартне запитання: "Поясніть порядок, у якому middleware обробляє запит та відповідь."
Відповідь має чітку структуру. Під час обробки запиту middleware-класи виконуються зверху вниз, у порядку визначення в MIDDLEWARE. Під час обробки відповіді -- знизу вверх. Ця багатошарова архітектура (onion-layer) означає, що перший middleware у списку огортає все інше.
# middleware.py
import time
import logging
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""Logs the time taken to process each request."""
def __init__(self, get_response):
self.get_response = get_response # next middleware or view
def __call__(self, request):
start_time = time.monotonic()
response = self.get_response(request) # passes to next layer
duration_ms = (time.monotonic() - start_time) * 1000
logger.info(
"method=%s path=%s status=%d duration=%.2fms",
request.method,
request.path,
response.status_code,
duration_ms,
)
return responseПоширене додаткове запитання: "Як перервати ланцюжок middleware?" Повернення HttpResponse з __call__ до виклику self.get_response(request) повністю зупиняє ланцюжок. Решта middleware та сама view-функція ніколи не виконуються.
# middleware.py — Rate limiting with short-circuit
from django.core.cache import cache
class RateLimitMiddleware:
"""Blocks requests exceeding 100 per minute per IP."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
ip = request.META.get("REMOTE_ADDR")
cache_key = f"rate_limit:{ip}"
request_count = cache.get(cache_key, 0)
if request_count >= 100:
return JsonResponse( # short-circuits — view never executes
{"error": "Rate limit exceeded. Try again in 60 seconds."},
status=429,
)
cache.set(cache_key, request_count + 1, timeout=60)
return self.get_response(request)Хуки middleware: process_view, process_exception та process_template_response
Окрім __call__, middleware в Django підтримує три необов'язкові методи-хуки. Інтерв'юери використовують їх для оцінки глибини знань кандидата.
# middleware.py — Full middleware with all hooks
class AuditMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
"""Called after URL resolution, before the view executes."""
logger.info("Calling view: %s", view_func.__name__)
return None # returning None continues normal processing
def process_exception(self, request, exception):
"""Called only if the view raises an exception."""
logger.error("View exception: %s", exception, exc_info=True)
return None # returning None lets Django's default handling proceed
def process_template_response(self, request, response):
"""Called if the response has a render() method (TemplateResponse)."""
response.context_data["audit_timestamp"] = time.time()
return response # must return a response with render()process_view спрацьовує після розв'язання URL, але до виконання view. Повернення None продовжує виконання; повернення HttpResponse перериває ланцюжок. process_exception спрацьовує лише при необроблених винятках у view. process_template_response викликається лише для об'єктів TemplateResponse, а не для звичайних HttpResponse.
Готовий до співбесід з Django?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
DRF-серіалізатори: вкладена серіалізація та продуктивність
Запитання про серіалізатори Django REST Framework зосереджені на вкладеній серіалізації та її впливі на продуктивність. Найчастіше запитання: "Як обробляти вкладені зв'язки, не створюючи N+1 запитів?"
# serializers.py
from rest_framework import serializers
from .models import Developer, Company, Skill
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ["name", "category"]
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ["name", "founded_year"]
class DeveloperSerializer(serializers.ModelSerializer):
company = CompanySerializer(read_only=True) # nested FK
skills = SkillSerializer(many=True, read_only=True) # nested M2M
class Meta:
model = Developer
fields = ["id", "name", "company", "skills"]Сам по собі серіалізатор не вирішує проблему продуктивності. ViewSet повинен оптимізувати queryset.
# views.py
from rest_framework import viewsets
from .models import Developer
from .serializers import DeveloperSerializer
class DeveloperViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = DeveloperSerializer
def get_queryset(self):
return (
Developer.objects
.select_related("company") # JOIN for FK
.prefetch_related("skills") # separate query for M2M
.order_by("-id")
)Без select_related та prefetch_related у ViewSet кожен серіалізований об'єкт Developer спричинює окремі запити для company та skills. На списку з 50 розробників це означає 1 + 50 + 50 = 101 запит замість 3.
Кастомні дозволи та автентифікація в DRF
Типове запитання з DRF на співбесіді: "Напишіть кастомний permission, який обмежує доступ на основі ролі користувача та власника об'єкта."
# permissions.py
from rest_framework.permissions import BasePermission
class IsOwnerOrAdmin(BasePermission):
"""
Object-level permission:
- Admin users can access any object
- Regular users can only access objects they own
"""
message = "Access restricted to the object owner or admin users."
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
# Assumes the model has an 'owner' field pointing to User
return obj.owner == request.user# views.py — Applying custom permissions
from rest_framework import viewsets, permissions
from .permissions import IsOwnerOrAdmin
class ProjectViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, IsOwnerOrAdmin]
def get_queryset(self):
# Non-admin users only see their own projects
if self.request.user.is_staff:
return Project.objects.all()
return Project.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user) # auto-assign ownerВажливий нюанс, на який звертають увагу інтерв'юери: has_permission виконується при кожному запиті (рівень списку), тоді як has_object_permission -- лише при виклику get_object() (рівень деталей). Якщо не перевизначити get_queryset для list-ендпоінту, виникає прогалина в безпеці: користувачі зможуть бачити об'єкти, що їм не належать.
Покладатися виключно на has_object_permission без фільтрації queryset означає залишити list-ендпоінт без захисту. Завжди слід поєднувати object-level permissions із відфільтрованим get_queryset, щоб забезпечити контроль доступу на обох рівнях -- і для списку, і для деталей.
Троттлінг та пагінація в DRF
Троттлінг і пагінація -- стандартні додаткові запитання на Django-співбесідах. Інтерв'юери хочуть переконатися, що кандидат розуміє production-конфігурацію API.
# settings.py — Production DRF configuration
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "20/minute", # unauthenticated users
"user": "200/minute", # authenticated users
},
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination",
"PAGE_SIZE": 25,
}# pagination.py — Custom cursor pagination for consistent ordering
from rest_framework.pagination import CursorPagination
class CreatedAtCursorPagination(CursorPagination):
page_size = 25
ordering = "-created_at" # must be a unique, sequential field
cursor_query_param = "cursor" # ?cursor=abc123Cursor-пагінація перевершує offset-пагінацію на великих наборах даних, оскільки не потребує підрахунку загальної кількості рядків. Компроміс: клієнт не може перейти на довільну сторінку. Саме таку відповідь очікують інтерв'юери на запитання "Чому ви обрали б cursor-пагінацію замість пагінації за номером сторінки?"
Висновок
Ключові тези для підготовки до Django-співбесіди:
- Оптимізація ORM: метод запиту має відповідати типу зв'язку.
select_related-- для ForeignKey/OneToOne,prefetch_related-- для ManyToMany та зворотних FK. Кастомні QuerySet забезпечують ланцюжкову, повторно використовувану бізнес-логіку. - Архітектура middleware: запит проходить зверху вниз, відповідь -- знизу вверх. Переривання ланцюжка через повернення відповіді до
get_response()-- фундаментальний паттерн для rate limiting, перевірки автентифікації та валідації запитів. - Продуктивність DRF-серіалізаторів: вкладені серіалізатори вимагають явної оптимізації queryset у ViewSet. Без
select_related/prefetch_relatedсеріалізація спричинює N+1 запити на масштабі. - Дозволи DRF:
has_object_permissionнеобхідно поєднувати з відфільтрованимget_querysetдля контролю доступу на обох рівнях -- list та detail. Object-level permissions самі по собі залишають list-ендпоінт без захисту. - Троттлінг та пагінація: cursor-пагінація масштабується краще за offset на великих таблицях. Ліміти запитів слід конфігурувати окремо для анонімних та автентифікованих користувачів.
Для практичного закріплення цих паттернів варто пройти запитання зі співбесід з Django ORM та запитання з Django middleware на SharpSkill.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

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

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

Django та Celery: асинхронна обробка завдань і питання для співбесід 2026
Повний гайд з інтеграції Django та Celery: налаштування, маршрутизація завдань, Celery Beat, Flower, продакшн-конфігурація та питання для співбесід 2026.