Najpopularniejsze pytania rekrutacyjne z Django (z odpowiedziami)

Piotr Dul

04-11-2026

ORM i problem N+1, migracje, widoki i formularze, bezpieczeństwo, Django REST Framework i wdrożenie. Sprawdź, jakie pytania z Django padają na rozmowach na juniora i jak na nie odpowiadać.

Najpopularniejsze pytania rekrutacyjne z Django (z odpowiedziami)
Na rozmowach z Django najczęściej pada kilkanaście pytań: czym jest wzorzec MVT, jak działa ORM i czym różni się select_related od prefetch_related, po co są migracje, czym null różni się od blank, jak Django chroni przed CSRF i jak wygląda API w Django REST Framework. Rekruter nie sprawdza nazw poleceń z pamięci, tylko to, czy wiesz, jakie zapytania do bazy wygeneruje twój kod i co framework robi za ciebie. Dobra odpowiedź to jedno zdanie definicji i krótki przykład.

Rozmowa o Django rzadko jest egzaminem z całej dokumentacji. Najczęściej to te same kilkanaście tematów, przy których rekruter sprawdza, czy rozumiesz, co dzieje się pod spodem, a nie tylko czy potrafisz wpisać python manage.py startapp. Poniżej znajdziesz pytania, które wracają na rozmowach na stanowisko juniora, razem z tym, jak na nie odpowiadać. Django zakłada znajomość samego języka, więc najpierw sprawdź się na pytaniach rekrutacyjnych z Pythona, a pytania o bazę danych znajdziesz w artykule o pytaniach rekrutacyjnych z baz danych i SQL.

Przykłady uruchomiliśmy w Django 6.1 i Django REST Framework 3.18 na bazie SQLite, w testowym projekcie z startproject. Liczby zapytań w tym artykule pochodzą z tego pomiaru (100 wpisów w bazie). Nie sprawdzaliśmy PostgreSQL ani wdrożenia na serwerze, więc tam opieramy się na dokumentacji. Przy konkretnym projekcie zawsze zerkaj do dokumentacji jego wersji, bo szczegóły potrafią się między wersjami różnić.

Jak odpowiadać na pytania rekrutacyjne z Django?

Najlepiej w trzech krokach: jedno zdanie definicji, krótki przykład (kilka linijek kodu lub sytuacja z projektu) i informacja, kiedy to ma znaczenie w praktyce. Taka odpowiedź zajmuje 30–60 sekund i pokazuje, że rozumiesz mechanizm, a nie wkułeś formułkę. Przy pytaniu o framework pomaga czwarty krok: powiedz, co byś musiał zrobić ręcznie, gdyby Django tego nie robiło. Jeśli czegoś nie wiesz, powiedz, co wiesz, i rozumuj na głos.

Pytania są ułożone od podstaw do tematów, które częściej pojawiają się przy ofertach na mid-poziom.

Podstawy Django

Czym jest Django i czym różni się od Flaska i FastAPI?

Django to framework webowy w Pythonie typu „baterie w zestawie”: dostajesz ORM, system migracji, panel administracyjny, uwierzytelnianie, formularze, szablony i ochronę przed typowymi atakami w jednym pakiecie. Flask to mikroframework, w którym te elementy dobierasz sam, a FastAPI skupia się na szybkim budowaniu API z walidacją opartą na typach. Django wybiera się, gdy aplikacja ma dużo ekranów, bazę danych i panel dla administratora, a FastAPI lub Flask, gdy budujesz małą usługę albo API. Pełne porównanie z tabelą cech znajdziesz w artykule Django vs Flask vs FastAPI, a podstawy w tekście co to jest Django i do czego służy.

Co to jest MVT i czym różni się od MVC?

MVT (Model-View-Template) to wariant MVC w Django. Model to klasa opisująca dane i tabelę w bazie. Widok (view) to funkcja lub klasa, która przyjmuje żądanie, pobiera dane i zwraca odpowiedź. Szablon (template) to plik HTML, w którym wyświetlasz dane. Rolę kontrolera z MVC pełni w Django w dużej mierze sam framework: dopasowuje adres URL do widoku w pliku urls.py. Pułapka językowa: „widok” w Django to odpowiednik kontrolera, a nie szablonu. Rekruterzy chętnie o to dopytują.

Czym różni się projekt od aplikacji w Django?

Projekt to całość: ustawienia (settings.py), główny plik tras (urls.py) i zestaw aplikacji. Aplikacja to wydzielony kawałek funkcjonalności (np. blog, konta, płatności) z własnymi modelami, widokami i testami. Powstają poleceniami django-admin startproject i python manage.py startapp. Aplikacja zaczyna działać dopiero po dopisaniu do INSTALLED_APPS, co jest najczęstszą przyczyną błędu „nie widzi mojego modelu”. Jedną aplikację można użyć w wielu projektach, o ile jest napisana niezależnie od reszty.

Jak wygląda droga żądania w Django?

Żądanie trafia do serwera, przechodzi przez kolejne warstwy middleware, a router (urls.py) dopasowuje adres do widoku. Widok pobiera dane z modelu, przekazuje je do szablonu lub serializera i zwraca odpowiedź, która wraca przez middleware w odwrotnej kolejności. Dobra odpowiedź na rozmowie wymienia te kroki po kolei, bo od nich zaczyna się szukanie błędów: jeśli odpowiedź jest 404, problem leży w urls.py, jeśli 403 w CSRF albo uprawnieniach, jeśli 500 w widoku.

Jakie polecenia manage.py trzeba znać?

PolecenieCo robi
python manage.py runserveruruchamia serwer deweloperski
python manage.py makemigrationstworzy pliki migracji na podstawie zmian w modelach
python manage.py migratestosuje migracje w bazie
python manage.py createsuperusertworzy konto administratora
python manage.py shellinteraktywna konsola z załadowanym projektem
python manage.py testuruchamia testy
python manage.py check --deploywypisuje ostrzeżenia o ustawieniach niebezpiecznych na produkcji

Na rozmowie nie trzeba znać wszystkich, ale warto wiedzieć, że shell pozwala szybko sprawdzić zapytanie bez pisania widoku, a check --deploy na świeżym projekcie wypisuje kilka ostrzeżeń (w naszym teście było ich siedem), bo domyślne ustawienia są wygodne lokalnie, a nie bezpieczne na serwerze.

Baza danych i ORM

Co to jest Django ORM i dlaczego nie zastępuje znajomości SQL?

ORM (Object-Relational Mapping) mapuje tabele na klasy, a wiersze na obiekty. Zamiast pisać SELECT * FROM blog_post WHERE id = 1, piszesz Post.objects.get(pk=1). Dostajesz też ochronę przed SQL injection, bo wartości trafiają do zapytania jako parametry, a nie sklejony tekst. Sprawdziliśmy to na przykładzie: Author.objects.filter(name="x' OR 1=1 --") zostało wysłane do bazy jako zapytanie z parametrem %s, a podejrzany tekst jako osobna wartość. ORM nie zwalnia jednak z myślenia o bazie: żeby wiedzieć, ile zapytań wykona twój kod, musisz rozumieć, co generuje. O samym ataku piszemy w artykule o SQL injection.

Jak definiuje się model i relacje?

Model to klasa dziedzicząca po models.Model, w której pola są atrybutami klasy:

class Author(models.Model):
    name = models.CharField(max_length=100)


class Post(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="posts")
    tags = models.ManyToManyField("Tag", blank=True, related_name="posts")
    title = models.CharField(max_length=200)
    subtitle = models.CharField(max_length=200, blank=True)
    published_at = models.DateTimeField(null=True, blank=True)
RelacjaPolePrzykład
Jeden do jednegoOneToOneFieldużytkownik i jego profil
Jeden do wieluForeignKeyautor i jego wpisy
Wiele do wieluManyToManyFieldwpisy i tagi (tabela pośrednia tworzy się sama)

related_name nazywa relację odwrotną: dzięki related_name="posts" zapiszesz author.posts.all() zamiast domyślnego author.post_set.all(). O samych relacjach w bazie piszemy w artykule o relacjach i JOIN w SQL.

Czym różni się null=True od blank=True?

null dotyczy bazy danych: czy kolumna może zawierać wartość NULL. blank dotyczy walidacji: czy formularz lub full_clean() pozwala zostawić pole puste. To dwie niezależne rzeczy, które często się łączy dla pól opcjonalnych.

# title bez blank=True: full_clean() zgłasza błąd
Post(author=a, title="").full_clean()
# ValidationError: {'title': ['This field cannot be blank.']}

# subtitle ma blank=True bez null=True: zapisuje się jako pusty tekst
p = Post(author=a, title="ok")
p.full_clean(); p.save()
p.subtitle        # ''
p.published_at    # None (null=True, blank=True)

Zasada, którą rekruter chce usłyszeć: dla pól tekstowych zwykle używa się samego blank=True i przechowuje pusty tekst, żeby „brak wartości” miał jedną postać (a nie dwie: '' i NULL). null=True jest naturalne dla dat, liczb i kluczy obcych.

Co robi on_delete i jakie ma opcje?

Argument on_delete mówi, co zrobić z rekordami zależnymi, gdy usuwasz rekord, do którego się odwołują. Najczęstsze opcje:

OpcjaEfekt po usunięciu autora
CASCADEusuwa też jego wpisy
PROTECTblokuje usunięcie, dopóki istnieją wpisy
SET_NULLustawia NULL w polu klucza (wymaga null=True)
SET_DEFAULTustawia wartość domyślną pola
DO_NOTHINGnic nie robi, więc baza może zgłosić błąd integralności

Sprawdziliśmy CASCADE: po author.delete() wpisy tego autora znikają z bazy. Pozostałe opcje opisujemy według dokumentacji. Dobra odpowiedź podaje, kiedy wybierasz którą: CASCADE dla danych, które nie mają sensu bez rodzica (pozycje zamówienia), PROTECT dla danych, których nie wolno stracić przypadkiem (faktury).

Czym jest QuerySet i dlaczego jest „leniwy”?

QuerySet to opis zapytania do bazy. Samo zbudowanie go (Post.objects.filter(...).exclude(...)) nie wykonuje żadnego zapytania. Baza zostaje odpytana dopiero wtedy, gdy wynik jest potrzebny: przy iteracji, list(), len() albo wyświetleniu. Wynik zostaje potem zapamiętany w obiekcie.

qs = Post.objects.filter(title__startswith="t").exclude(views=5)   # 0 zapytań
list(qs); list(qs)                                                  # 1 zapytanie (drugi raz z pamięci podręcznej)
Post.objects.count(); Post.objects.filter(pk=1).exists()            # po 1 zapytaniu każde

Wniosek: ten sam QuerySet można przeglądać kilka razy bez kosztu, ale nowy QuerySet to nowe zapytanie. Do sprawdzenia, czy rekordy istnieją, służy exists(), a do policzenia count(), bo nie pobierają całych obiektów.

Czym jest problem N+1 i czym różni się select_related od prefetch_related?

N+1 to sytuacja, w której jedno zapytanie pobiera listę rekordów, a potem dla każdego z nich leci osobne zapytanie o powiązane dane. Zmierzyliśmy to dla 100 wpisów:

# źle: 1 zapytanie o wpisy + po jednym o autora = 101 zapytań
for p in Post.objects.all():
    print(p.author.name)

# dobrze: 1 zapytanie (JOIN)
for p in Post.objects.select_related("author"):
    print(p.author.name)

# relacja wiele do wielu: 101 zapytań bez prefetch_related, 2 z nim
for p in Post.objects.prefetch_related("tags"):
    print(list(p.tags.all()))
select_relatedprefetch_related
Dla relacjiForeignKey, OneToOneField (pojedynczy obiekt)ManyToManyField i relacja odwrotna (wiele obiektów)
Jak działajedno zapytanie z JOINosobne zapytanie na relację, łączenie w Pythonie
Zapytań dla 100 wpisów12

Krótka reguła: „do jednego” przez select_related, „do wielu” przez prefetch_related. Jak rozpoznać problem w praktyce: policz zapytania. W testach służy do tego CaptureQueriesContext (z django.test.utils), a lokalnie narzędzie typu Django Debug Toolbar.

Jak filtrować w ORM: exclude, Q i F?

Django nie ma operatora „różne”, dlatego używa się exclude(). Warunki łączone przez „lub” zapisuje się obiektami Q. Wyrażenie F odwołuje się do wartości kolumny w samej bazie:

Post.objects.exclude(title="t1")                          # wszystkie poza t1
Post.objects.filter(Q(title="t1") | Q(title="t2"))        # t1 lub t2
Post.objects.filter(pk=p.pk).update(views=F("views") + 1) # licznik w bazie

F ma znaczenie przy licznikach: zapis post.views += 1; post.save() czyta wartość do Pythona, więc dwa równoległe żądania mogą nadpisać sobie wynik. Z F zwiększenie odbywa się w jednym zapytaniu w bazie.

Czym różnią się get(), filter() i get_object_or_404()?

filter() zawsze zwraca QuerySet (może pusty). get() zwraca dokładnie jeden obiekt, a gdy go nie ma, zgłasza DoesNotExist; gdy jest ich więcej niż jeden, MultipleObjectsReturned. Oba wyjątki sprawdziliśmy. W widoku zamiast ręcznie łapać wyjątek używa się skrótu get_object_or_404(Post, pk=pk), który zwraca odpowiedź 404.

Co to są migracje i jak cofnąć ostatnią?

Migracja to plik opisujący zmianę struktury bazy, wygenerowany z różnic w modelach. Przepływ jest zawsze ten sam: zmieniasz model, makemigrations tworzy plik, migrate stosuje go w bazie. Podgląd SQL, który zostanie wykonany, pokazuje sqlmigrate, a stan migracji showmigrations. Cofnięcie robi się, podając nazwę aplikacji i numer migracji, do której wracasz (python manage.py migrate blog zero cofa wszystkie migracje aplikacji; sprawdziliśmy).

Dwie zasady, o które dopytują rekruterzy: pliki migracji trafiają do repozytorium, a migracji, która była już uruchomiona na innych środowiskach, się nie edytuje, tylko dodaje nową. Po zmianie modelu bez makemigrations kod będzie szukał kolumny, której nie ma w bazie. Podobną ideę w narzędziach spoza Django opisujemy w artykule o migracjach z Alembic.

Czym jest transaction.atomic?

To blok, w którym wszystkie zapisy do bazy albo zakończą się razem, albo zostaną cofnięte razem. Przykład: tworzysz zamówienie i jego pozycje, a w połowie wyskakuje błąd. Bez transakcji zostaje zamówienie bez pozycji.

try:
    with transaction.atomic():
        Author.objects.create(name="tmp")
        raise RuntimeError
except RuntimeError:
    pass

Author.objects.filter(name="tmp").exists()   # False

Sprawdziliśmy to w teście: po wyjątku autor nie został zapisany.

Jak rozszerzyć model użytkownika?

Najlepiej zrobić to na początku projektu: stworzyć własny model użytkownika (dziedziczący po AbstractUser) i wskazać go w ustawieniu AUTH_USER_MODEL. Zmiana domyślnego modelu w działającym projekcie z danymi jest bolesna, bo dotyka migracji i kluczy obcych. Alternatywa dla prostych przypadków to osobny model profilu powiązany relacją OneToOneField. Tego mechanizmu nie uruchamialiśmy w testowym projekcie; opisujemy go według dokumentacji.

Widoki, formularze i szablony

Czym różni się widok funkcyjny od klasowego?

Widok funkcyjny to zwykła funkcja przyjmująca request:

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return JsonResponse({"id": post.pk, "title": post.title})

Widok klasowy (class-based view, np. ListView, DetailView) pozwala użyć gotowego zachowania i nadpisać tylko to, co się różni. Funkcje są prostsze i bardziej jawne, klasy oszczędzają kod przy typowych operacjach CRUD. Dobra odpowiedź nie wybiera „zawsze jednego”: do prostych, nietypowych widoków funkcje, do powtarzalnych list i formularzy widoki klasowe.

Jak działają formularze i ModelForm?

Formularz Django waliduje dane z żądania i zamienia je na bezpieczne wartości w cleaned_data. ModelForm generuje formularz z modelu, a w Meta.fields wyraźnie wskazujesz, które pola wolno ustawić:

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title", "subtitle"]

f = PostForm({"title": "x", "subtitle": "", "views": 9999, "author": 1})
f.is_valid()                 # True
sorted(f.cleaned_data)       # ['subtitle', 'title']
PostForm({"title": ""}).errors   # {'title': ['This field is required.']}

Dosłane pola views i author zostały zignorowane, bo nie ma ich na liście. To odpowiednik białej listy: unikaj fields = "__all__", bo każde nowe pole w modelu stanie się dostępne dla użytkownika, także to, którego miał nie ustawiać (np. is_staff).

Jak Django zabezpiecza szablony przed XSS?

Szablony domyślnie zamieniają znaki specjalne HTML: {{ v }} dla <script>alert(1)</script> wypisuje &lt;script&gt;alert(1)&lt;/script&gt;, więc kod nie wykona się w przeglądarce. Filtr |safe (oraz mark_safe) wyłącza tę ochronę, więc użycie go na danych od użytkownika otwiera drogę do ataku XSS. Sprawdziliśmy obie postaci. Zasada: domyślnie bez |safe, a do |safe tylko treść, którą sam wygenerowałeś lub oczyściłeś. O samym ataku piszemy w artykule o XSS.

Co to jest middleware w Django?

Middleware to warstwa, przez którą przechodzi każde żądanie, zanim trafi do widoku (i przez którą wraca odpowiedź). Służy do zadań wspólnych: sesji, CSRF, uwierzytelniania, logowania, nagłówków. Najprościej wyobrazić je jako cebulę: żądanie wchodzi do środka przez kolejne warstwy, odpowiedź wychodzi tą samą drogą.

class TimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        request.before = True                    # kod przed widokiem
        response = self.get_response(request)    # wywołanie dalej
        response["X-Seen"] = "yes"               # kod po widoku
        return response

Klasę dopisuje się do listy MIDDLEWARE w ustawieniach, a kolejność na liście ma znaczenie. W teście taki nagłówek faktycznie pojawił się w odpowiedzi.

Bezpieczeństwo

Jak Django chroni przed CSRF?

Formularz zmieniający dane zawiera ukryty token wstawiany znacznikiem {% csrf_token %}. Django porównuje go z tokenem powiązanym z sesją użytkownika i odrzuca żądanie, jeśli się nie zgadza (odpowiedź 403). Chroni to przed sytuacją, w której obca strona wysyła w imieniu zalogowanego użytkownika żądanie do twojej aplikacji.

Pułapka w testach: domyślny test Client pomija sprawdzanie CSRF. W naszym teście POST bez tokenu dał 200, a z Client(enforce_csrf_checks=True) 403. Jeśli testujesz zabezpieczenie, trzeba je jawnie włączyć. Z drugiej strony: wyłączanie ochrony (@csrf_exempt), bo żądanie AJAX dostaje 403, to zły skrót. Poprawnie dołącza się token do nagłówka żądania. Mechanizm wyjaśniamy w artykule o CSRF i ciasteczkach SameSite.

Jak działa uwierzytelnianie i autoryzacja?

Uwierzytelnianie (authentication) odpowiada na pytanie „kim jesteś?”, autoryzacja (authorization) na „co ci wolno?”. Django ma gotowy system użytkowników, sesji, grup i uprawnień. Dekorator @login_required przekierowuje niezalogowanego użytkownika na stronę logowania:

@login_required
def secret(request):
    return JsonResponse({"ok": True})

# bez logowania: 302 na /accounts/login/?next=/secret/
# po zalogowaniu: 200

Zasada, którą rekruter chce usłyszeć: sprawdzaj uprawnienia do konkretnego rekordu, a nie tylko to, czy użytkownik jest zalogowany. Klasyczny błąd: ktoś zmienia id w adresie i widzi cudze dane. Uwierzytelnianie w API opartym na tokenach opisujemy na przykładzie FastAPI w artykule o JWT i ochronie endpointów; w Django REST Framework służą do tego klasy uwierzytelniania i uprawnień, których w naszym teście nie uruchamialiśmy.

Co robią DEBUG i ALLOWED_HOSTS na produkcji?

DEBUG = True pokazuje szczegółowe strony błędów z kodem i ustawieniami, więc na produkcji musi być wyłączone. Przy DEBUG = False Django wymaga też wypełnionej listy ALLOWED_HOSTS i odrzuca żądania z innym nagłówkiem Host:

# DEBUG=False, ALLOWED_HOSTS=["devmentor.pl"]
client.get("/posts/1/", HTTP_HOST="evil.com").status_code   # 400

Sprawdziliśmy: obcy host dostaje 400. Na produkcji trzeba też pamiętać o SECRET_KEY (nie w repozytorium, tylko w zmiennych środowiskowych) oraz o plikach statycznych, które po wyłączeniu DEBUG nie są już serwowane przez serwer deweloperski. Pomaga w tym python manage.py check --deploy.

Django REST Framework

Co to jest Django REST Framework i po co serializer?

Django REST Framework (DRF) to biblioteka do budowania API w Django. Serializer (serializer) zamienia obiekty modelu na JSON i odwrotnie, a przy odbieraniu danych dodatkowo je waliduje, podobnie jak formularz:

class PostSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source="author.name", read_only=True)

    class Meta:
        model = Post
        fields = ["id", "title", "author_name"]


class PostList(ListAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

Odpowiedź ma postać listy obiektów, np. {"id": 1, "title": "t0", "author_name": "a0"}. Tak samo jak w formularzu, jawna lista fields jest zabezpieczeniem: nie wystawiasz pól, których nie planowałeś.

Dlaczego ten widok DRF jest wolny i jak to naprawić?

Serializer pole author_name pobiera przez post.author.name, więc dla każdego wpisu leci osobne zapytanie. Zmierzyliśmy: 101 zapytań dla 100 wpisów. Wystarczy zmienić queryset:

class PostList(ListAPIView):
    queryset = Post.objects.select_related("author")
    serializer_class = PostSerializer

Po zmianie zapytań jest 1. To jedno z najczęstszych pytań o DRF, bo problem N+1 jest tu szczególnie łatwy do przeoczenia: kod wygląda czysto, a koszt ukrywa się w serializerze. Pozostałe elementy DRF (ViewSet, router, paginacja, uprawnienia) opisujemy w kursie, ale w tym artykule ich nie testowaliśmy. Podobny mechanizm walidacji danych wejściowych w innym frameworku opisujemy w artykule o Pydantic w FastAPI.

Testy i wdrożenie

Jak testuje się aplikację w Django?

Django ma wbudowane narzędzia testowe: TestCase opakowuje każdy test w transakcję, którą wycofuje na końcu, więc testy nie zostawiają śladów w bazie, a Client wywołuje widoki jak przeglądarka. Uruchamia się je poleceniem python manage.py test. Rekruter oczekuje odpowiedzi, co testujesz i dlaczego: ścieżkę rejestracji, walidację, uprawnienia i liczbę zapytań (assertNumQueries), a nie wywołania biblioteki. Zasady pisania testów od strony metody opisujemy w artykule o TDD i BDD w pytest.

Jak wdrożyć aplikację Django na serwer?

W skrócie: kod trafia na serwer z repozytorium, instalujesz zależności, ustawiasz zmienne środowiskowe (SECRET_KEY, dane bazy, DEBUG=False, ALLOWED_HOSTS), uruchamiasz migracje, zbierasz pliki statyczne poleceniem collectstatic, a aplikację serwuje serwer aplikacyjny (np. Gunicorn) za serwerem WWW. Wdrożenie często pakuje się w kontener Docker. Dobra odpowiedź wspomina o rzeczach, które łatwo pominąć: baza produkcyjna to zwykle PostgreSQL, a nie SQLite, pliki użytkowników trzeba przechowywać poza kontenerem, a check --deploy pokazuje listę ustawień do poprawy. Tej części nie uruchamialiśmy w naszym teście, więc opisujemy ją na podstawie dokumentacji i programu kursu.

Jakie zadania z kodem pojawiają się na rozmowach z Django?

Najczęściej: prosty CRUD (wpisy, zadania) z formularzem i walidacją, model z relacją jeden do wielu, lista z poprawką N+1, własny widok chroniony logowaniem, endpoint API w DRF z serializerem oraz test jednego widoku. Rekruter patrzy, czy używasz konwencji frameworka, czy zabezpieczasz dane (jawna lista fields, uprawnienia, CSRF) i czy rozumiesz zapytania, które wygeneruje ORM. Umiejętności wymagane od juniora backendowego omawia poradnik o tym, jak przygotować się do rekrutacji na juniora developera.

Co zrobi Django? Mini-test na rozgrzewkę

Rekruterzy lubią pytanie „co się stanie?”. Spróbuj odpowiedzieć na trzy poniższe, zanim zajrzysz do odpowiedzi. Wszystkie sprawdziliśmy uruchomieniem w Django 6.1.

Zadanie 1. Ile zapytań do bazy wykona ten widok dla 100 wpisów?

class PostList(ListAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer   # pole author_name z source="author.name"

Odpowiedź: 101 (jedno o wpisy i po jednym o autora dla każdego z nich). Z Post.objects.select_related("author") będzie jedno zapytanie.

Zadanie 2. Test wysyła POST bez tokenu CSRF, a widok nie jest oznaczony @csrf_exempt. Jaki kod odpowiedzi dostaniesz w zwykłym teście z self.client?

Odpowiedź: 200. Domyślny test Client nie sprawdza CSRF. Dopiero Client(enforce_csrf_checks=True) zwróci 403. Dlatego test zabezpieczenia trzeba włączyć jawnie.

Zadanie 3. Pole subtitle = models.CharField(max_length=200, blank=True) zostawiasz puste i zapisujesz model. Co trafi do bazy, NULL czy pusty tekst?

Odpowiedź: pusty tekst (''), bo blank=True dotyczy walidacji, a null nie jest ustawione. Pole published_at z null=True, blank=True zapisze się jako None.

Jak przygotować się do rozmowy z Django?

Samo czytanie odpowiedzi nie wystarczy, bo pytania uzupełniające szybko pokazują, czy rozumiesz temat. Skuteczniej działa zbudowanie małej aplikacji (blog z logowaniem, komentarzami i API) bez kopiowania gotowca oraz głośne tłumaczenie decyzji komuś innemu, na przykład mentorowi. Dla porządku:

ObszarCo umieć wyjaśnić własnymi słowami
PodstawyDjango a Flask i FastAPI, MVT, projekt a aplikacja, droga żądania, manage.py
Baza danychmodel i relacje, null a blank, on_delete, leniwy QuerySet, N+1, select_related i prefetch_related, migracje
Zapytaniaexclude, Q, F, get a filter, transaction.atomic
Widoki i formularzewidok funkcyjny a klasowy, ModelForm i jawne fields, szablony i autoescape, middleware
BezpieczeństwoCSRF, @login_required i uprawnienia, XSS, SQL injection, DEBUG i ALLOWED_HOSTS
API i praktykaserializer w DRF, N+1 w serializerze, testy, wdrożenie

Na czym juniorzy najczęściej tracą punkty?

Nie na trudnych tematach, tylko na trzech drobiazgach, które wychodzą dopiero przy pytaniach uzupełniających:

  1. Pętla z odwołaniem do relacji bez select_related. Kod działa na kilku rekordach, a na produkcji zalewa bazę zapytaniami (u nas 101 zamiast 1). Nie wystarczy powiedzieć „znam N+1”. Pokaż, jak go rozpoznać (liczba zapytań) i jak naprawić.
  2. Mylenie null z blank. To jedno z najwyżej ocenianych pytań o Django na Stack Overflow. Wyjaśnij, że jedno dotyczy bazy, a drugie walidacji, i podaj przykład pola tekstowego.
  3. Logika biznesowa w widoku. Widok, który ma 200 linii i sam liczy rabaty, nie da się przetestować ani użyć ponownie. Wspomnij o wydzieleniu logiki do modelu, menedżera lub osobnej funkcji.

Błędy początkujących w samym używaniu frameworka zbieramy w artykule o najczęstszych błędach początkujących w Django.

Na co zwraca uwagę mentor, gdy sprawdza kod w Django?

Przy code review kodu w Django mentor zwykle zaczyna od trzech rzeczy, które pasują też do rozmowy rekrutacyjnej.

Pierwsze: „Ile zapytań do bazy wykonuje ten widok?” Kod z pętlą po rekordach i odwołaniem do relacji działa na kilku danych testowych i dopiero na produkcji okazuje się wolny. W naszym pomiarze różnica to 101 zapytań zamiast jednego. Dlatego zanim oddasz kod z ORM, policz zapytania: w teście przez assertNumQueries albo CaptureQueriesContext. Kto zadaje to pytanie sam sobie, wyłapuje N+1 bez pomocy mentora.

Drugie: „Wypisuj pola jawnie, nigdy __all__.” Zarówno w ModelForm, jak i w serializerze DRF jawna lista fields jest białą listą. Z __all__ każde nowe pole dodane do modelu staje się od razu dostępne z zewnątrz, także to, którego użytkownik nie miał ustawiać, np. is_staff. Dopisanie pola do listy to jedna linijka, a naprawianie wycieku po fakcie jest dużo droższe.

Trzecie: zbuduj aplikację na samym Django, zanim dołożysz DRF. Model, widok, formularz z walidacją i ochrona CSRF pokazują, co framework robi za ciebie. Kto raz zrobił to bez serializerów, rozumie, czym serializer jest (formularzem dla API), i potrafi wyjaśnić to na rozmowie, zamiast powtarzać nazwy z dokumentacji.

Jak brzmi dobra odpowiedź, a jak słaba?

Weźmy pytanie o select_related:

OdpowiedźDlaczego
Słaba„To takie przyspieszenie zapytań w Django.”To efekt, a nie wyjaśnienie. Pytanie „jak?” od razu to pokaże.
Dobra„Dołącza powiązany obiekt w jednym zapytaniu z JOIN, więc przy pętli po wpisach z autorem mam 1 zapytanie zamiast 1 + N. Używam go dla ForeignKey i OneToOne, a dla relacji wiele do wielu prefetch_related.”Jest mechanizm, zakres użycia i porównanie.

Ten sam schemat działa przy każdym pytaniu z tego artykułu: definicja, mechanizm, przykład użycia.

Co zrobić, gdy nie znasz odpowiedzi?

Nie zgaduj i nie milcz. Powiedz, co wiesz na pewno, a potem rozumuj na głos, np.: „Nie pamiętam dokładnej nazwy, ale wiem, że szukam sposobu na wczytanie powiązanych obiektów razem z listą, więc sprawdziłbym select_related w dokumentacji ORM.” Taka odpowiedź pokazuje, że potrafisz sobie poradzić w pracy, a umiejętność znalezienia odpowiedzi jest w codziennej pracy ważniejsza niż pamięć do szczegółów.

Dobrze też zobaczyć, jak wygląda start w Pythonie od strony uczestnika. W podcaście Paweł Truszkowski opowiada o płatnym stażu z Pythona: o łączeniu stażu z pracą na etacie, wsparciu AI i pierwszych miesiącach w FastAPI.

Znajomość odpowiedzi to połowa sukcesu, drugą jest znalezienie rozmów, na które warto się umówić. Jeśli szukasz ofert, sięgnij po darmowy poradnik o tym, jak szukać pracy w 2026 roku, w którym zebraliśmy 3 sposoby poszukiwania pracy, które u naszych podopiecznych dają najlepsze efekty. Całość procesu, od CV po rozmowę, opisuje artykuł o tym, jak przejść cały proces rekrutacyjny na juniora.

Jeśli braki wyjdą przy ORM albo DRF, to sygnał, że warto uporządkować wiedzę z mentorem. W kursie Python z Django są osobne moduły o wprowadzeniu do Django, modelach i formularzach, Django REST Framework, autoryzacji w API, wdrożeniu i konteneryzacji Dockerem. Zadania i projekty z tej tematyki sprawdza mentor w code review.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force