Witajcie! Dziś rzucimy okiem na Django 5.2, które zostało wydane 2 kwietnia 2025 roku. To wydanie jest szczególnie ważne, ponieważ otrzymało status LTS (Long-Term Support), co oznacza, że będzie otrzymywać poprawki bezpieczeństwa przez co najmniej trzy lata - aż do kwietnia 2028!
Jako wieloletni użytkownik Django, zawsze z niecierpliwością czekam na nowe wydania, a szczególnie na te z długoterminowym wsparciem. Dlaczego? Bo właśnie na nich budujemy nasze produkcyjne aplikacje, które muszą działać stabilnie przez lata. Przyjrzyjmy się więc, co nowego przynosi nam Django 5.2 i dlaczego warto rozważyć aktualizację.
Kompatybilność z Pythonem
Django 5.2 obsługuje Pythona w wersjach 3.10, 3.11, 3.12 oraz 3.13. Twórcy Django zalecają korzystanie z najnowszych wydań każdej serii, aby skorzystać z najnowszych funkcji i poprawek bezpieczeństwa. Warto zauważyć, że wsparcie dla poprzedniej wersji LTS, Django 4.2, zakończy się w kwietniu 2026 roku, więc mamy jeszcze trochę czasu na migrację starszych projektów.
Najważniejsze nowości w Django 5.2
Automatyczny import modeli w shellu
Jedna z najbardziej praktycznych nowości to automatyczny import modeli przy korzystaniu z komendy manage.py shell
. Do tej pory musieliśmy ręcznie importować modele za każdym razem, gdy uruchamialiśmy konsolę Django, co było powtarzalne i czasochłonne:
# Wcześniej za każdym razem:
from myapp.models import MyModel
from django.contrib.auth.models import User
# itd...
Teraz modele ze wszystkich zainstalowanych aplikacji są automatycznie importowane, co oszczędza nam sporo czasu i klepania w klawiaturę. Możemy również uzyskać szczegółowe informacje o importowanych obiektach, ustawiając flagę --verbosity
na poziom 2 lub wyższy:
$ python manage.py shell --verbosity=2
6 objects imported automatically, including:
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
Jeśli chcielibyśmy dostosować tę listę automatycznych importów (np. dodać własne moduły lub pominąć niektóre modele), możemy nadpisać metodę get_auto_imports()
we własnym poleceniu shell:
# myapp/management/commands/shell.py
from django.core.management.commands import shell
class Command(shell.Command):
def get_auto_imports(self):
# Dodajemy dodatkowe importy:
return super().get_auto_imports() + [
"django.urls.reverse",
"django.urls.resolve",
]
Złożone klucze główne (Composite Primary Keys)
To funkcjonalność, na którą społeczność Django czekała od dawna! Django 5.2 wprowadza wsparcie dla złożonych kluczy głównych, czyli kluczy składających się z wielu pól. Teraz możemy definiować modele, w których unikalny identyfikator to kombinacja dwóch lub więcej pól, a nie tylko pojedyncze pole.
W większości przypadków pojedynczy klucz główny jest wystarczający, ale w projektowaniu baz danych czasami konieczne jest zdefiniowanie klucza głównego składającego się z wielu pól.
Aby użyć złożonego klucza głównego, wystarczy ustawić atrybut pk
modelu na instancję CompositePrimaryKey
:
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
class Order(models.Model):
reference = models.CharField(max_length=20, primary_key=True)
class OrderLineItem(models.Model):
pk = models.CompositePrimaryKey("product_id", "order_id")
product = models.ForeignKey(Product, on_delete=models.CASCADE)
order = models.ForeignKey(Order, on_delete=models.CASCADE)
quantity = models.IntegerField()
To spowoduje, że Django utworzy złożony klucz główny PRIMARY KEY (product_id, order_id)
podczas tworzenia tabeli w bazie danych.
Złożony klucz główny jest reprezentowany jako krotka:
>>> product = Product.objects.create(name="apple")
>>> order = Order.objects.create(reference="A755H")
>>> item = OrderLineItem.objects.create(product=product, order=order, quantity=1)
>>> item.pk
(1, "A755H")
Możesz również przypisać krotkę do atrybutu pk
. To ustawia powiązane wartości pól:
>>> item = OrderLineItem(pk=(2, "B142C"))
>>> item.pk
(2, "B142C")
>>> item.product_id
2
>>> item.order_id
"B142C"
Po złożonym kluczu głównym można również filtrować przy użyciu krotki:
>>> OrderLineItem.objects.filter(pk=(1, "A755H")).count()
1
Migracja do złożonego klucza głównego
Ważne: Django nie wspiera migracji do lub z złożonego klucza głównego po utworzeniu tabeli. Nie wspiera również dodawania lub usuwania pól ze złożonego klucza głównego.
Jeśli chcesz przekształcić istniejącą tabelę z pojedynczego klucza głównego na złożony, musisz to zrobić zgodnie z instrukcjami swojego backendu bazy danych - Django nie zrobi tego automatycznie.
Po ręcznym ustawieniu złożonego klucza głównego w bazie danych, dodaj pole CompositePrimaryKey
do swojego modelu. To pozwoli Django rozpoznać i odpowiednio obsłużyć złożony klucz główny.
Chociaż operacje migracji (np. AddField
, AlterField
) na polach klucza głównego nie są obsługiwane, makemigrations
nadal wykryje zmiany. Aby uniknąć błędów, zaleca się stosowanie takich migracji z flagą --fake
.
Alternatywnie, można użyć SeparateDatabaseAndState
do wykonania migracji specyficznych dla backendu oraz migracji wygenerowanych przez Django w jednej operacji.
Złożone klucze główne i relacje
Pola relacji, w tym relacje ogólne (generic relations), nie obsługują złożonych kluczy głównych. Na przykład, mając model OrderLineItem
, następujący kod nie jest obsługiwany:
class Foo(models.Model):
item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)
Ponieważ ForeignKey
obecnie nie może odwoływać się do modeli ze złożonymi kluczami głównymi.
Aby obejść to ograniczenie, można użyć ForeignObject
jako alternatywy:
class Foo(models.Model):
item_order_id = models.IntegerField()
item_product_id = models.CharField(max_length=20)
item = models.ForeignObject(
OrderLineItem,
on_delete=models.CASCADE,
from_fields=("item_order_id", "item_product_id"),
to_fields=("order_id", "product_id"),
)
ForeignObject
działa podobnie do ForeignKey
, z tą różnicą, że nie tworzy żadnych kolumn (np. item_id
), ograniczeń klucza obcego ani indeksów w bazie danych.
Ostrzeżenie: ForeignObject
jest częścią wewnętrznego API Django. Oznacza to, że nie jest objęty polityką deprecjacji.
Złożone klucze główne w funkcjach bazodanowych
Wiele funkcji bazodanowych akceptuje tylko pojedyncze wyrażenie:
MAX("order_id") # OK
MAX("product_id", "order_id") # ERROR
W takich przypadkach, podanie referencji złożonego klucza głównego powoduje ValueError
, ponieważ składa się z wielu wyrażeń kolumnowych. Wyjątkiem jest Count
:
Max("order_id") # OK
Max("pk") # ValueError
Count("pk") # OK
Złożone klucze główne w formularzach
Ponieważ złożony klucz główny jest polem wirtualnym, czyli polem, które nie reprezentuje pojedynczej kolumny bazy danych, to pole jest wyłączone z ModelForms
.
Na przykład, mając następujący formularz:
class OrderLineItemForm(forms.ModelForm):
class Meta:
model = OrderLineItem
fields = "__all__"
Formularz ten nie będzie miał pola formularza pk
dla złożonego klucza głównego:
>>> OrderLineItemForm()
<OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>
Ustawienie złożonego pola głównego pk
jako pola formularza powoduje FieldError
dla nieznanego pola.
Uwaga: Pola klucza głównego są tylko do odczytu. Jeśli zmienisz wartość klucza głównego w istniejącym obiekcie, a następnie go zapiszesz, zostanie utworzony nowy obiekt obok starego. To samo dotyczy złożonych kluczy głównych. Dlatego warto ustawić Field.editable
na False
dla wszystkich pól klucza głównego, aby wykluczyć je z ModelForms
.
Introspektowanie modeli ze złożonymi kluczami głównymi
Przed wprowadzeniem złożonych kluczy głównych, pojedyncze pole tworzące klucz główny modelu można było uzyskać przez introspekcję atrybutu primary_key
jego pól:
>>> pk_field = None
>>> for field in Product._meta.get_fields():
... if field.primary_key:
... pk_field = field
... break
...
>>> pk_field
<django.db.models.fields.AutoField: id>
Teraz, gdy klucz główny może składać się z wielu pól, nie można już polegać na atrybucie primary_key
, aby zidentyfikować członków klucza głównego, ponieważ będzie on ustawiony na False
, aby zachować zasadę, że co najwyżej jedno pole na model będzie miało ten atrybut ustawiony na True
:
>>> pk_fields = []
>>> for field in OrderLineItem._meta.get_fields():
... if field.primary_key:
... pk_fields.append(field)
...
>>> pk_fields
[]
Aby tworzyć kod aplikacji, który prawidłowo obsługuje złożone klucze główne, należy zamiast tego używać atrybutu _meta.pk_fields
:
>>> Product._meta.pk_fields
[<django.db.models.fields.AutoField: id>]
>>> OrderLineItem._meta.pk_fields
[
<django.db.models.fields.ForeignKey: product>,
<django.db.models.fields.ForeignKey: order>
]
Warto pamiętać, że Django admin obecnie nie obsługuje modeli ze złożonymi kluczami głównymi. Modeli takich nie można zarejestrować w Django admin. Wsparcie dla tego powinno pojawić się w przyszłych wydaniach.
Uproszczone dostosowywanie pól formularzy (BoundField)
Django 5.2 wprowadza prostszy mechanizm nadpisywania sposobu renderowania pól formularza. Wcześniej, aby dostosować zachowanie obiektu BoundField
(który reprezentuje pole formularza związane z danymi), trzeba było nadpisać metodę Field.get_bound_field()
, co było mało intuicyjne.
Teraz możemy określić własną klasę BoundField
na trzech poziomach:
- Globalnie (dla całego projektu):
BaseRenderer.bound_field_class
- Na poziomie formularza:
Form.bound_field_class
- Na poziomie pojedynczego pola:
Field.bound_field_class
Zobaczmy to na przykładzie:
from django import forms
class CustomBoundField(forms.BoundField):
custom_class = "custom-field"
def css_classes(self, extra_classes=None):
result = super().css_classes(extra_classes)
if self.custom_class not in result:
result += f" {self.custom_class}"
return result.strip()
class MyForm(forms.Form):
bound_field_class = CustomBoundField # używaj naszej klasy dla wszystkich pól
name = forms.CharField(max_length=100)
email = forms.EmailField()
Teraz, gdy renderujemy formularz, każde pole będzie miało dodatkową klasę CSS "custom-field". To rozwiązanie jest znacznie bardziej eleganckie i łatwiejsze w utrzymaniu niż wcześniejsze podejścia.
Asynchroniczne metody w systemie uwierzytelniania
Django od wersji 3.1 stopniowo rozszerza wsparcie dla asynchroniczności. W Django 5.2 dodano szereg asynchronicznych metod w module uwierzytelniania (django.contrib.auth
). Są to asynchroniczne odpowiedniki istniejących metod, z prefiksem a
:
UserManager.acreate_user()
,acreate_superuser()
User.aget_user_permissions()
,aget_group_permissions()
,aget_all_permissions()
User.ahas_perm()
,ahas_perms()
,ahas_module_perms()
ModelBackend.aauthenticate()
i inne metody backendu uwierzytelniania
Dzięki temu w widokach asynchronicznych możemy teraz np. tworzyć użytkowników bez blokowania pętli zdarzeń:
async def register_view(request):
# Walidacja danych...
user = await User.objects.acreate_user(
username="jan",
email="[email protected]",
password="tajne_haslo123"
)
# Użytkownik utworzony asynchronicznie
return JsonResponse({"status": "success"})
Ten rozwój asynchroniczności w Django to ważny krok naprzód, pozwalający na budowę bardziej wydajnych aplikacji, szczególnie w przypadkach z dużą ilością operacji I/O.
Nowe widgety formularzy HTML5
Django 5.2 dodaje trzy nowe klasy widgetów formularza dla popularnych typów pól HTML5:
ColorInput
- widżet generujący<input type="color">
, umożliwiający wybór koloruSearchInput
- widżet dla pola wyszukiwania (<input type="search">
)TelInput
- widżet dla numeru telefonu (<input type="tel">
)
Używanie tych widgetów jest bardzo proste:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(label="Imię i nazwisko")
phone = forms.CharField(label="Telefon", widget=forms.TelInput)
search_query = forms.CharField(label="Szukaj", required=False, widget=forms.SearchInput)
favorite_color = forms.CharField(label="Ulubiony kolor", required=False, widget=forms.ColorInput)
To drobna zmiana, ale pomocna - kod staje się bardziej semantyczny i lepiej wykorzystuje możliwości nowoczesnych przeglądarek, a my piszemy mniej własnego kodu.
Bezpieczeństwo
Warto wspomnieć o kilku zmianach związanych z bezpieczeństwem:
- Domyślna liczba iteracji algorytmu PBKDF2 dla hashowania haseł wzrosła z 870 000 do 1 000 000, co zwiększa bezpieczeństwo (utrudnia ataki brute-force).
- Filtracja danych w raportach błędów: obiekt
SafeExceptionReporterFilter
uznaje teraz każdą zmienną, której nazwa zawiera AUTH, za wrażliwą. - Uruchamiając serwer deweloperski komendą
manage.py runserver
, zobaczymy teraz ostrzeżenie, że ten serwer nie nadaje się do środowiska produkcyjnego. Można je wyłączyć zmienną środowiskowąDJANGO_RUNSERVER_HIDE_WARNING=true
.
Nadchodzące poprawki w Django 5.2.1
Jak to zwykle bywa, nawet po starannym testowaniu, w nowym wydaniu pojawiły się błędy. Planowana na 7 maja 2025 wersja Django 5.2.1 zawiera poprawki kilku istotnych regresji:
- Naprawienie błędu powodującego awarię przy adnotowaniu wyrażeń agregacyjnych nad zapytaniami z jawnym grupowaniem przez transformacje.
- Usunięcie zbędnych zapytań przy prefetchingu relacji z kluczami obcymi, które mogą być nullowe.
- Naprawienie awarii
QuerySet.bulk_create()
z polami geometrii nullable na PostGIS. - Naprawienie błędu z niepoprawnym wyborem pól przy używaniu
QuerySet.alias()
povalues()
. - Usunięcie możliwości uszkodzenia danych w
file_move_safe()
przyallow_overwrite=True
. - Naprawienie awarii przy używaniu
QuerySet.select_for_update(of=(…))
zvalues()/values_list()
. - Naprawienie błędu powodującego niepoprawne wartości z
QuerySet.values_list()
przy zduplikowanych nazwach pól.
Jeśli korzystacie z tych funkcji, warto poczekać na 5.2.1 lub uważnie obserwować swoje logi błędów.
Zmiany niekompatybilne wstecz i funkcje przestarzałe
Zmiany niekompatybilne
Django 5.2 wprowadza kilka zmian, które mogą wymagać dostosowania kodu:
- Usunięto wsparcie dla PostgreSQL 13 (wymagany przynajmniej PostgreSQL 14)
- Usunięto wsparcie dla PostGIS 3.0 i GDAL 3.0
- Połączenia MySQL domyślnie używają teraz zestawu znaków utf8mb4 zamiast utf8/utf8mb3
- Dodawanie alternatyw do
EmailMultiAlternatives
jest teraz możliwe wyłącznie przez metodęattach_alternative()
Funkcje przestarzałe
Kilka elementów zostało oznaczonych jako "deprecated" i zostanie usuniętych w przyszłych wersjach:
- Argument
all
w funkcjidjango.contrib.staticfiles.finders.find()
zastąpiony przezfind_all
- Używanie
ordering
jako parametru funkcji agregujących PostgreSQL (ArrayAgg
,JSONBAgg
,StringAgg
) - zamiast tego należy używaćorder_by
- Automatyczne użycie
request.user
gdyuser=None
w funkcjachlogin()
ialogin()
Podsumowanie
Django 5.2 to solidne wydanie LTS, które wprowadza szereg przydatnych funkcji i ulepszeń. Automatyczny import modeli w shellu, wsparcie dla złożonych kluczy głównych i rozbudowana asynchroniczność to funkcje, które mogą znacząco poprawić komfort pracy i wydajność naszych aplikacji.
Jako wydanie z długoterminowym wsparciem, Django 5.2 będzie otrzymywać poprawki bezpieczeństwa aż do kwietnia 2028 roku, co czyni je dobrym wyborem dla nowych projektów produkcyjnych. Jeśli korzystasz z Django 4.2 LTS, masz jeszcze czas na migrację (do kwietnia 2026), ale warto zacząć planować ten proces.
A Wy, co sądzicie o nowym Django 5.2? Które funkcje wydają się Wam najbardziej przydatne? Dajcie znać!
Spodobał Ci się post?
Podziel się nim!
Masz uwagi do posta, chcesz porozmawiać, szukasz pomocy z Pythonem i Django? Napisz do mnie!
Zapraszam do zadawania pytań przez formularz kontaktowy. Pamiętaj, że jeśli potrzebujesz wsparcia, możesz napisać do mnie - pomogę.
Jeśli jesteście zainteresowani pogłębieniem wiedzy o Django i Pythonie, zapraszam do dołączenia do społeczności PyMasters, gdzie regularnie omawiamy najnowsze technologie i najlepsze praktyki. A dla początkujących programistów przygotowałem darmowego ebooka "WARSZTAT JUNIORA: Przewodnik po kluczowych kompetencjach i narzędziach dla początkującego programisty Pythona".
Do następnego razu!