# 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ę. - Published: 2026-04-03 - Updated: 2026-04-03 - Author: SharpSkill - Tags: django, python, interview, orm, middleware, drf, rest-api - Reading time: 9 min --- Pytania rekrutacyjne Django dotyczące ORM, middleware i Django REST Framework stanowią fundament każdej rozmowy kwalifikacyjnej na stanowisko Python-developera w 2026 roku. Niniejszy artykuł omawia zaawansowane zagadnienia -- od optymalizacji zapytań przez `select_related` i `prefetch_related`, przez architekturę potoku middleware, aż po serializery, uprawnienia i throttling w DRF. Każda sekcja zawiera praktyczne przykłady kodu gotowe do zastosowania w realnych projektach opartych na Django 5.2 LTS i DRF 3.17. > **Wskazówka rekrutacyjna** > > Rekruterzy szczególnie cenią kandydatów, którzy potrafią wyjaśnić różnice między `select_related` a `prefetch_related` na konkretnych przykładach SQL. Umiejętność uzasadnienia wyboru jednej metody nad drugą w kontekście wydajności daje wyraźną przewagę podczas rozmowy technicznej. ## Optymalizacja ORM -- select_related i prefetch_related Jednym z najczęściej zadawanych pytań na rozmowach jest problem N+1 i sposoby jego rozwiązywania w Django ORM. Aby zrozumieć to zagadnienie, warto zacząć od modeli danych ilustrujących typowe relacje: klucz obcy (ForeignKey) oraz relacje wiele-do-wielu (ManyToManyField). ```python # 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) ``` Model `Developer` posiada relację ForeignKey do `Company` oraz ManyToMany do `Skill`. Bez odpowiedniej optymalizacji iteracja po developerach i odwoływanie się do powiązanych obiektów generuje oddzielne zapytanie SQL dla każdego wiersza -- to właśnie problem N+1. Rozwiązaniem jest zastosowanie odpowiedniego mechanizmu prefetchowania w zależności od typu relacji: ```python # 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 (...) ``` Zasada jest prosta: `select_related` stosuje się do relacji ForeignKey i OneToOneField, ponieważ generuje pojedyncze zapytanie z JOIN. Natomiast `prefetch_related` obsługuje relacje ManyToMany i odwrotne klucze obce, wykonując dwa oddzielne zapytania i łącząc wyniki w Pythonie. Znajomość tej różnicy eliminuje większość problemów wydajnościowych w aplikacjach Django. ## Niestandardowe Managery i łączenie QuerySetów Podczas rozmów rekrutacyjnych często pada pytanie o Custom Managery -- mechanizm enkapsulacji często używanych zapytań na poziomie modelu. Zamiast powtarzać logikę filtrowania w wielu widokach, można zdefiniować niestandardowy QuerySet i Manager. ```python # 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() ``` Kluczowa zaleta tego podejścia polega na możliwości łączenia metod QuerySet w łańcuch (chaining). Każda metoda zwraca nowy QuerySet, co pozwala budować złożone zapytania w czytelny, deklaratywny sposób: ```python # 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 ) ``` Standardowe metody QuerySet, takie jak `select_related`, pozostają dostępne nawet przy użyciu niestandardowego Managera. To podejście jest zgodne z zasadą DRY i centralizuje logikę zapytań w jednym miejscu. > **Django 5.2 LTS -- klucze kompozytowe** > > Django 5.2 LTS wprowadza natywne wsparcie dla kluczy kompozytowych (composite primary keys) poprzez pole `CompositePrimaryKey`. Eliminuje to konieczność stosowania obejść z unikalnymi ograniczeniami (`unique_together`) w tabelach pośredniczących. W kontekście rekrutacyjnym warto wspomnieć o tej funkcji jako przykładzie ewolucji ORM. ## Potok middleware -- architektura i kolejność wykonywania Middleware w Django to łańcuch warstw opakowujących (wrapper layers), przez które przechodzi każde żądanie HTTP. Każdy middleware otrzymuje referencję do następnego elementu w łańcuchu poprzez `get_response`. Wywołanie `self.get_response(request)` przekazuje sterowanie dalej -- do następnego middleware lub do widoku. Poniższy przykład ilustruje middleware mierzący czas przetwarzania każdego żądania: ```python # 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 ``` Kod wykonany przed wywołaniem `get_response` działa w fazie żądania (request phase), a kod po nim -- w fazie odpowiedzi (response phase). To fundamentalny wzorzec, który rekruterzy oczekują od kandydatów. Middleware może również skrócić łańcuch przetwarzania, zwracając odpowiedź bez przekazywania żądania dalej. Typowy przykład to rate limiting: ```python # 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) ``` Gdy `RateLimitMiddleware` zwraca `JsonResponse` bezpośrednio, widok nigdy nie zostaje wywołany. To skrócenie (short-circuit) jest kluczowym mechanizmem ochrony przed nadmiernym obciążeniem serwera. ## Hooki middleware -- process_view, process_exception i process_template_response Oprócz standardowej metody `__call__`, middleware w Django może implementować dodatkowe hooki pozwalające na interwencję w różnych fazach cyklu przetwarzania. Pytanie o te hooki pojawia się regularnie na rozmowach o zaawansowanych zagadnieniach Django. ```python # 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() ``` Trzy kluczowe hooki działają w następujący sposób: - **process_view** -- wywoływany po rozwiązaniu URL, ale przed wykonaniem widoku. Zwrócenie `None` kontynuuje normalne przetwarzanie; zwrócenie `HttpResponse` pomija widok. - **process_exception** -- wywoływany wyłącznie gdy widok rzuci wyjątek. Służy do logowania, alertów lub niestandardowej obsługi błędów. - **process_template_response** -- wywoływany gdy odpowiedź posiada metodę `render()` (czyli jest instancją `TemplateResponse`). Umożliwia modyfikację kontekstu szablonu przed renderowaniem. Kolejność middleware w ustawieniu `MIDDLEWARE` jest istotna: żądanie przechodzi przez middleware od góry do dołu, a odpowiedź -- od dołu do góry. ## Serializery DRF -- zagnieżdżone relacje i wydajność [Django REST Framework](https://www.django-rest-framework.org/) wykorzystuje serializery do konwersji między obiektami Python a formatami takimi jak JSON. Na rozmowach kwalifikacyjnych częstym pytaniem jest obsługa zagnieżdżonych relacji bez generowania problemu N+1. ```python # 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"] ``` Parametr `read_only=True` oznacza, że zagnieżdżone dane są dostępne wyłącznie przy odczycie (GET). Aby uniknąć problemu N+1 w kontekście DRF, ViewSet musi odpowiednio optymalizować QuerySet: ```python # 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") ) ``` Bez `select_related` i `prefetch_related` w ViewSecie każdy serializowany developer generuje oddzielne zapytania dla company i skills. Dla listy 50 developerów oznacza to 1 + 50 + 50 = 101 zapytań zamiast 3. ## Uprawnienia w DRF -- kontrola dostępu na poziomie obiektu System uprawnień w DRF działa na dwóch poziomach: globalnym (view-level) i obiektowym (object-level). Na rozmowach rekrutacyjnych często pada pytanie o implementację niestandardowych klas uprawnień. ```python # 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 ``` Metoda `has_object_permission` jest wywoływana przez DRF automatycznie przy operacjach `retrieve`, `update`, `partial_update` i `destroy`. Zastosowanie niestandardowych uprawnień w ViewSecie wygląda następująco: ```python # 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 ``` Kluczowy niuans, na który zwracają uwagę rekruterzy: `has_permission` działa przy każdym żądaniu (poziom listy), natomiast `has_object_permission` -- tylko przy wywołaniu `get_object()` (poziom szczegółów). Brak nadpisania `get_queryset` dla widoków listowych tworzy lukę bezpieczeństwa. > **Częsty błąd bezpieczeństwa w DRF** > > Poleganie wyłącznie na has_object_permission bez filtrowania queryset pozostawia endpoint listowy bez ochrony. Zawsze należy łączyć uprawnienia na poziomie obiektu z filtrowanym get_queryset, aby wymusić kontrolę dostępu zarówno na widokach listowych, jak i szczegółowych. ## Throttling i paginacja w DRF -- konfiguracja produkcyjna Throttling (ograniczanie liczby żądań) i paginacja to dwa mechanizmy, których poprawna konfiguracja jest warunkiem wdrożenia API na produkcję. Rekruterzy często pytają o różnice między typami paginacji i strategie throttlingu. ```python # 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, } ``` Oddzielne limity dla użytkowników anonimowych i uwierzytelnionych są standardową praktyką. W kontekście paginacji, `CursorPagination` oferuje najlepszą wydajność dla dużych zbiorów danych, ponieważ nie używa `OFFSET`: ```python # 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=abc123 ``` Pole `ordering` musi wskazywać na kolumnę z indeksem i unikalnymi, sekwencyjnymi wartościami. Cursor pagination koduje pozycję w ciągu znaków (cursor), co uniemożliwia przeskakiwanie do dowolnej strony, ale zapewnia stałą wydajność niezależnie od numeru strony. ## Podsumowanie Przygotowanie do rozmowy rekrutacyjnej z Django wymaga solidnego opanowania trzech filarów: ORM, middleware i Django REST Framework. Kluczowe zagadnienia do zapamiętania: - **Optymalizacja ORM**: `select_related` stosuje się do ForeignKey/OneToOne (SQL JOIN), a `prefetch_related` do ManyToMany i odwrotnych relacji (oddzielne zapytania). Niestandardowe QuerySety centralizują logikę zapytań i umożliwiają czytelne łączenie metod (chaining). - **Architektura middleware**: żądanie przepływa od góry do dołu, odpowiedź od dołu do góry. Skrócenie łańcucha (short-circuit) przez zwrócenie odpowiedzi przed `get_response()` to fundamentalny wzorzec dla rate limitingu, walidacji i kontroli dostępu. - **Wydajność serializerów DRF**: zagnieżdżone serializery wymagają optymalizacji QuerySet w ViewSecie. Bez `select_related`/`prefetch_related` serializacja generuje N+1 zapytań na skali. - **Uprawnienia DRF**: `has_object_permission` należy łączyć z filtrowanym `get_queryset`, aby wymusić kontrolę dostępu na obu poziomach. Same uprawnienia obiektowe pozostawiają widoki listowe bez ochrony. - **Throttling i paginacja**: cursor pagination skaluje się lepiej niż offset na dużych tabelach. Limity żądań należy konfigurować oddzielnie dla użytkowników anonimowych i uwierzytelnionych. Aby przećwiczyć te wzorce, warto skorzystać z [pytań rekrutacyjnych z Django ORM](/technologies/django/interview-questions/django-orm-advanced) oraz [pytań o Django middleware](/technologies/django/interview-questions/django-middleware) na platformie SharpSkill. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/django/django-interview-orm-middleware-drf