Pytania rekrutacyjne Django
68 pytań — 20 odpowiedzi dostępnych od razu
Podstawy Django
Czym jest Django i jakie problemy rozwiązuje?
Odpowiedź w 30 sekund: Django to wysokopoziomowy framework webowy w Pythonie, który przyspiesza tworzenie bezpiecznych i skalowalnych aplikacji. Działa według zasady „baterie w zestawie" — dostarcza gotowe ORM, panel administracyjny, system uwierzytelniania, routing i szablony, więc nie trzeba budować ich od zera.
Odpowiedź w 2 minuty: Django powstało, by rozwiązać powtarzalne problemy budowy aplikacji webowych: dostęp do bazy danych, obsługę formularzy, uwierzytelnianie, bezpieczeństwo i organizację kodu. Zamiast składać aplikację z wielu osobnych bibliotek, dostajesz spójny, zintegrowany zestaw narzędzi. To skraca czas wytwarzania i zmniejsza ryzyko błędów, bo kluczowe komponenty są dobrze przetestowane i utrzymywane.
Framework promuje dobre praktyki: rozdział warstw (architektura MTV), zasadę DRY (Don't Repeat Yourself) oraz „convention over configuration". ORM pozwala pracować z bazą w Pythonie zamiast pisać SQL. System migracji wersjonuje zmiany schematu. Wbudowany panel admina daje od razu interfejs CRUD do danych. Dochodzi do tego solidny fundament bezpieczeństwa: domyślna ochrona przed XSS, CSRF i SQL injection.
Django sprawdza się szczególnie w aplikacjach intensywnie korzystających z bazy danych: serwisach treściowych, sklepach, panelach administracyjnych, API (z Django REST Framework) i platformach SaaS. Jest „opiniotwórczy" — narzuca pewną strukturę, co dla dużych zespołów jest zaletą (spójność), a dla bardzo małych projektów bywa nadmiarem. Typowa pułapka początkujących to walka z konwencjami frameworka zamiast korzystania z nich. Django rozwija się w stabilnym cyklu (wersje LTS), co czyni je dobrym wyborem na długie utrzymanie.
Materiały
Na czym polega architektura MTV (Model-Template-View) i czym różni się od klasycznego MVC?
Odpowiedź w 30 sekund: MTV to wariant wzorca MVC stosowany w Django: Model to dane i logika bazy, Template to warstwa prezentacji (HTML), a View to logika sterująca, która łączy dane z szablonem. Różnica nazewnicza wobec MVC: to, co w MVC nazywa się „controllerem", w Django nazywa się „view", a „view" z MVC odpowiada „template".
Odpowiedź w 2 minuty: MTV dzieli aplikację na trzy warstwy o jasno rozdzielonych odpowiedzialnościach. Model definiuje strukturę danych i reguły domenowe — to klasy mapowane na tabele bazy przez ORM. Template odpowiada za prezentację: pliki z HTML-em i specjalnym językiem szablonów, które wstawiają dane w odpowiednie miejsca. View to funkcja lub klasa, która odbiera żądanie, pobiera potrzebne dane z modeli, wykonuje logikę i wybiera szablon do wyrenderowania odpowiedzi.
Wzorzec MTV jest właściwie odmianą MVC, tyle że z inną terminologią, co bywa źródłem nieporozumień. W klasycznym MVC „Controller" obsługuje wejście i steruje przepływem, a „View" to prezentacja. W Django rolę kontrolera pełni „View", a rolę prezentacji „Template". Sam framework (mapowanie URL → widok, ORM, renderowanie) bywa nazywany „kontrolerem" w szerokim sensie — to Django decyduje, który widok obsłuży dane żądanie.
Korzyść z tego rozdziału to czystszy, łatwiejszy w utrzymaniu kod: projektant może pracować nad szablonami, a programista nad logiką, nie wchodząc sobie w drogę. Zmiana wyglądu nie wymaga ruszania logiki i odwrotnie. Pułapka: wkładanie logiki biznesowej do szablonów (DTL celowo to utrudnia) albo „grube" widoki ze zbyt dużą ilością logiki — wtedy warto wydzielić logikę do modeli (zasada „fat models, thin views") lub osobnej warstwy serwisowej.
Diagram:
flowchart LR
K["Klient (przeglądarka)"] -->|"żądanie URL"| R["URL dispatcher (routing)"]
R --> V["View (logika sterująca)"]
V -->|"pobiera dane"| M["Model (ORM / baza)"]
M --> V
V -->|"przekazuje kontekst"| T["Template (HTML)"]
T -->|"odpowiedź HTML"| K
Materiały
Jaka jest różnica między projektem (project) a aplikacją (app) w Django?
Odpowiedź w 30 sekund: Projekt to cała konfiguracja i kontener całej witryny — zawiera ustawienia, główny routing i spina wszystko w działającą całość. Aplikacja to wydzielony moduł realizujący jedną funkcję (np. blog, koszyk, konta). Projekt składa się z wielu aplikacji, a dobrze zaprojektowana aplikacja jest na tyle samodzielna, że da się ją ponownie użyć w innym projekcie.
Odpowiedź w 2 minuty:
Projekt (project) to najwyższy poziom organizacji w Django. Generuje go django-admin startproject i zawiera plik settings.py (konfiguracja), główny urls.py (routing), pliki wsgi.py/asgi.py (punkty wejścia serwera) oraz manage.py. Projekt sam w sobie nie realizuje funkcji biznesowych — jest spoiwem, które łączy aplikacje i konfiguruje całość (baza danych, zainstalowane aplikacje, middleware, szablony).
Aplikacja (app) to moduł realizujący konkretny obszar funkcjonalny. Tworzy ją python manage.py startapp i zawiera własne models.py, views.py, urls.py, admin.py, migracje itd. Filozofia Django zakłada, że aplikacja powinna „robić jedną rzecz i robić ją dobrze" — np. osobna aplikacja na blog, osobna na sklep, osobna na konta użytkowników. Aby aplikacja była aktywna, dodaje się ją do INSTALLED_APPS w ustawieniach projektu.
Kluczowa relacja: jeden projekt zawiera wiele aplikacji, a jedna dobrze zaprojektowana aplikacja może działać w wielu projektach (reużywalność — tak działają pakiety z PyPI, np. django-allauth). To zachęca do modularności i czystego podziału odpowiedzialności. Pułapki: tworzenie jednej gigantycznej aplikacji „na wszystko" (trudna w utrzymaniu i nieprzenośna) lub odwrotnie — rozdrabnianie na zbyt wiele mikro-aplikacji o silnych wzajemnych zależnościach. Dobra granica aplikacji to spójny zestaw modeli i logiki wokół jednego pojęcia domenowego.
Diagram:
flowchart TD
P["Projekt (settings, główny urls.py, wsgi/asgi)"]
P --> A1["Aplikacja: blog"]
P --> A2["Aplikacja: sklep"]
P --> A3["Aplikacja: konta"]
A1 --> M1["models / views / urls / admin"]
A2 --> M2["models / views / urls / admin"]
A3 --> M3["models / views / urls / admin"]
Materiały
Do czego służy plik settings.py i jakie są jego najważniejsze ustawienia?
Odpowiedź w 30 sekund:
settings.py to centralny plik konfiguracji projektu Django — definiuje bazę danych, zainstalowane aplikacje, middleware, szablony, pliki statyczne, lokalizację i ustawienia bezpieczeństwa. To pojedyncze źródło prawdy o tym, jak skonfigurowana jest cała aplikacja.
Odpowiedź w 2 minuty:
settings.py zawiera ustawienia wczytywane przez Django przy starcie. Do najważniejszych należą: DEBUG (tryb deweloperski vs produkcyjny — na produkcji MUSI być False), SECRET_KEY (klucz do podpisów kryptograficznych — tajny), ALLOWED_HOSTS (dozwolone domeny), INSTALLED_APPS (lista aktywnych aplikacji, w tym własnych i wbudowanych jak django.contrib.admin), MIDDLEWARE (łańcuch warstw przetwarzających żądania) oraz DATABASES (konfiguracja połączenia z bazą).
Dalej konfiguruje się: TEMPLATES (silnik i katalogi szablonów), STATIC_URL/STATIC_ROOT i MEDIA_URL/MEDIA_ROOT (pliki statyczne i media), ROOT_URLCONF (moduł z głównym routingiem), ustawienia lokalizacji (LANGUAGE_CODE, TIME_ZONE, USE_I18N, USE_TZ) oraz AUTH_USER_MODEL (jeśli używamy własnego modelu użytkownika). Każde z tych ustawień wpływa na zachowanie całego frameworka.
Dobra praktyka w realnych projektach to rozdzielenie konfiguracji per środowisko (osobne ustawienia dla developmentu, testów, produkcji) oraz wstrzykiwanie sekretów i różnic przez zmienne środowiskowe — SECRET_KEY, dane bazy czy klucze API nigdy nie powinny trafiać na sztywno do repozytorium. Popularne podejścia to podział na pliki settings/base.py, settings/prod.py, settings/dev.py albo biblioteki jak django-environ. Pułapki: trzymanie DEBUG=True lub sekretów w repo na produkcji, oraz brak poprawnych ALLOWED_HOSTS (przy DEBUG=False Django odrzuci żądania). Przed wdrożeniem warto uruchomić python manage.py check --deploy.
Przykład kodu:
# settings.py - kluczowe ustawienia (uproszczone, z odczytem ze środowiska)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
DEBUG = os.environ.get("DEBUG", "False") == "True"
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # z env, nie w repo
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",")
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog", # własna aplikacja
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ.get("DB_HOST", "localhost"),
}
}
ROOT_URLCONF = "projekt.urls"
LANGUAGE_CODE = "pl"
TIME_ZONE = "Europe/Warsaw"
USE_TZ = True
Materiały
Czym różnią się narzędzia django-admin oraz manage.py i do czego ich używamy?
Odpowiedź w 30 sekund:
django-admin to narzędzie wiersza poleceń instalowane razem z Django, służące głównie do utworzenia nowego projektu. manage.py to wygenerowany w projekcie skrypt, który robi to samo, ale automatycznie wskazuje ustawienia projektu — dlatego w codziennej pracy nad konkretnym projektem używamy manage.py.
Odpowiedź w 2 minuty:
django-admin to globalne polecenie dostępne po instalacji Django. Używamy go przede wszystkim wtedy, gdy projekt jeszcze nie istnieje — najczęściej do django-admin startproject nazwa. Może wykonywać te same komendy zarządzające co manage.py, ale nie wie z automatu, gdzie szukać ustawień, więc trzeba mu wskazać moduł ustawień przez zmienną DJANGO_SETTINGS_MODULE.
manage.py to cienki skrypt tworzony automatycznie w katalogu projektu przez startproject. Jego kluczowa zaleta to to, że sam ustawia DJANGO_SETTINGS_MODULE na ustawienia danego projektu, zanim przekaże sterowanie do django-admin. Dzięki temu w pracy nad istniejącym projektem po prostu uruchamiamy python manage.py <komenda> bez ręcznego konfigurowania środowiska. To kanoniczny sposób uruchamiania poleceń zarządzających.
Najczęściej używane komendy to: runserver (serwer deweloperski), makemigrations i migrate (migracje schematu bazy), createsuperuser (konto administratora), shell (interaktywna konsola z załadowanym Django), startapp (nowa aplikacja), collectstatic (zebranie plików statycznych), test (uruchomienie testów) oraz check (audyt konfiguracji). Można też pisać własne komendy zarządzające. Pułapki: mylenie obu narzędzi (do startu projektu — django-admin, do reszty — manage.py), zapominanie o wirtualnym środowisku oraz używanie runserver na produkcji (jest tylko do developmentu).
Przykład kodu:
# django-admin: tworzenie nowego projektu (gdy jeszcze nie istnieje)
django-admin startproject mojprojekt
# manage.py: codzienna praca w istniejącym projekcie
python manage.py startapp blog # nowa aplikacja
python manage.py makemigrations # generowanie migracji
python manage.py migrate # zastosowanie migracji
python manage.py createsuperuser # konto administratora
python manage.py runserver # serwer deweloperski
python manage.py shell # konsola z załadowanym Django
python manage.py test # uruchomienie testów
Materiały
Czym różni się WSGI od ASGI i kiedy w Django potrzebny jest ASGI?
Odpowiedź w 30 sekund:
WSGI to synchroniczny standard komunikacji między serwerem WWW a aplikacją Python — jedno żądanie na raz w wątku. ASGI to jego asynchroniczny następca, obsługujący async/await, długotrwałe połączenia i WebSockety. ASGI potrzebujesz, gdy używasz widoków asynchronicznych, WebSocketów, streamingu lub dużej liczby równoległych połączeń I/O.
Odpowiedź w 2 minuty:
WSGI (Web Server Gateway Interface) to wieloletni standard łączący serwery (Gunicorn, uWSGI) z aplikacjami Pythona. Jest synchroniczny: każde żądanie jest obsługiwane przez wątek/proces, który jest zajęty do końca obsługi. To proste i sprawdzone, świetnie nadaje się do klasycznych aplikacji renderujących HTML i operacji ograniczonych przez CPU lub szybką bazę. Punktem wejścia jest wsgi.py i obiekt application.
ASGI (Asynchronous Server Gateway Interface) to nowszy standard zaprojektowany pod programowanie asynchroniczne. Obsługuje async/await, dzięki czemu jeden proces może równolegle „trzymać" wiele oczekujących połączeń bez blokowania wątku na czas operacji I/O. Co ważne, ASGI obejmuje nie tylko HTTP, ale też protokoły długotrwałe — przede wszystkim WebSockety — których WSGI w ogóle nie obsługuje. Punktem wejścia jest asgi.py, a serwery to np. Uvicorn, Daphne czy Hypercorn. ASGI jest wstecznie zgodny: aplikacja ASGI uruchomi też kod synchroniczny.
Kiedy wybrać ASGI? Gdy aplikacja używa widoków async, komunikacji w czasie rzeczywistym (WebSockety, np. przez Django Channels), streamingu odpowiedzi, albo gdy ma wiele równoległych połączeń czekających na zewnętrzne API (I/O-bound). Dla typowej aplikacji opartej na synchronicznym ORM i renderowaniu HTML WSGI jest w zupełności wystarczające i prostsze operacyjnie. Pułapki: ASGI nie przyspiesza zadań CPU-bound (te i tak blokują pętlę zdarzeń — należą do Celery); mieszanie kodu sync i async w widokach async wymaga ostrożności (sync_to_async, asynchroniczny ORM), a sam fakt uruchomienia pod ASGI nie czyni magicznie kodu szybszym, jeśli pozostaje synchroniczny. Wybór to decyzja architektoniczna zależna od realnych potrzeb, nie moda.
Diagram:
flowchart TD
subgraph WSGI["WSGI (synchroniczny)"]
W1["Żądanie"] --> W2["Wątek zajęty do końca obsługi"]
W2 --> W3["Odpowiedź HTTP"]
end
subgraph ASGI["ASGI (asynchroniczny)"]
A1["Żądanie HTTP / WebSocket"] --> A2["Pętla zdarzeń (await zwalnia wątek)"]
A2 --> A3["Wiele połączeń równolegle"]
A3 --> A4["Odpowiedź / strumień / WebSocket"]
end
| Cecha | WSGI | ASGI |
|---|---|---|
| Model | synchroniczny | asynchroniczny (+ sync) |
| WebSockety | nie | tak |
| Widoki async | brak korzyści | pełne wsparcie |
| Serwery | Gunicorn, uWSGI | Uvicorn, Daphne, Hypercorn |
| Punkt wejścia | wsgi.py |
asgi.py |
| Najlepsze do | klasyczne aplikacje HTML | real-time, I/O-bound, streaming |
Materiały
Modele i migracje
Czym jest model w Django i jak go definiujemy?
Odpowiedź w 30 sekund:
Model to klasa Pythona dziedzicząca po django.db.models.Model, która reprezentuje pojedynczą tabelę w bazie danych — jej atrybuty (pola) odpowiadają kolumnom. Model jest jednocześnie definicją struktury danych i warstwą dostępu do nich przez ORM, dzięki czemu pracujesz z obiektami Pythona zamiast pisać SQL.
Odpowiedź w 2 minuty:
Każdy model definiujesz jako klasę dziedziczącą po models.Model. Pola deklarujesz jako atrybuty klasy, używając typów pól (CharField, IntegerField, DateTimeField itd.). Django na podstawie tej definicji generuje schemat bazy (przez migracje) oraz udostępnia API zapytań — np. Article.objects.filter(...). Domyślnie Django automatycznie dodaje klucz główny id typu auto-increment, jeśli go nie zdefiniujesz.
Modele to centralne miejsce logiki danych w aplikacji. Tu definiujesz nie tylko strukturę, ale i metody operujące na pojedynczym rekordzie (np. def is_recent(self)), opcje porządkowania i unikalności (klasa Meta), oraz reprezentację tekstową (__str__), która ułatwia pracę w panelu admina i konsoli. Dobra praktyka mówi „grube modele, chude widoki" — logikę domenową umieszczaj bliżej modeli.
Po zdefiniowaniu modelu trzeba wygenerować i zastosować migracje (makemigrations, migrate), aby zmiany trafiły do bazy. Typowe pułapki: zapominanie o migracjach po zmianie modelu, brak metody __str__ (utrudnia debugowanie), oraz przechowywanie logiki biznesowej w widokach zamiast w modelach. Pamiętaj też, że zmiana typu pola na istniejących danych produkcyjnych bywa ryzykowna i wymaga przemyślanej migracji.
Przykład kodu:
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200) # kolumna VARCHAR(200)
body = models.TextField() # długi tekst
published = models.BooleanField(default=False) # flaga publikacji
created_at = models.DateTimeField(auto_now_add=True) # ustawiane przy tworzeniu
class Meta:
ordering = ["-created_at"] # domyślne sortowanie: od najnowszych
def __str__(self):
# czytelna reprezentacja w adminie i konsoli
return self.title
Materiały
Jakie są najczęściej używane typy pól (Field) i jak dobrać właściwy do danych?
Odpowiedź w 30 sekund:
Najczęstsze pola to CharField (krótki tekst z max_length), TextField (długi tekst), IntegerField/DecimalField/FloatField (liczby), BooleanField, DateField/DateTimeField, EmailField, SlugField oraz pola relacji (ForeignKey, ManyToManyField, OneToOneField). Typ pola dobierasz do charakteru danych: tekst krótki czy długi, czy potrzebna precyzja liczbowa, czy to relacja do innego modelu.
Odpowiedź w 2 minuty:
Do tekstu używasz CharField (wymaga max_length) dla krótkich wartości jak tytuły czy nazwy oraz TextField dla dłuższych treści bez limitu. Specjalizowane warianty (EmailField, URLField, SlugField) to CharField z dodatkową walidacją formatu — warto z nich korzystać, bo dają walidację „za darmo".
Do liczb: IntegerField (liczby całkowite), BigIntegerField (duże wartości), PositiveIntegerField (nieujemne), FloatField (zmiennoprzecinkowe — UWAGA: niedokładne dla pieniędzy) oraz DecimalField (precyzyjne wartości dziesiętne — to właściwy wybór dla kwot, wymaga max_digits i decimal_places). Do dat: DateField, DateTimeField, TimeField, często z opcjami auto_now (aktualizacja przy każdym zapisie) lub auto_now_add (ustawienie przy utworzeniu).
Inne częste: BooleanField (prawda/fałsz), JSONField (struktury JSON, wspierane natywnie), FileField/ImageField (pliki i obrazy), oraz pola relacji. Większość pól przyjmuje wspólne opcje: null (czy kolumna może być NULL w bazie), blank (czy pole może być puste w formularzach — to walidacja, nie baza), default, unique, choices (ograniczenie do listy wartości), db_index (indeks). Typowa pułapka: mylenie null z blank — null dotyczy bazy danych, blank walidacji formularzy. Dla pól tekstowych nie ustawiaj null=True, bo Django zaleca pustą wartość "" zamiast NULL. Druga pułapka: FloatField zamiast DecimalField dla pieniędzy prowadzi do błędów zaokrągleń.
Przykład kodu:
from django.db import models
class Product(models.Model):
STATUS_CHOICES = [("draft", "Szkic"), ("active", "Aktywny")]
name = models.CharField(max_length=120) # krótki tekst
description = models.TextField(blank=True) # długi, opcjonalny
price = models.DecimalField(max_digits=10, decimal_places=2) # kwota — precyzja!
stock = models.PositiveIntegerField(default=0) # nieujemna liczba
slug = models.SlugField(unique=True) # przyjazny URL, unikalny
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
created_at = models.DateTimeField(auto_now_add=True) # ustawiane raz, przy tworzeniu
updated_at = models.DateTimeField(auto_now=True) # aktualizowane przy każdym zapisie
Materiały
Jak działają migracje w Django i czym różni się makemigrations od migrate?
Odpowiedź w 30 sekund:
Migracje to system kontroli wersji schematu bazy danych — przekładają zmiany w modelach na operacje na strukturze bazy. makemigrations analizuje zmiany w modelach i generuje pliki migracji (instrukcje zmian), natomiast migrate faktycznie wykonuje te instrukcje na bazie danych, modyfikując jej strukturę.
Odpowiedź w 2 minuty:
Migracje rozwiązują problem synchronizacji kodu modeli ze schematem bazy danych. Gdy zmieniasz model (dodajesz pole, zmieniasz typ, tworzysz nowy model), makemigrations porównuje aktualny stan modeli z poprzednim stanem zapisanym w migracjach i tworzy nowy plik migracji w katalogu migrations/ aplikacji. Ten plik to czytelny kod Pythona opisujący operacje (np. AddField, CreateModel). Migracje są wersjonowane i powinny trafiać do repozytorium — dzięki temu cały zespół i środowiska mają spójny schemat.
migrate to drugi krok: wykonuje niezastosowane jeszcze migracje na bazie danych, generując odpowiedni SQL i uruchamiając go. Django śledzi, które migracje już zastosowano, w tabeli django_migrations, więc migrate jest idempotentny — uruchomiony ponownie nie zrobi nic, jeśli wszystko jest aktualne. Migracje mają zależności (każda wskazuje poprzednią), co pozwala wykonywać je we właściwej kolejności także między aplikacjami.
Przydatne polecenia: sqlmigrate app 0001 pokazuje SQL danej migracji bez jej wykonywania, showmigrations listuje stan, a migrate app 0002 pozwala cofnąć się do wskazanej migracji (rollback). Typowe pułapki: edytowanie już zastosowanych migracji (psuje historię — zamiast tego twórz nowe), niezcommitowanie wygenerowanych migracji (rozjazd między środowiskami), oraz konflikty migracji przy pracy zespołowej (rozwiązywane przez makemigrations --merge). Na produkcji ryzykowne zmiany (usunięcie kolumny, zmiana typu) wymagają ostrożności, bo mogą zablokować tabelę lub spowodować utratę danych.
Diagram:
flowchart LR
M["Zmiana w models.py"] -->|"makemigrations"| F["Plik migracji (0002_add_field.py)"]
F -->|"migrate"| DB["Zmiana struktury bazy danych"]
F -.->|"sqlmigrate (podgląd)"| SQL["Wygenerowany SQL"]
DB --> T["Wpis w django_migrations (śledzenie stanu)"]
Przykład kodu:
# 1) Po zmianie modeli — wygeneruj pliki migracji
python manage.py makemigrations katalog
# 2) Podejrzyj SQL, który zostanie wykonany (bez zmian w bazie)
python manage.py sqlmigrate katalog 0002
# 3) Zastosuj migracje do bazy danych
python manage.py migrate
# Stan migracji i ewentualny rollback do migracji 0001
python manage.py showmigrations katalog
python manage.py migrate katalog 0001
Materiały
Jak definiujemy relacje między modelami (ForeignKey, ManyToManyField, OneToOneField)?
Odpowiedź w 30 sekund:
ForeignKey modeluje relację wiele-do-jednego (np. wiele artykułów ma jednego autora), ManyToManyField relację wiele-do-wielu (np. artykuł ma wiele tagów, a tag wiele artykułów), a OneToOneField relację jeden-do-jednego (np. profil rozszerzający użytkownika). Definiujesz je jako pola wskazujące na docelowy model i konfigurujesz zachowanie przy usuwaniu (on_delete).
Odpowiedź w 2 minuty:
ForeignKey to najczęstsza relacja — umieszczasz ją po stronie „wielu". Wymaga argumentu on_delete, który określa, co zrobić, gdy obiekt docelowy zostanie usunięty: CASCADE (usuń też powiązane), PROTECT (zablokuj usunięcie), SET_NULL (ustaw NULL — wymaga null=True), SET_DEFAULT, DO_NOTHING. Django automatycznie tworzy dostęp odwrotny (np. od autora do jego artykułów przez author.article_set lub nazwę z related_name).
ManyToManyField realizuje relację wiele-do-wielu, tworząc pod spodem ukrytą tabelę pośredniczącą (junction table). Definiujesz ją po jednej ze stron — Django udostępnia obustronny dostęp. Gdy potrzebujesz przechowywać dodatkowe dane o samym powiązaniu (np. datę dołączenia, rolę), używasz własnego modelu pośredniczącego przez argument through. Operacje na relacji to .add(), .remove(), .set(), .clear().
OneToOneField to w istocie ForeignKey z unique=True — gwarantuje, że każdemu rekordowi po jednej stronie odpowiada najwyżej jeden po drugiej. Typowe zastosowanie to rozszerzanie istniejącego modelu (np. profil użytkownika powiązany z User). Typowe pułapki: brak related_name przy wielu relacjach do tego samego modelu (kolizja nazw dostępu odwrotnego), nieprzemyślany on_delete=CASCADE na ważnych danych (kaskadowe usunięcie), oraz problem N+1 przy iterowaniu po relacjach bez select_related/prefetch_related.
Przykład kodu:
from django.db import models
from django.conf import settings
class Author(models.Model):
name = models.CharField(max_length=100)
class Tag(models.Model):
name = models.CharField(max_length=50)
class Article(models.Model):
title = models.CharField(max_length=200)
# wiele-do-jednego: wiele artykułów -> jeden autor
author = models.ForeignKey(
Author,
on_delete=models.PROTECT, # nie pozwól usunąć autora z artykułami
related_name="articles", # dostęp odwrotny: author.articles.all()
)
# wiele-do-wielu: artykuł <-> wiele tagów
tags = models.ManyToManyField(Tag, related_name="articles", blank=True)
class Profile(models.Model):
# jeden-do-jednego: rozszerzenie modelu użytkownika
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile"
)
bio = models.TextField(blank=True)
Diagram:
erDiagram
AUTHOR ||--o{ ARTICLE : "pisze (ForeignKey)"
ARTICLE }o--o{ TAG : "oznaczony (ManyToMany)"
USER ||--|| PROFILE : "ma (OneToOne)"
Materiały
Do czego służy klasa Meta w modelu i jakie opcje najczęściej w niej ustawiamy?
Odpowiedź w 30 sekund:
Klasa Meta to wewnętrzna klasa modelu służąca do konfiguracji metadanych, które nie są polami — m.in. domyślne sortowanie, nazwa tabeli, ograniczenia unikalności, indeksy, czytelne nazwy oraz to, czy model jest abstrakcyjny. Pozwala dostroić zachowanie modelu bez zmiany jego struktury danych.
Odpowiedź w 2 minuty:
Najczęściej ustawiane opcje to ordering (domyślna kolejność zwracanych obiektów, np. ["-created_at"]), verbose_name i verbose_name_plural (czytelne nazwy w panelu admina, ważne dla poprawnej polskiej odmiany liczby mnogiej), db_table (własna nazwa tabeli, gdy nie chcemy domyślnej), oraz abstract = True (model abstrakcyjny — baza dla innych modeli, nie tworzy własnej tabeli).
Do integralności danych służą constraints (ograniczenia na poziomie bazy, np. UniqueConstraint, CheckConstraint) oraz unique_together (starsza forma wymuszenia unikalności kombinacji pól — obecnie zaleca się UniqueConstraint w constraints). Wydajność wspierasz przez indexes (definiowanie indeksów bazodanowych, w tym złożonych i częściowych). Uprawnienia obiektowe konfigurujesz przez permissions i default_permissions.
Praktyczna uwaga: ustawienie ordering ma realny wpływ na wydajność, bo każde zapytanie zwracające listę będzie sortowane — przy dużych tabelach upewnij się, że pole sortowania ma indeks. Modele abstrakcyjne (abstract = True) to wygodny sposób na współdzielenie pól (np. created_at/updated_at) między wieloma modelami bez powielania kodu. Typowa pułapka: domyślne ordering potrafi nieoczekiwanie spowalniać zapytania i komplikować paginację oraz distinct(); w nowszych wersjach Django nadmierne poleganie na domyślnym sortowaniu bywa odradzane na rzecz jawnego order_by() tam, gdzie kolejność jest istotna.
Przykład kodu:
from django.db import models
class TimeStampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True # model bazowy — nie tworzy własnej tabeli
class Order(TimeStampedModel):
number = models.CharField(max_length=20)
customer = models.CharField(max_length=100)
total = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
ordering = ["-created_at"] # domyślnie od najnowszych
verbose_name = "Zamówienie" # czytelna nazwa w adminie
verbose_name_plural = "Zamówienia" # poprawna liczba mnoga
constraints = [
# numer zamówienia unikalny w obrębie klienta
models.UniqueConstraint(
fields=["number", "customer"], name="unique_order_per_customer"
),
]
indexes = [models.Index(fields=["customer", "-created_at"])]
Materiały
Jak radzisz sobie z migracjami danych (data migrations) i ryzykownymi zmianami schematu na produkcji?
Odpowiedź w 30 sekund:
Migracje danych tworzysz jako puste migracje z operacją RunPython, która przekształca istniejące rekordy (np. wypełnia nowe pole). Ryzykowne zmiany schematu na produkcji rozbijasz na bezpieczne, kompatybilne wstecz kroki, wykonujesz w transakcjach (gdy baza to wspiera) i wdrażasz etapowo, unikając długich blokad tabel.
Odpowiedź w 2 minuty:
Migracja danych różni się od migracji schematu: zamiast zmieniać strukturę, modyfikuje zawartość tabel. Generujesz pustą migrację (makemigrations --empty app) i dodajesz operację migrations.RunPython(forward, reverse), gdzie forward przekształca dane, a reverse cofa zmianę (dla rollbacku). Ważne: wewnątrz migracji używaj historycznej wersji modelu przez apps.get_model("app", "Model"), a nie bezpośredniego importu — gwarantuje to zgodność ze stanem schematu w danym momencie historii migracji.
Przy ryzykownych zmianach schematu na produkcji kluczowa jest strategia kompatybilna wstecz, zwłaszcza przy zero-downtime deploymencie. Klasyczny wzorzec dla zmiany niebezpiecznej (np. zmiana nazwy/typu kolumny, przeniesienie danych) to expand–migrate–contract: najpierw dodajesz nową strukturę (expand), nie usuwając starej; potem migrujesz dane i przełączasz kod, by używał nowej (migrate); na końcu, w osobnym wdrożeniu, usuwasz starą strukturę (contract). Dzięki temu w żadnym momencie działający kod nie odwołuje się do nieistniejącej kolumny.
Praktyki ograniczające ryzyko: dodawaj nowe kolumny jako null=True lub z wartością domyślną wyliczaną w aplikacji (nie kosztowny DEFAULT na całej tabeli), unikaj operacji blokujących dużą tabelę na długo (na PostgreSQL np. dodanie kolumny z DEFAULT w starszych wersjach przepisywało tabelę), przy masowych aktualizacjach przetwarzaj dane porcjami (batch), testuj migracje na kopii danych produkcyjnych i mierz czas. Pamiętaj, że migracje danych nie powinny zależeć od bieżącego kodu aplikacji — gdy model się zmieni, stara migracja musi nadal działać. Typowa pułapka: bezpośredni import modelu w migracji danych (psuje się po późniejszych zmianach modelu) oraz brak operacji odwrotnej, uniemożliwiający rollback.
Diagram:
flowchart TD
A["1. EXPAND: dodaj nową kolumnę/strukturę (null=True), bez usuwania starej"] --> B["2. Wdróż kod piszący do starej i nowej struktury"]
B --> C["3. MIGRATE: migracja danych RunPython — przenieś dane porcjami"]
C --> D["4. Wdróż kod czytający tylko z nowej struktury"]
D --> E["5. CONTRACT: usuń starą kolumnę w osobnym wdrożeniu"]
Przykład kodu:
# migrations/0005_fill_full_name.py
from django.db import migrations
def fill_full_name(apps, schema_editor):
# Historyczna wersja modelu — zgodna ze stanem schematu na ten moment
Person = apps.get_model("people", "Person")
# Przetwarzanie porcjami, by nie obciążyć bazy jednym wielkim UPDATE
for person in Person.objects.all().iterator(chunk_size=2000):
person.full_name = f"{person.first_name} {person.last_name}".strip()
person.save(update_fields=["full_name"])
def reverse(apps, schema_editor):
# Operacja odwrotna umożliwia rollback (tu: czyścimy pole)
Person = apps.get_model("people", "Person")
Person.objects.update(full_name="")
class Migration(migrations.Migration):
dependencies = [("people", "0004_person_full_name")]
operations = [migrations.RunPython(fill_full_name, reverse)]
Materiały
Django ORM i zapytania
Czym jest QuerySet i na czym polega jego leniwa ewaluacja (lazy evaluation)?
Odpowiedź w 30 sekund:
QuerySet to obiekt reprezentujący zbiór rekordów z bazy danych — to interfejs ORM do budowania zapytań. Leniwa ewaluacja oznacza, że samo utworzenie i filtrowanie QuerySetu NIE wykonuje zapytania do bazy; SQL uruchamia się dopiero w momencie, gdy faktycznie potrzebujesz danych (iteracja, list(), indeksowanie, len()).
Odpowiedź w 2 minuty:
QuerySet pozwala budować zapytania metodą łańcuchową: Article.objects.filter(published=True).exclude(author=None).order_by("-created_at"). Każda taka metoda zwraca nowy QuerySet, ale żadne z tych wywołań nie odpytuje jeszcze bazy. To właśnie leniwa ewaluacja — Django odkłada wykonanie zapytania do ostatniej chwili, dzięki czemu możesz dowolnie składać i przekazywać QuerySety bez kosztu, a finalne zapytanie SQL jest jedno i zoptymalizowane.
QuerySet jest „wymuszany" (evaluated) w konkretnych sytuacjach: iteracja w pętli for, konwersja list(qs), bool(qs) lub instrukcja if qs, len(qs), krojenie z krokiem, oraz repr() (np. w konsoli). Po pierwszym wykonaniu wyniki są cache'owane wewnątrz tego konkretnego obiektu QuerySet — ponowna iteracja po tym samym obiekcie nie odpytuje bazy. Ale uwaga: każde nowe wywołanie metody tworzy nowy QuerySet z własnym, pustym cache.
Praktyczne konsekwencje: leniwość pozwala budować zapytania warunkowo (np. dodawać filtry w zależności od parametrów) bez wielokrotnego odpytywania bazy. Typowe pułapki: wielokrotne wymuszanie tego samego logicznego zapytania w różnych miejscach (każde to osobny round-trip do bazy — lepiej raz zrzutować na listę), używanie len(qs) zamiast qs.count() gdy potrzebujesz tylko liczby (count nie ładuje obiektów do pamięci), oraz sprawdzanie istnienia przez if qs zamiast qs.exists(). Dla bardzo dużych zbiorów iterator() pozwala uniknąć ładowania wszystkiego do pamięci naraz.
Diagram:
flowchart LR
Q1["Article.objects.all()"] -->|".filter()"| Q2["QuerySet (bez zapytania)"]
Q2 -->|".exclude()"| Q3["QuerySet (bez zapytania)"]
Q3 -->|".order_by()"| Q4["QuerySet (bez zapytania)"]
Q4 -->|"iteracja / list() / len()"| DB["Wykonanie SQL i pobranie danych"]
DB --> C["Cache wewnątrz QuerySetu"]
Przykład kodu:
# Budowanie zapytania — żadnego round-tripa do bazy jeszcze nie ma
qs = Article.objects.filter(published=True)
qs = qs.exclude(author__isnull=True)
qs = qs.order_by("-created_at")
# Dopiero TUTAJ wykona się jedno zapytanie SQL (iteracja)
for article in qs:
print(article.title)
# Lepsze praktyki dla typowych przypadków:
total = Article.objects.filter(published=True).count() # SELECT COUNT(*), bez ładowania obiektów
has_any = Article.objects.filter(published=True).exists() # szybkie sprawdzenie istnienia
Materiały
Jaka jest różnica między filter() a get() i jak obsłużyć brak wyniku?
Odpowiedź w 30 sekund:
filter() zwraca QuerySet (zbiór, potencjalnie pusty) pasujących rekordów i nigdy nie rzuca wyjątku przy braku wyników. get() zwraca dokładnie jeden obiekt i rzuca DoesNotExist, gdy nic nie pasuje, oraz MultipleObjectsReturned, gdy pasuje więcej niż jeden rekord. get() używasz, gdy oczekujesz dokładnie jednego wyniku.
Odpowiedź w 2 minuty:
filter() służy do pobierania zbiorów — zawsze daje QuerySet, który może być pusty. Jest bezpieczny w użyciu, bo brak wyników nie powoduje błędu; po prostu iterujesz po pustym zbiorze. Używasz go wszędzie tam, gdzie spodziewasz się zera, jednego lub wielu rekordów.
get() służy do pobierania pojedynczego, konkretnego obiektu — zwykle po kluczu głównym lub innym unikalnym polu. Jeśli żaden rekord nie pasuje, rzuca wyjątek Model.DoesNotExist; jeśli pasuje więcej niż jeden, rzuca Model.MultipleObjectsReturned. Te wyjątki są celowe — sygnalizują, że założenie „dokładnie jeden wynik" zostało złamane.
Obsługa braku wyniku zależy od kontekstu. W widoku webowym, gdy brak obiektu powinien dać stronę 404, używasz skrótu get_object_or_404(), który automatycznie zamienia DoesNotExist na odpowiedź 404. W logice, gdzie brak wyniku jest normalną sytuacją, opakowujesz get() w try/except DoesNotExist albo używasz filter(...).first(), który zwraca pierwszy obiekt lub None. Typowe pułapki: używanie get() po polu nieunikalnym (ryzyko MultipleObjectsReturned), nieobsłużony DoesNotExist powodujący błąd 500, oraz nadużywanie try/except tam, gdzie czytelniejszy byłby first().
Przykład kodu:
from django.shortcuts import get_object_or_404
from django.core.exceptions import ObjectDoesNotExist
# filter() — zbiór, bezpieczny przy braku wyników
published = Article.objects.filter(published=True) # może być pusty QuerySet
# get() — dokładnie jeden obiekt, rzuca wyjątek przy 0 lub wielu wynikach
try:
article = Article.objects.get(slug="django-tips")
except Article.DoesNotExist:
article = None
except Article.MultipleObjectsReturned:
# slug powinien być unikalny — to sygnał błędu w danych
raise
# W widoku — automatyczne 404 przy braku obiektu
article = get_object_or_404(Article, slug="django-tips")
# Alternatywa bez wyjątku — first() zwraca obiekt lub None
maybe = Article.objects.filter(slug="django-tips").first()
Materiały
Czym jest problem N+1 zapytań i jak go rozpoznać oraz rozwiązać?
Odpowiedź w 30 sekund:
Problem N+1 występuje, gdy aplikacja wykonuje jedno zapytanie po listę N obiektów, a potem osobne zapytanie dla każdego z nich, by pobrać dane z relacji — razem N+1 zapytań zamiast jednego lub dwóch. Rozwiązujesz go przez select_related (dla relacji ForeignKey/OneToOne) i prefetch_related (dla relacji wiele-do-wielu i odwrotnych ForeignKey).
Odpowiedź w 2 minuty:
Klasyczny scenariusz N+1: pobierasz listę artykułów (1 zapytanie), a następnie w pętli odwołujesz się do article.author.name. Ponieważ autor nie był pobrany z artykułem, Django wykonuje osobne zapytanie dla każdego artykułu — przy 100 artykułach to 1 + 100 = 101 zapytań. Efekt: poprawnie działająca strona, która dramatycznie zwalnia wraz ze wzrostem liczby rekordów.
Rozpoznasz problem na kilka sposobów: przez Django Debug Toolbar (pokazuje liczbę i treść zapytań na stronie — duplikaty od razu rzucają się w oczy), przez logowanie SQL (django.db.backends na poziomie DEBUG), albo przez asercje w testach (assertNumQueries). Sygnał ostrzegawczy to liczba zapytań rosnąca proporcjonalnie do liczby wyświetlanych rekordów.
Rozwiązanie zależy od typu relacji. select_related wykonuje JOIN i pobiera powiązane obiekty w tym samym zapytaniu — działa dla relacji „do jednego" (ForeignKey, OneToOneField). prefetch_related wykonuje osobne, ale tylko jedno dodatkowe zapytanie na relację i łączy dane w Pythonie — działa dla relacji „do wielu" (ManyToManyField, odwrotny ForeignKey). Można je łączyć i zagnieżdżać (np. select_related("author").prefetch_related("tags")). Typowa pułapka: nadmierny select_related z wieloma JOIN-ami na szerokich tabelach potrafi spowolnić zapytanie; oraz mylenie zastosowań — select_related na relacji wiele-do-wielu nie zadziała. Do precyzyjnej kontroli prefetchowanego zapytania służy obiekt Prefetch.
Diagram:
flowchart TD
subgraph BEZ["Bez optymalizacji (N+1)"]
A1["1 zapytanie: lista artykułów"] --> A2["+N zapytań: autor każdego artykułu"]
end
subgraph Z["Z select_related"]
B1["1 zapytanie z JOIN: artykuły + autorzy"]
end
A2 -.->|"select_related('author')"| B1
Przykład kodu:
# PROBLEM N+1: 1 zapytanie + po jednym na każdego autora w pętli
for article in Article.objects.all():
print(article.author.name) # ukryte zapytanie przy każdej iteracji
# ROZWIĄZANIE 1: select_related — JOIN, jedno zapytanie (relacja do jednego)
for article in Article.objects.select_related("author"):
print(article.author.name) # autor już pobrany, brak dodatkowych zapytań
# ROZWIĄZANIE 2: prefetch_related — relacja do wielu (tagi)
for article in Article.objects.prefetch_related("tags"):
print([t.name for t in article.tags.all()]) # 2 zapytania łącznie
# Wykrywanie w teście
from django.test import TestCase
class QueryTest(TestCase):
def test_no_n_plus_one(self):
with self.assertNumQueries(1): # oczekujemy dokładnie 1 zapytania
list(Article.objects.select_related("author"))
Materiały
Jaka jest różnica między select_related a prefetch_related?
Odpowiedź w 30 sekund:
select_related wykonuje pojedyncze zapytanie z JOIN i działa dla relacji „do jednego" (ForeignKey, OneToOneField). prefetch_related wykonuje osobne zapytanie dla każdej relacji i łączy wyniki w Pythonie — działa dla relacji „do wielu" (ManyToManyField, odwrotny ForeignKey). Pierwszy wykorzystuje JOIN w SQL, drugi dodatkowe zapytania i scalanie po stronie aplikacji.
Odpowiedź w 2 minuty:
select_related rozszerza zapytanie o JOIN, pobierając powiązane obiekty razem z głównymi w jednym zapytaniu SQL. Sprawdza się przy relacjach prowadzących do pojedynczego obiektu — gdy z artykułu chcesz dostać jego autora czy kategorię. Zaleta: jeden round-trip do bazy. Wada: przy wielu lub głębokich JOIN-ach oraz szerokich tabelach zapytanie może się rozrosnąć i spowolnić, a dane z tabel „po jednej" stronie potrafią się powielać w wynikach.
prefetch_related działa inaczej: dla każdej wskazanej relacji wykonuje osobne zapytanie (np. jedno po artykuły, drugie po wszystkie ich tagi), a następnie łączy wyniki w pamięci Pythona. To jedyna opcja dla relacji wiele-do-wielu i odwrotnych ForeignKey, bo tam JOIN powodowałby zwielokrotnienie wierszy. Zaleta: stała, mała liczba zapytań niezależnie od liczby obiektów. Wada: scalanie w Pythonie zużywa pamięć, a przy bardzo dużych zbiorach bywa kosztowne.
W praktyce często łączysz oba: Article.objects.select_related("author").prefetch_related("tags"). Dla precyzyjnej kontroli prefetchowanego QuerySetu (np. filtrowanie lub własne sortowanie powiązanych obiektów) używasz obiektu Prefetch. Typowe pułapki: użycie select_related na relacji wiele-do-wielu (nie zadziała), nadmierne prefetchowanie relacji, których i tak nie używasz (zbędne zapytania), oraz zakładanie, że jedno rozwiązuje wszystko — wybór zależy od kierunku i krotności relacji.
| Cecha | select_related | prefetch_related |
|---|---|---|
| Mechanizm | JOIN w jednym zapytaniu SQL | osobne zapytania + scalanie w Pythonie |
| Typy relacji | ForeignKey, OneToOneField (do jednego) | ManyToMany, odwrotny ForeignKey (do wielu) |
| Liczba zapytań | 1 | 1 na każdą relację |
| Koszt | szeroki JOIN, możliwe duplikaty | zużycie pamięci przy scalaniu |
Przykład kodu:
from django.db.models import Prefetch
# select_related — relacje "do jednego", jeden JOIN
articles = Article.objects.select_related("author", "category")
# prefetch_related — relacje "do wielu"
articles = Article.objects.prefetch_related("tags", "comments")
# Łączenie obu + kontrola prefetchowanego QuerySetu przez Prefetch
articles = (
Article.objects
.select_related("author") # autor przez JOIN
.prefetch_related(
Prefetch(
"comments",
queryset=Comment.objects.filter(approved=True), # tylko zaakceptowane
)
)
)
Materiały
Do czego służą obiekty F() i Q() w zapytaniach?
Odpowiedź w 30 sekund:
F() odwołuje się do wartości pola modelu bezpośrednio w bazie danych, pozwalając wykonywać operacje na kolumnach bez wczytywania wartości do Pythona — np. atomowy inkrement licznika lub porównanie dwóch pól. Q() reprezentuje warunek zapytania i umożliwia budowanie złożonej logiki z operatorami OR (|), AND (&) i NOT (~), czego zwykłe filter() z argumentami nazwanymi nie pozwala wyrazić.
Odpowiedź w 2 minuty:
F() to wyrażenie odnoszące się do wartości pola po stronie bazy danych. Jego główne zalety to atomowość i wydajność. Przy obj.views = F("views") + 1; obj.save() inkrementacja dzieje się w jednej operacji SQL (UPDATE ... SET views = views + 1), bez wczytywania bieżącej wartości — eliminuje to wyścig (race condition) między równoległymi żądaniami. F() pozwala też porównywać pola tego samego rekordu (np. filter(sold__gt=F("stock"))) i wykonywać operacje arytmetyczne na poziomie bazy.
Q() rozwiązuje ograniczenie zwykłego filtrowania: argumenty nazwane w filter() zawsze łączą się operatorem AND. Gdy potrzebujesz OR, negacji lub złożonych nawiasowanych warunków, używasz obiektów Q. Łączysz je operatorami | (OR), & (AND) i ~ (NOT), budując wyrażenia logiczne dowolnej złożoności. To niezbędne np. przy wyszukiwarkach, gdzie chcesz dopasować rekord po tytule LUB treści.
Praktyczne zastosowania: F() do liczników (wyświetlenia, polubienia), aktualizacji masowych (Product.objects.update(price=F("price") * Decimal("1.1"))) i porównań pól; Q() do filtrów warunkowych i wyszukiwania pełnotekstowego po wielu polach. Typowe pułapki: po użyciu F() w zapisie obiekt w pamięci nie zna nowej wartości, dopóki nie wykonasz refresh_from_db(); mieszanie argumentów pozycyjnych Q() z nazwanymi w filter() wymaga, by obiekty Q były przed argumentami nazwanymi. Nadużywanie update() z F() pomija sygnały save() — pamiętaj o tym, jeśli logika zależy od sygnałów.
Przykład kodu:
from django.db.models import F, Q
# F() — atomowy inkrement licznika, bez wyścigu i bez wczytywania wartości
Article.objects.filter(pk=pk).update(views=F("views") + 1)
# F() — porównanie dwóch pól tego samego rekordu
oversold = Product.objects.filter(sold__gt=F("stock"))
# F() — masowa podwyżka cen po stronie bazy
Product.objects.update(price=F("price") * Decimal("1.10"))
# Q() — warunek OR (tytuł LUB treść zawiera frazę)
results = Article.objects.filter(
Q(title__icontains="django") | Q(body__icontains="django")
)
# Q() — złożona logika: opublikowane ORAZ (autor X LUB wyróżnione)
qs = Article.objects.filter(
Q(published=True) & (Q(author__name="Anna") | Q(featured=True))
)
# Q() z negacją (~) — opublikowane, ale nie wyróżnione
qs = Article.objects.filter(Q(published=True) & ~Q(featured=True))
Materiały
Jak wykonać agregację i adnotację danych (aggregate, annotate)?
Odpowiedź w 30 sekund:
aggregate() zwraca pojedynczy słownik z wartościami zbiorczymi dla całego QuerySetu (np. średnia cena wszystkich produktów). annotate() dodaje wyliczoną wartość do KAŻDEGO obiektu w QuerySecie (np. liczba komentarzy przy każdym artykule), zwykle w połączeniu z grupowaniem. Oba korzystają z funkcji agregujących jak Count, Sum, Avg, Max, Min.
Odpowiedź w 2 minuty:
aggregate() służy do obliczeń na całym zbiorze i zwraca słownik z wynikami — np. Order.objects.aggregate(Sum("total"), Avg("total")) da łączną i średnią wartość zamówień. Wykonuje się natychmiast (jest „terminalny"), bo zwraca konkretne liczby, a nie QuerySet. Klucze w wyniku możesz nazwać samodzielnie (aggregate(suma=Sum("total"))).
annotate() dokłada do każdego rekordu nową, wyliczoną „kolumnę". Najczęstsze użycie to zliczanie powiązanych obiektów: Author.objects.annotate(num_articles=Count("articles")) doda do każdego autora pole num_articles. Pod spodem Django generuje GROUP BY. Po adnotacji możesz po niej filtrować (.filter(num_articles__gt=5)) i sortować (.order_by("-num_articles")) — to potężny mechanizm raportowania bez surowego SQL.
Funkcje agregujące możesz łączyć z F() (operacje na polach), Q() (warunkowe zliczanie przez argument filter, np. Count("articles", filter=Q(articles__published=True))), oraz z Case/When dla logiki warunkowej. Typowe pułapki: łączenie wielu annotate(Count(...)) na różnych relacjach w jednym zapytaniu powoduje zwielokrotnienie wierszy przez JOIN-y i zawyżone liczby — rozwiązaniem jest distinct=True w Count lub rozbicie na osobne zapytania/prefetch; mylenie kolejności filter() przed i po annotate() (filtr przed adnotacją ogranicza zbiór wejściowy, filtr po adnotacji działa na wyliczonej wartości). Pamiętaj też, że domyślne ordering z Meta może niespodziewanie wpłynąć na GROUP BY.
Przykład kodu:
from django.db.models import Count, Sum, Avg, Q
# aggregate — jedna wartość zbiorcza dla całego zbioru (zwraca słownik)
stats = Order.objects.aggregate(
suma=Sum("total"),
srednia=Avg("total"),
liczba=Count("id"),
)
# -> {"suma": Decimal("12500.00"), "srednia": ..., "liczba": 42}
# annotate — wyliczona wartość przy KAŻDYM obiekcie + filtrowanie i sortowanie
popularni = (
Author.objects
.annotate(liczba_artykulow=Count("articles")) # GROUP BY autor
.filter(liczba_artykulow__gte=3) # filtr po wyliczonej wartości
.order_by("-liczba_artykulow")
)
# Warunkowe zliczanie przez filter w Count (tylko opublikowane)
autorzy = Author.objects.annotate(
opublikowane=Count("articles", filter=Q(articles__published=True))
)
# Unikanie zawyżonych liczb przy wielu relacjach — distinct=True
qs = Author.objects.annotate(tagi=Count("articles__tags", distinct=True))
Materiały
Widoki (Views)
Jaka jest różnica między widokami funkcyjnymi (FBV) a klasowymi (CBV)?
Odpowiedź w 30 sekund:
Widok funkcyjny (FBV) to zwykła funkcja Pythona przyjmująca request i zwracająca odpowiedź — prosty, jawny i łatwy do odczytania. Widok klasowy (CBV) to klasa, która organizuje logikę wokół metod HTTP (get, post) i pozwala na dziedziczenie oraz miksiny, co sprzyja ponownemu wykorzystaniu kodu. FBV są bardziej bezpośrednie, CBV bardziej rozszerzalne.
Odpowiedź w 2 minuty:
Widoki funkcyjne (Function-Based Views) to najstarsze i najprostsze podejście. Funkcja dostaje obiekt request, samodzielnie obsługuje metody HTTP (przez sprawdzanie request.method) i zwraca HttpResponse. Ich zaleta to czytelność i jawność — cały przepływ widać w jednej funkcji, łatwo prześledzić, co się dzieje. Dekoratory (@login_required, @require_http_methods) dodają zachowania w przejrzysty sposób.
Widoki klasowe (Class-Based Views) organizują logikę jako klasę z metodami odpowiadającymi metodom HTTP — get() obsługuje GET, post() obsługuje POST itd. Dzięki dziedziczeniu i miksinom pozwalają wydzielać i ponownie wykorzystywać wspólne zachowania. Django dostarcza bogatą hierarchię gotowych CBV (widoki generyczne) dla typowych operacji CRUD, co drastycznie redukuje powtarzalny kod. CBV podpinasz w URL-ach przez .as_view().
Wybór zależy od kontekstu. FBV są lepsze dla nietypowej, jednorazowej logiki, gdzie czytelność liczy się bardziej niż reużywalność, oraz dla osób uczących się Django. CBV są lepsze dla powtarzalnych wzorców (listy, szczegóły, formularze CRUD) i gdy chcesz dzielić logikę między widokami przez miksiny. Typowa pułapka CBV: ich „magia" i wielopoziomowe dziedziczenie utrudniają zrozumienie, skąd pochodzi dane zachowanie — pomaga narzędzie Classy Class-Based Views (CCBV) do podglądu metod. Pułapka FBV: duplikacja kodu przy wielu podobnych widokach.
| Cecha | FBV (funkcyjne) | CBV (klasowe) |
|---|---|---|
| Forma | funkcja | klasa z metodami HTTP |
| Czytelność | wysoka, jawna | niższa (dziedziczenie) |
| Reużywalność | przez dekoratory | przez dziedziczenie i miksiny |
| Najlepsze do | nietypowej logiki | powtarzalnych wzorców CRUD |
Przykład kodu:
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.views import View
# FBV — funkcja, jawna obsługa metod
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, "articles/detail.html", {"article": article})
# CBV — klasa, metody odpowiadają metodom HTTP
class ArticleDetailView(View):
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, "articles/detail.html", {"article": article})
# urls.py
# path("article/<int:pk>/", article_detail) # FBV
# path("article/<int:pk>/", ArticleDetailView.as_view()) # CBV
Materiały
Czym są generyczne widoki klasowe (Generic Class-Based Views) i jakie problemy rozwiązują?
Odpowiedź w 30 sekund:
Generyczne widoki klasowe to gotowe klasy Django realizujące najczęstsze operacje webowe — wyświetlanie listy obiektów (ListView), szczegółów (DetailView), tworzenie (CreateView), edycję (UpdateView) i usuwanie (DeleteView). Rozwiązują problem powtarzalnego kodu: zamiast pisać te same wzorce CRUD w kółko, konfigurujesz gotowy widok kilkoma atrybutami.
Odpowiedź w 2 minuty: Większość aplikacji webowych powtarza te same czynności: pokaż listę rekordów, pokaż szczegóły jednego, dodaj nowy przez formularz, edytuj, usuń. Pisanie tego ręcznie za każdym razem to mnóstwo niemal identycznego kodu. Generyczne widoki klasowe (Generic CBV) hermetyzują te wzorce — wystarczy wskazać model lub QuerySet i ewentualnie szablon, a widok zajmie się resztą: pobraniem danych, obsługą formularza, walidacją, przekierowaniem po sukcesie.
Najważniejsze widoki dzielą się na grupy. Widoki wyświetlania: ListView (lista z paginacją) i DetailView (pojedynczy obiekt). Widoki edycji: CreateView, UpdateView, DeleteView (operacje na formularzach i bazie). Widoki bazowe: View, TemplateView (renderowanie szablonu), RedirectView. Każdy z nich udostępnia punkty rozszerzeń — metody takie jak get_queryset() (filtrowanie danych), get_context_data() (dodatkowy kontekst), form_valid() (logika po poprawnym formularzu), które nadpisujesz, by dostosować zachowanie.
Korzyść to ogromna oszczędność kodu i spójność. Pułapka to ukryta złożoność: zachowanie pochodzi z wielu klas bazowych i miksinów, więc gdy potrzebujesz nietypowej logiki, trzeba wiedzieć, którą metodę nadpisać — bez tego łatwo „walczyć z frameworkiem". Pomaga referencja CCBV (Classy Class-Based Views) pokazująca pełną hierarchię metod. Druga pułapka: zmuszanie generycznego widoku do bardzo nietypowych przepływów, gdzie czytelniejszy byłby zwykły FBV lub bazowy View.
Diagram:
flowchart TD
V["View (bazowy)"] --> TV["TemplateView"]
V --> LV["ListView (lista + paginacja)"]
V --> DV["DetailView (jeden obiekt)"]
V --> CV["CreateView (formularz tworzenia)"]
V --> UV["UpdateView (formularz edycji)"]
V --> DEL["DeleteView (potwierdzenie + usunięcie)"]
Przykład kodu:
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView
class ArticleListView(ListView):
model = Article
paginate_by = 20 # paginacja "za darmo"
# szablon domyślny: articles/article_list.html
def get_queryset(self):
# punkt rozszerzenia — pokazujemy tylko opublikowane
return super().get_queryset().filter(published=True)
class ArticleDetailView(DetailView):
model = Article # automatyczne pobranie po pk/slug z URL
class ArticleCreateView(CreateView):
model = Article
fields = ["title", "body", "published"] # wygeneruj ModelForm z tych pól
success_url = reverse_lazy("article-list")
def form_valid(self, form):
form.instance.author = self.request.user # ustaw autora przed zapisem
return super().form_valid(form)
Materiały
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziURL routing, middleware i cykl żądania
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziSzablony Django
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziFormularze i walidacja
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziDjango REST Framework
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziUwierzytelnianie i autoryzacja
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziBezpieczeństwo
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziTestowanie
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziWydajność, cache i zadania asynchroniczne
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziSygnały, transakcje i wdrożenie
Odpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 48 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi