Fiszki Online Django (Preview)
Darmowy podgląd 15 z 68 dostępnych pytań
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
↑ Powrót na góręModele i migracje
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
↑ Powrót na górę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
↑ Powrót na góręWidoki (Views)
Jak działają miksiny (mixins) w widokach klasowych?
Odpowiedź w 30 sekund:
Miksiny to małe klasy dostarczające pojedyncze, reużywalne zachowanie, które „domieszowujesz" do widoku przez wielodziedziczenie. Dzięki nim łączysz funkcjonalności — np. wymóg logowania (LoginRequiredMixin) z listą obiektów (ListView) — bez powielania kodu. Działają w oparciu o kolejność rozwiązywania metod (MRO) w Pythonie.
Odpowiedź w 2 minuty:
Miksin to klasa zaprojektowana nie do samodzielnego użycia, lecz do dodania jednej cechy do innej klasy. W widokach klasowych Django generyczne widoki same są zbudowane z miksinów (np. ContextMixin, SingleObjectMixin, FormMixin). Ty również możesz dodawać gotowe lub własne miksiny, by wzbogacić widok. Najczęstsze wbudowane to LoginRequiredMixin (wymóg zalogowania), PermissionRequiredMixin (wymóg uprawnień) i UserPassesTestMixin (własny warunek dostępu).
Mechanizm opiera się na wielodziedziczeniu i MRO (Method Resolution Order) — Pythonowej kolejności przeszukiwania klas bazowych. Kluczowa zasada praktyczna: miksiny umieszczasz PRZED główną klasą widoku w deklaracji dziedziczenia, np. class MyView(LoginRequiredMixin, ListView). Dzięki temu metody miksinu (np. sprawdzenie uprawnień w dispatch()) wykonają się przed logiką widoku. Odwrotna kolejność spowoduje, że miksin nie zadziała poprawnie.
Własny miksin tworzysz, gdy chcesz współdzielić zachowanie między wieloma widokami — np. dodawanie wspólnego kontekstu, filtrowanie po właścicielu, czy logowanie dostępu. Często nadpisujesz metody takie jak dispatch(), get_context_data() czy get_queryset(), pamiętając o wywołaniu super(), by nie przerwać łańcucha. Typowe pułapki: zła kolejność miksinów (najczęstszy błąd), zapominanie o super() (urywa łańcuch wywołań i psuje inne miksiny), oraz nadmierne stosy miksinów, które stają się trudne do prześledzenia. Reguła: miksiny mają być małe i jednozadaniowe.
Diagram:
flowchart LR
M1["LoginRequiredMixin (kontrola dostępu)"] --> M2["WłasnyMixin (wspólny kontekst)"]
M2 --> LV["ListView (logika listy)"]
LV --> RES["Gotowy widok = suma zachowań (wg MRO)"]
Przykład kodu:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
# Własny miksin — jedno, reużywalne zachowanie
class OwnerQuerysetMixin:
"""Ogranicza queryset do obiektów należących do zalogowanego użytkownika."""
def get_queryset(self):
qs = super().get_queryset() # WAŻNE: zachowaj łańcuch
return qs.filter(owner=self.request.user)
# Kolejność ma znaczenie: miksiny PRZED główną klasą widoku
class MyArticlesView(LoginRequiredMixin, OwnerQuerysetMixin, ListView):
model = Article
paginate_by = 20
# Efekt: wymóg logowania + filtr po właścicielu + standardowa lista
Materiały
↑ Powrót na góręSzablony Django
Jak działa dziedziczenie szablonów (extends, block, include)?
Odpowiedź w 30 sekund:
Dziedziczenie szablonów pozwala zdefiniować szablon bazowy ze wspólną strukturą (nagłówek, stopka, układ) i obszarami {% block %}, które szablony potomne nadpisują przez {% extends %}. Tag {% include %} z kolei wstawia zawartość innego szablonu w danym miejscu. Razem eliminują powielanie kodu HTML i utrzymują spójny układ strony.
Odpowiedź w 2 minuty:
Mechanizm extends/block to serce reużywalności szablonów. Tworzysz szablon bazowy (np. base.html) zawierający całą wspólną strukturę: <head>, nawigację, stopkę. W miejscach, które mają się różnić między stronami, umieszczasz {% block nazwa %}{% endblock %}. Szablon potomny zaczyna się od {% extends "base.html" %} i definiuje tylko te bloki, które chce nadpisać. Wszystko poza blokami dziedziczy z bazowego.
Bloki mogą mieć treść domyślną (wyświetlaną, gdy potomny nie nadpisze) i można je zagnieżdżać. W nadpisanym bloku możesz przywołać oryginalną zawartość przez {{ block.super }} — przydatne, gdy chcesz dodać coś do treści bazowej zamiast ją zastępować (np. dopisać skrypt do bloku extra_js). Ważna zasada: {% extends %} musi być pierwszym tagiem w szablonie potomnym.
{% include %} to inny mechanizm — wstawia w danym miejscu zawartość innego szablonu, opcjonalnie przekazując mu kontekst ({% include "card.html" with item=article %}). Służy do wydzielania powtarzalnych fragmentów (karta produktu, formularz, komponent paginacji) używanych w wielu miejscach. Różnica: extends definiuje hierarchię „od ogółu do szczegółu" (układ strony), a include komponuje stronę z mniejszych, reużywalnych klocków. Typowe pułapki: {% extends %} nie na pierwszej linii, próba używania block.super poza nadpisanym blokiem, oraz nadmierne zagnieżdżanie include'ów powodujące trudny do prześledzenia i wolniejszy rendering.
Diagram:
flowchart TD
BASE["base.html (układ + bloki: title, content, extra_js)"]
BASE -->|"{% extends %}"| LIST["article_list.html (nadpisuje title, content)"]
BASE -->|"{% extends %}"| DETAIL["article_detail.html (nadpisuje title, content)"]
LIST -->|"{% include %}"| CARD["_card.html (reużywalny komponent)"]
DETAIL -->|"{% include %}"| CARD
Przykład kodu:
{# base.html — szablon bazowy ze wspólną strukturą i blokami #}
<!DOCTYPE html>
<html lang="pl">
<head><title>{% block title %}Mój serwis{% endblock %}</title></head>
<body>
<nav>...wspólna nawigacja...</nav>
<main>{% block content %}{% endblock %}</main>
<footer>...wspólna stopka...</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
{# article_list.html — szablon potomny #}
{% extends "base.html" %} {# MUSI być pierwszą linią #}
{% block title %}Artykuły — {{ block.super }}{% endblock %} {# rozszerzamy tytuł bazowy #}
{% block content %}
{% for article in articles %}
{% include "_card.html" with item=article %} {# reużywalny komponent #}
{% endfor %}
{% endblock %}
Materiały
↑ Powrót na góręDjango REST Framework
Jak działają routery (routers) w DRF i co automatyzują?
Odpowiedź w 30 sekund:
Routery automatycznie generują zestaw adresów URL dla ViewSetu, mapując akcje (list, retrieve, create, update, destroy) na odpowiednie ścieżki i metody HTTP. Zamiast ręcznie pisać kilka path() na zasób, rejestrujemy ViewSet w routerze i dołączamy wygenerowane router.urls.
Odpowiedź w 2 minuty:
W konwencji REST jeden zasób ma przewidywalny zestaw endpointów: GET /produkty/ (lista), POST /produkty/ (utwórz), GET /produkty/{id}/ (szczegóły), PUT/PATCH /produkty/{id}/ (aktualizuj), DELETE /produkty/{id}/ (usuń). Pisanie tego ręcznie dla każdego zasobu jest powtarzalne. Router DRF generuje cały ten komplet automatycznie na podstawie zarejestrowanego ViewSetu.
Najczęściej używamy DefaultRouter (dodaje też główny endpoint API z listą zasobów i obsługę formatów) lub SimpleRouter. Rejestracja to router.register(r"produkty", ProduktViewSet), gdzie pierwszy argument to prefiks URL, a opcjonalny basename jest potrzebny, gdy ViewSet nie ma queryset (np. nadpisuje get_queryset). Następnie włączamy router.urls do urlpatterns. Router rozpoznaje też własne akcje oznaczone dekoratorem @action i automatycznie tworzy dla nich ścieżki (np. POST /produkty/{id}/oznacz-jako-promocja/).
Routery działają tylko z ViewSetami — dla APIView i widoków generycznych nadal piszemy path() ręcznie. To kompromis: routery dają zwięzłość i spójne, „resztful” adresy, ale odbierają część kontroli nad dokładnym kształtem URL. Gdy potrzebujemy nietypowych ścieżek, łączymy podejścia: część zasobów przez router, część przez ręczne path(). Pułapka: użycie @action(detail=True) vs detail=False decyduje, czy akcja działa na pojedynczym obiekcie (z {id} w URL), czy na kolekcji.
Przykład kodu:
# urls.py - rejestracja ViewSetu w routerze
from rest_framework.routers import DefaultRouter
from .views import ProduktViewSet
router = DefaultRouter()
router.register(r"produkty", ProduktViewSet, basename="produkt")
urlpatterns = router.urls
# wygenerowane automatycznie:
# GET/POST /produkty/
# GET/PUT/PATCH/DELETE /produkty/{pk}/
# views.py - własna akcja również dostaje URL od routera
from rest_framework.decorators import action
from rest_framework.response import Response
class ProduktViewSet(viewsets.ModelViewSet):
queryset = Produkt.objects.all()
serializer_class = ProduktSerializer
# tworzy endpoint POST /produkty/{pk}/promocja/
@action(detail=True, methods=["post"])
def promocja(self, request, pk=None):
produkt = self.get_object()
produkt.cena *= 0.8
produkt.save()
return Response({"status": "promocja włączona"})
Materiały
↑ Powrót na góręBezpieczeństwo
Jak działa mechanizm ochrony CSRF w Django?
Odpowiedź w 30 sekund:
Django generuje sekretny token CSRF powiązany z użytkownikiem, który musi być dołączony do każdego żądania zmieniającego stan (POST/PUT/DELETE). CsrfViewMiddleware porównuje token z żądania z oczekiwanym; jeśli się nie zgadza lub go brak, zwraca 403. W szablonie token wstawiamy tagiem {% csrf_token %}.
Odpowiedź w 2 minuty: CSRF (Cross-Site Request Forgery) wykorzystuje fakt, że przeglądarka automatycznie dołącza ciasteczka uwierzytelniające do żądań — także tych wywołanych przez złośliwą stronę. Bez ochrony atakujący mógłby spreparować formularz, który wysyła żądanie w imieniu zalogowanej ofiary (np. zmiana hasła, przelew). Ochrona polega na wymaganiu wartości, której obca strona nie zna i nie może odczytać.
Django realizuje to schematem „double submit” z dodatkowym sekretem. Token CSRF jest powiązany z sesją/ciasteczkiem i jednocześnie musi być przesłany jawnie — w ukrytym polu formularza (tag {% csrf_token %}) lub w nagłówku X-CSRFToken (dla AJAX). CsrfViewMiddleware sprawdza zgodność tylko dla „niebezpiecznych” metod (POST, PUT, PATCH, DELETE); metody bezpieczne (GET, HEAD, OPTIONS) są pomijane, bo z definicji nie powinny modyfikować stanu. Token jest też maskowany losowo w każdym żądaniu, co utrudnia ataki typu BREACH.
W praktyce: w formularzach HTML wystarczy {% csrf_token %}; w żądaniach JS odczytujemy token z ciasteczka csrftoken i wysyłamy w nagłówku. Dla endpointów przyjmujących żądania z innych zaufanych domen konfigurujemy CSRF_TRUSTED_ORIGINS. Dekorator @csrf_exempt wyłącza ochronę punktowo (np. webhook z zewnętrzną weryfikacją), a @csrf_protect wymusza ją tam, gdzie middleware jest wyłączony. Pułapki: brak {% csrf_token %} (403 przy wysyłce), mylenie CSRF z CORS, oraz wyłączanie ochrony „żeby działało” zamiast poprawnego przekazania tokenu. Na produkcji warto CSRF_COOKIE_SECURE = True.
Przykład kodu:
{# Formularz HTML #}
<form method="post">
{% csrf_token %} {# wstawia ukryte pole z tokenem #}
{{ form.as_p }}
<button type="submit">Zapisz</button>
</form>
// AJAX - token z ciasteczka w nagłówku
const csrftoken = document.cookie
.split("; ").find(c => c.startsWith("csrftoken="))?.split("=")[1];
fetch("/api/zmien/", {
method: "POST",
headers: { "X-CSRFToken": csrftoken, "Content-Type": "application/json" },
body: JSON.stringify({ wartosc: 1 }),
});
Diagram:
flowchart TD
A["Żądanie POST/PUT/DELETE"] --> B["CsrfViewMiddleware"]
B --> C{"Token obecny i poprawny?"}
C -->|Tak| D["Widok wykonuje akcję"]
C -->|Nie| E["403 Forbidden"]
F["Metoda GET/HEAD"] -.pomijane.-> D
Materiały
↑ Powrót na góręJakie ustawienia bezpieczeństwa warto włączyć na produkcji (SECRET_KEY, ALLOWED_HOSTS, HTTPS/HSTS, cookies)?
Odpowiedź w 30 sekund:
Na produkcji bezwzględnie: DEBUG = False, tajny i unikalny SECRET_KEY z zmiennej środowiskowej, poprawne ALLOWED_HOSTS, wymuszenie HTTPS (SECURE_SSL_REDIRECT, HSTS) oraz bezpieczne ciasteczka (SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, HttpOnly). Pomaga manage.py check --deploy, który audytuje konfigurację.
Odpowiedź w 2 minuty:
Najważniejsze jest DEBUG = False — z DEBUG = True Django pokazuje pełne ślady stosu i ustawienia przy błędach, co ujawnia wrażliwe dane. SECRET_KEY musi być długi, losowy i tajny; używany do podpisów (sesje, tokeny CSRF, hasła resetu), więc jego wyciek kompromituje bezpieczeństwo. Trzymamy go w zmiennej środowiskowej, nigdy w repozytorium. ALLOWED_HOSTS musi zawierać prawdziwe domeny — przy DEBUG = False Django odrzuca żądania z nieznanym nagłówkiem Host, co blokuje ataki Host header poisoning.
HTTPS to fundament: SECURE_SSL_REDIRECT = True przekierowuje HTTP na HTTPS, a HSTS (SECURE_HSTS_SECONDS z INCLUDE_SUBDOMAINS i PRELOAD) instruuje przeglądarki, by zawsze łączyły się po HTTPS — chroni przed atakami downgrade i SSL stripping. Uwaga: HSTS należy wdrażać ostrożnie i stopniowo, bo źle ustawiony (długi czas, gdy HTTPS nie jest gotowe) potrafi zablokować dostęp do strony. Gdy aplikacja stoi za proxy, ustawiamy SECURE_PROXY_SSL_HEADER, by Django poprawnie rozpoznawało HTTPS.
Ciasteczka zabezpieczamy flagami: SESSION_COOKIE_SECURE i CSRF_COOKIE_SECURE (przesyłane tylko po HTTPS), SESSION_COOKIE_HTTPONLY (niedostępne dla JavaScript, utrudnia kradzież przez XSS) oraz SameSite (ogranicza wysyłanie ciasteczek z obcych witryn). Dodatkowo warto dołączyć nagłówki przez SecurityMiddleware (SECURE_CONTENT_TYPE_NOSNIFF). Pułapka: łatwo zapomnieć któreś z tych ustawień — dlatego standardem jest uruchamianie python manage.py check --deploy, które wypisuje brakujące zabezpieczenia. Sekrety i różnice środowiskowe trzymamy w zmiennych środowiskowych, a nie w kodzie.
Przykład kodu:
# settings.py (produkcja) - kluczowe ustawienia bezpieczeństwa
import os
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # z env, nie w repo
ALLOWED_HOSTS = ["flipcards.pl", "www.flipcards.pl"]
# HTTPS / HSTS
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000 # 1 rok (wdrażać stopniowo!)
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # za proxy
# Ciasteczka
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
# Dodatkowe nagłówki
SECURE_CONTENT_TYPE_NOSNIFF = True
# Audyt konfiguracji przed wdrożeniem
python manage.py check --deploy
Materiały
↑ Powrót na góręWydajność, cache i zadania asynchroniczne
Czym są indeksy bazodanowe i jak definiować je w modelach?
Odpowiedź w 30 sekund:
Indeks to dodatkowa struktura danych (najczęściej B-drzewo), która przyspiesza wyszukiwanie, sortowanie i łączenie po danej kolumnie kosztem nieco wolniejszych zapisów i większego miejsca na dysku. W Django definiujesz je przez db_index=True na polu, listę indexes w Meta (z klasą models.Index) lub ograniczenia (UniqueConstraint), które również tworzą indeks.
Odpowiedź w 2 minuty:
Bez indeksu baza musi przeskanować całą tabelę (full table scan), żeby znaleźć pasujące wiersze — przy dużych tabelach to wolne. Indeks działa jak posortowany skorowidz: baza szybko lokalizuje wiersze spełniające warunek WHERE, przyspiesza ORDER BY i operacje JOIN. Cena to narzut przy INSERT/UPDATE/DELETE (indeks trzeba aktualizować) oraz dodatkowe miejsce, dlatego indeksuje się kolumny realnie używane w filtrach i sortowaniu, a nie wszystkie z rzędu.
W Django najprostszy sposób to db_index=True na pojedynczym polu. Klucze obce (ForeignKey) są indeksowane domyślnie, podobnie pola z unique=True. Dla indeksów wielokolumnowych (composite) i zaawansowanych opcji używasz Meta.indexes z models.Index(fields=[...], name=...). Kolejność pól w indeksie złożonym ma znaczenie — indeks [a, b] wspiera filtrowanie po a oraz po a, b, ale nie po samym b. Możesz tworzyć indeksy malejące ("-data"), indeksy częściowe (condition=Q(...) — indeksowane tylko wybrane wiersze, np. aktywne rekordy) oraz indeksy funkcyjne na wyrażeniach (np. Lower("email") dla wyszukiwania bez rozróżniania wielkości liter). Specyficzne dla PostgreSQL typy (GIN, GiST, BRIN) znajdziesz w django.contrib.postgres.indexes.
Po dodaniu indeksu generujesz i uruchamiasz migrację (makemigrations + migrate). Na produkcji pamiętaj, że tworzenie indeksu może blokować tabelę — w PostgreSQL używa się CREATE INDEX CONCURRENTLY (w Django przez AddIndexConcurrently z django.contrib.postgres.operations i atomic = False w migracji). Typowe pułapki: indeksowanie kolumn o niskiej selektywności (np. pole boolean z rozkładem 50/50 daje mały zysk), zapominanie o indeksie pod ORDER BY w paginowanych listach, oraz zbyt wiele indeksów spowalniających zapisy. Zawsze weryfikuj realny efekt przez QuerySet.explain(), który pokaże, czy baza faktycznie używa indeksu.
Przykład kodu:
from django.db import models
from django.db.models import Q
from django.db.models.functions import Lower
class Zamowienie(models.Model):
numer = models.CharField(max_length=20, db_index=True) # indeks na pojedynczym polu
klient = models.ForeignKey("Klient", on_delete=models.CASCADE) # FK indeksowany domyślnie
status = models.CharField(max_length=20)
utworzone = models.DateTimeField(auto_now_add=True)
email = models.EmailField()
class Meta:
indexes = [
# Indeks złożony — wspiera filtr po statusie i sortowanie po dacie
models.Index(fields=["status", "-utworzone"], name="idx_status_data"),
# Indeks częściowy — tylko zamówienia oczekujące (mniejszy, szybszy)
models.Index(
fields=["utworzone"],
name="idx_oczekujace",
condition=Q(status="oczekujace"),
),
# Indeks funkcyjny — wyszukiwanie e-maila bez względu na wielkość liter
models.Index(Lower("email"), name="idx_email_lower"),
]
Materiały
↑ Powrót na górę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
↑ Powrót na góręURL routing, middleware i cykl żądania
Jak działa routing URL w Django (path, re_path)?
Odpowiedź w 30 sekund:
Routing URL dopasowuje ścieżkę z żądania HTTP do odpowiedniego widoku. Django przechodzi listę urlpatterns od góry do dołu i wybiera pierwszy pasujący wzorzec. path() używa czytelnych konwerterów (np. <int:pk>), a re_path() pełnych wyrażeń regularnych dla bardziej złożonych wzorców.
Odpowiedź w 2 minuty:
Punktem wejścia routingu jest moduł wskazany w ROOT_URLCONF (zwykle projekt/urls.py). Zawiera on listę urlpatterns. Gdy nadchodzi żądanie, resolver URL (URLResolver) porównuje ścieżkę żądania (bez domeny i query string) kolejno z każdym wzorcem. Pierwsze dopasowanie wygrywa — dlatego kolejność ma znaczenie, a bardziej szczegółowe wzorce warto umieszczać przed ogólnymi.
path() to nowoczesny, zalecany sposób. Korzysta z konwerterów ścieżki: str (domyślny, bez ukośnika), int, slug, uuid oraz path (akceptuje ukośniki). Wartości przechwycone trafiają do widoku jako argumenty nazwane (**kwargs). re_path() używamy, gdy potrzebujemy pełnej siły wyrażeń regularnych — np. opcjonalnych segmentów, alternatyw czy własnych formatów. W re_path() grupy nazwane (?P<nazwa>...) przekazują argumenty do widoku.
Do organizacji projektu służy include(), które pozwala delegować część ścieżki do urls.py poszczególnych aplikacji. Dzięki temu URL-e są modularne. Częsta pułapka: zapominanie o ukośniku na końcu wzorca przy włączonym APPEND_SLASH lub błędne kotwiczenie regexów (re_path automatycznie dopasowuje od początku ścieżki, ale warto kończyć wzorzec znakiem $, aby uniknąć przypadkowych dopasowań). Można też pisać własne konwertery ścieżki, rejestrując je przez register_converter().
Przykład kodu:
# projekt/urls.py
from django.urls import path, re_path, include
from blog import views
urlpatterns = [
# konwerter int przechwytuje liczbę i przekazuje jako kwarg pk
path("artykul/<int:pk>/", views.szczegoly, name="szczegoly"),
# konwerter slug dla przyjaznych adresów, np. /kategoria/django/
path("kategoria/<slug:nazwa>/", views.kategoria, name="kategoria"),
# re_path: rok jako dokładnie 4 cyfry - pełna kontrola przez regex
re_path(r"^archiwum/(?P<rok>[0-9]{4})/$", views.archiwum, name="archiwum"),
# delegacja części adresów do urls.py aplikacji sklep
path("sklep/", include("sklep.urls")),
]
Materiały
↑ Powrót na góręFormularze i walidacja
Czym są formularze Django (Form) i jakie problemy rozwiązują?
Odpowiedź w 30 sekund:
Formularz Django (forms.Form) to klasa opisująca pola wejściowe, która automatyzuje trzy rzeczy: renderowanie HTML, walidację danych oraz konwersję surowych danych z żądania na czyste typy Pythona. Dzięki temu nie pisze się ręcznie pól <input> ani powtarzalnej walidacji.
Odpowiedź w 2 minuty:
Obsługa formularzy bez frameworka to żmudne i błędogenne zadanie: trzeba wygenerować HTML, odczytać dane z request.POST, sprawdzić poprawność każdego pola, przekonwertować typy (np. tekst na liczbę czy datę) i wyświetlić komunikaty o błędach. Klasa Form rozwiązuje to deklaratywnie — definiujemy pola jako atrybuty klasy, a Django zajmuje się resztą.
Cykl życia formularza ma dwa stany. Formularz „niezwiązany" (unbound) tworzymy bez danych (MojFormularz()) i służy do wyświetlenia pustego formularza. Formularz „związany" (bound) tworzymy z danymi z żądania (MojFormularz(request.POST)) i na nim wywołujemy is_valid(). Jeśli zwróci True, oczyszczone i przekonwertowane dane są dostępne w słowniku form.cleaned_data. Jeśli False, błędy trafiają do form.errors i są wyświetlane przy odpowiednich polach.
Każde pole formularza ma typ (CharField, EmailField, IntegerField, DateField, ChoiceField itd.) oraz widget określający renderowanie HTML. Pola same walidują podstawy (wymagalność, długość, format e-mail), a wynik to bezpieczne, czyste dane. Typowy wzorzec widoku: na GET pokazujemy pusty formularz, na POST tworzymy związany formularz i sprawdzamy is_valid(). Formularze automatycznie też eskejpują dane przy renderowaniu, co chroni przed XSS.
Przykład kodu:
# forms.py
from django import forms
class KontaktForm(forms.Form):
imie = forms.CharField(max_length=100, label="Imię")
email = forms.EmailField(label="Adres e-mail")
wiadomosc = forms.CharField(widget=forms.Textarea)
# views.py - typowy wzorzec GET/POST
from django.shortcuts import render, redirect
def kontakt(request):
if request.method == "POST":
form = KontaktForm(request.POST) # formularz "związany" z danymi
if form.is_valid():
# dane są oczyszczone i przekonwertowane
wyslij_maila(form.cleaned_data["email"], form.cleaned_data["wiadomosc"])
return redirect("dziekujemy")
else:
form = KontaktForm() # formularz "niezwiązany" (pusty)
return render(request, "kontakt.html", {"form": form})
Materiały
↑ Powrót na góręUwierzytelnianie i autoryzacja
Jak działa wbudowany system uwierzytelniania (authentication) w Django?
Odpowiedź w 30 sekund:
Django dostarcza kompletny system uwierzytelniania w aplikacji django.contrib.auth: model User, mechanizm sesji, hashowanie haseł oraz gotowe widoki logowania i wylogowania. Uwierzytelnianie (authentication) odpowiada na pytanie „kim jesteś", a autoryzacja (authorization) — „co możesz zrobić". Sercem systemu są funkcje authenticate() i login() oraz backendy uwierzytelniania, które weryfikują dane logowania.
Odpowiedź w 2 minuty:
System składa się z kilku warstw. Pierwsza to model użytkownika (django.contrib.auth.models.User) przechowujący nazwę, hasło (w postaci zahashowanej), e-mail, flagi is_active, is_staff, is_superuser oraz relacje do grup i uprawnień. Hasła nigdy nie są zapisywane jawnie — Django używa konfigurowalnych hasherów (domyślnie PBKDF2, opcjonalnie Argon2 czy bcrypt) ustawianych w PASSWORD_HASHERS.
Druga warstwa to backendy uwierzytelniania (AUTHENTICATION_BACKENDS). Funkcja authenticate(request, username=..., password=...) przechodzi po kolejnych backendach, aż któryś zwróci obiekt użytkownika lub wszystkie zwrócą None. Domyślny ModelBackend sprawdza dane w bazie i jednocześnie pełni rolę systemu autoryzacji (dostarcza uprawnienia). Można dodać własne backendy, np. logowanie przez e-mail albo LDAP.
Trzecia warstwa to sesje i middleware. Po udanym login(request, user) identyfikator użytkownika zapisywany jest w sesji, a AuthenticationMiddleware przy każdym żądaniu wczytuje go i wystawia jako request.user. Jeśli użytkownik nie jest zalogowany, request.user to AnonymousUser. Dostęp do widoków kontroluje się dekoratorem @login_required, miksinem LoginRequiredMixin (dla CBV) oraz dekoratorami/miksinami uprawnień (@permission_required, PermissionRequiredMixin). Django dostarcza też gotowe widoki i URL-e (django.contrib.auth.urls) dla logowania, wylogowania, resetu hasła i jego zmiany.
Przykład kodu:
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
def login_view(request):
if request.method == "POST":
# authenticate() weryfikuje dane przez backendy z AUTHENTICATION_BACKENDS
user = authenticate(
request,
username=request.POST["username"],
password=request.POST["password"],
)
if user is not None:
# login() zapisuje id użytkownika w sesji i ustawia request.user
login(request, user)
return redirect("dashboard")
# user is None -> błędne dane logowania
return render(request, "login.html", {"error": "Błędne dane logowania"})
return render(request, "login.html")
def logout_view(request):
logout(request) # czyści dane sesji powiązane z uwierzytelnieniem
return redirect("login")
@login_required # przekierowuje do LOGIN_URL, jeśli użytkownik nie jest zalogowany
def dashboard(request):
return render(request, "dashboard.html", {"user": request.user})
Diagram:
flowchart TD
A["Żądanie POST z formularza logowania"] --> B["authenticate(request, username, password)"]
B --> C{"Backend zwrócił użytkownika?"}
C -->|"Nie (None)"| D["Błąd: nieprawidłowe dane"]
C -->|"Tak"| E["login(request, user)"]
E --> F["Zapis user_id w sesji"]
F --> G["AuthenticationMiddleware ustawia request.user przy kolejnych żądaniach"]
G --> H["Dostęp do widoków chronionych (@login_required)"]
Materiały
- Using the Django authentication system
- Customizing authentication in Django
- Password management in Django
Testowanie
Jak pisać testy w Django (TestCase) i czym różni się to od zwykłego unittest?
Odpowiedź w 30 sekund:
Django dostarcza klasę TestCase rozszerzającą unittest.TestCase o integrację z bazą danych: tworzy osobną testową bazę, owija każdy test w transakcję i wycofuje ją po teście (izolacja), oraz dodaje testowego klienta HTTP i wygodne asercje. Testy umieszczamy w tests.py lub pakiecie tests/ i uruchamiamy manage.py test.
Odpowiedź w 2 minuty:
Zwykły unittest to standardowa biblioteka Pythona do testów jednostkowych — działa, ale nic nie wie o Django ani o bazie danych. django.test.TestCase dokłada warstwę integracji niezbędną do testowania aplikacji webowej. Po pierwsze, przed uruchomieniem testów Django tworzy oddzielną testową bazę danych (nie dotyka produkcyjnej/deweloperskiej) i niszczy ją po zakończeniu. Po drugie, każdy test jest opakowany w transakcję, która jest wycofywana (rollback) na końcu — dzięki temu testy są od siebie izolowane i nie pozostawiają śmieci.
TestCase dostarcza też testowego klienta HTTP (self.client), którym symulujemy żądania do widoków bez stawiania serwera, oraz dodatkowe asercje (assertContains, assertRedirects, assertTemplateUsed, assertQuerySetEqual). Standardowy przepływ to metoda setUp() przygotowująca dane przed każdym testem oraz metody test_* zawierające asercje. Django ma też lżejsze klasy: SimpleTestCase (bez dostępu do bazy — szybsze dla testów niezależnych od danych) i TransactionTestCase (gdy test musi sprawdzać prawdziwe zachowanie transakcji, np. on_commit).
Różnica praktyczna: TestCase izoluje przez rollback (szybko), TransactionTestCase czyści tabele (wolniej, ale realniej dla logiki transakcyjnej). Uruchamianie: manage.py test znajduje i odpala wszystkie testy. Pułapki: stan między testami nie powinien wyciekać (TestCase to gwarantuje przez transakcje), a testy nie powinny zależeć od kolejności wykonania. Warto pisać małe, jednoznaczne testy z opisowymi nazwami.
Przykład kodu:
# tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Artykul
class ArtykulTestCase(TestCase):
def setUp(self):
# uruchamiane przed KAŻDYM testem; dane znikają po rollbacku
self.artykul = Artykul.objects.create(tytul="Test", tresc="Treść")
def test_str_zwraca_tytul(self):
# test jednostkowy modelu
self.assertEqual(str(self.artykul), "Test")
def test_strona_szczegolow_dziala(self):
# test widoku przez klienta HTTP
url = reverse("szczegoly", kwargs={"pk": self.artykul.pk})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Test")
Materiały
↑ Powrót na góręSygnały, transakcje i wdrożenie
Czym są sygnały (signals) w Django, kiedy ich używać, a kiedy ich unikać?
Odpowiedź w 30 sekund:
Sygnały to mechanizm publikuj-subskrybuj: nadawca wysyła powiadomienie o zdarzeniu (np. zapis modelu), a zarejestrowani odbiorcy (receivers) reagują. Wbudowane sygnały to m.in. pre_save, post_save, pre_delete, m2m_changed. Używaj ich do luźno powiązanych reakcji, ale unikaj ukrywania w nich kluczowej logiki — bywa trudna do prześledzenia.
Odpowiedź w 2 minuty:
Sygnały pozwalają „odsprzęgnąć" nadawcę zdarzenia od kodu reagującego. Django wysyła sygnały przy wielu zdarzeniach: cyklu życia modeli (pre_save/post_save, pre_delete/post_delete, m2m_changed), żądań (request_started/request_finished), migracji (pre_migrate/post_migrate) czy zalogowania (user_logged_in). Odbiorcę rejestrujemy dekoratorem @receiver lub przez signal.connect(). Można też tworzyć własne sygnały.
Dobre zastosowania to reakcje poboczne, które nie należą do głównej logiki domeny i powinny działać niezależnie od miejsca wywołania: np. czyszczenie cache po zapisie, indeksowanie do wyszukiwarki, wysyłka powiadomienia, tworzenie powiązanego profilu po utworzeniu użytkownika. Sygnały błyszczą, gdy nadawca i odbiorca są w różnych aplikacjach (luźne powiązanie) i nie chcemy, by jedna wiedziała o drugiej.
Jest jednak silny argument przeciw nadużywaniu. Sygnały rozpraszają logikę i czynią przepływ trudnym do prześledzenia — patrząc na save(), nie widać, że „magicznie" dzieje się coś jeszcze. To utrudnia debugowanie, testowanie i onboarding. Wielu doświadczonych deweloperów radzi: jeśli kontrolujesz miejsce wywołania (np. własny widok czy metoda serwisowa), wywołaj logikę jawnie zamiast przez sygnał. Pułapki: post_save z created do tworzenia profili (trudne do obejścia w testach), zapętlenia (sygnał wywołujący save, który znów wyzwala sygnał), problemy z bulk_create/update/delete — operacje masowe NIE wyzwalają sygnałów modelu. Odbiorców rejestruje się w AppConfig.ready(). Zasada: sygnały do prawdziwie odsprzęgniętych efektów ubocznych, jawne wywołania do logiki domenowej.
Przykład kodu:
# myapp/signals.py - reakcja na utworzenie użytkownika
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from .models import Profil
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def utworz_profil(sender, instance, created, **kwargs):
if created: # tylko przy pierwszym zapisie (nie przy update)
Profil.objects.create(user=instance)
# apps.py - rejestracja odbiorców w ready()
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self):
from . import signals # import rejestruje @receiver
Diagram:
flowchart LR
A["user.save()"] --> B["sygnał post_save"]
B --> C["odbiorca: utworz_profil"]
B --> D["odbiorca: wyczysc_cache"]
B --> E["odbiorca: wyslij_powiadomienie"]
Materiały
↑ Powrót na górę