Witaj w kolejnym wpisie z serii: "Zrozumieć Django". W poprzednim wpisie opisałem podstawowe informacje o modelach, opowiedziałem o zapytaniach oraz pokazałem przykład wykorzystania modelu do stworzenia prostego formularza kontaktowego.
W tym wpisie opiszę, jak Django zarządza bardziej skomplikowanymi zagadnieniami. Na warsztat wchodzą relacje pomiędzy modelami oraz genialny system zarządzania zmianami w bazie danych: migracje.
Czym w ogóle są relacje pomiędzy modelami?
Relacje między modelami są sposobem na określenie zależności pomiędzy danymi w aplikacji. Służą do modelowania rzeczywistych relacji między obiektami lub pojęciami w aplikacji.
Patrząc na przykład aplikacji do zarządzania szkołą, w której występują modele Student, Employee (pracownik), Department i Course, można podać następujące przykłady relacji:
- Jeden-do-jednego (OneToOne): używana, gdy dwa modele mają zawsze dokładnie jeden rekord, który jest powiązany z obydwoma modelami. Na przykład modele Student i StudentCard mogą mieć relację jeden-do-jednego, ponieważ każdy student ma dokładnie jedną kartę studencką, a każda karta studencka jest przypisana dokładnie jednemu studentowi.
- Jeden-do-wielu (OneToMany): używana, gdy jeden rekord jednego modelu jest powiązany z wieloma rekordami innego modelu. Na przykład modele Department i Employee mogą mieć relację jeden-do-wielu, ponieważ jeden dział może mieć wielu pracowników, a każdy pracownik jest przypisany do dokładnie jednego działu.
Relacja jeden do wielu: Department ma wielu Employee ale każdy Employee ma jeden Department. - Wiele-do-wielu (ManyToMany): używana, gdy rekordy jednego modelu mogą być powiązane z wieloma rekordami drugiego modelu, a rekordy drugiego modelu mogą być powiązane z wieloma rekordami pierwszego modelu. Na przykład modele Course i Student mogą mieć relację wiele-do-wielu, ponieważ wielu studentów może uczęszczać na wiele kursów. Jest to relacja podobna jak jeden-do-wielu, przy czym konieczna jest tabela łącząca wpisy pomiędzy tabelami Course i Student. Django tworzy ją automatycznie lub też można stworzyć tą tabelę samemu, co pozwala na rozszerzenie funkcjonalności. Relację wiele do wielu opiszę dokładnie w kolejnych wpisach.
Relacja wiele do wielu
Relacja jeden do jednego: Student i StudentCard.
W przykładowej szkole mamy model Student i StudentCard. Modele te są powiązane ze sobą relacją jeden do jeden. Po co to stosować? Można przecież zrobić duży model Student i zawrzeć w nim wszystkie dane związane ze StudentCard. Relacja w tym przypadku ułatwia zarządzanie danymi oraz lepsze odwzorowanie obiektów rzeczywistych na te stosowane w systemie informatycznym - przecież Student i jego legitymacja to oddzielne byty. Pozwala też uniknąć powielenia danych. Zamiast trzymać imię i nazwisko studenta w modelu StudentCard, trzymamy te dane w modelu Student i używamy relacji do uzyskania dostępu z poziomu StudentCard.
Praktyka
Modele Student i StudendCard
from django.db import models
# Create your models here.
class Student(models.Model):
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255, blank=True)
last_name = models.CharField(max_length=255)
email = models.EmailField(max_length=255)
birth_date = models.DateField()
phone_number = models.CharField(max_length=255)
addres_street = models.CharField(max_length=255)
address_city = models.CharField(max_length=255)
address_zipcode = models.CharField(max_length=255)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class StudentCard(models.Model):
student = models.OneToOneField(Student, on_delete=models.CASCADE)
card_id = models.CharField(max_length=20)
expiration_date = models.DateField()
creation_date = models.DateField(auto_now_add=True)
photo = models.ImageField(upload_to="student_photos")
def __str__(self):
return f"{self.student.first_name} {self.student.last_name} {self.card_id}"
Żeby dodać modele do bazy należy wykonać migrację.
./mamage.py makemigrations
./manage.py migrate
A żeby zobaczyć modele "na żywo", najlepiej dodać je do admina.
from django.contrib import admin
# Register your models here.
from .models import StudentCard, Student
admin.site.register(Student)
admin.site.register(StudentCard)


Jak widać na screenach, możemy łatwo powiązać StudentCard ze Student. Trudniej to zrobić w drugą stronę. Po stworzeniu kilku studentów, dodawanie legitymacji w adminie wygląda tak:

Czyli Django podsuwa w adminie listę obiektów, z którymi można się połączyć. Co ciekawe, jeśli wybierzemy obiekt już przypisany, dostaniemy błąd:

Poniżej kilka przykładów, jak dostać się do danych StudentCard z poziomu modelu Student i odwrotnie.
W tym celu wykorzystam Djangową powłokę uruchamianą komendą ./manage.py shell
(ref: https://docs.djangoproject.com/en/4.1/ref/django-admin/#shell). Bardzo polecam naukę pracy z powłoką - znacznie ułatwia tworzenie skomplikowanych zapytań ORM i nie tylko.
» ./manage.py shell
Python 3.10.7 (main, Sep 15 2022, 01:51:29) [Clang 14.0.0 (clang-1400.0.29.102)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> # import modeli
>>> from accounts.models import StudentCard, Student
>>> # pobranie i wyświetlenie pierwszego studenta
>>> student = Student.objects.first()
>>> student
<Student: John Doe>
>>> # pobranie pierwszej legitymacji
>>> student_card = StudentCard.objects.first()
>>> student_card
<StudentCard: John Doe 123456>
>>> # wyświetlenie studenta z poziomu legitymacji
>>> student_card.student
<Student: John Doe>
>>> student_card.student.first_name
'John'
>>> student_card.student.last_name
'Doe'
>>> # wyświetlenie danych legitymacji z poziomu studenta
>>> # czyli wykorzystanie relacji odwrotnej
>>> student.studentcard
<StudentCard: John Doe 123456>
>>> student.studentcard.card_id
'123456'
>>> student.studentcard.expiration_date
datetime.date(2023, 8, 17)
>>>
Jak widać, bardzo łatwo można poruszać się pomiędzy obiektami powiązanymi relacją jeden do jednego, z obu "końców". Jeśli model nie zawiera pola z relacją, to w zapytaniu należy wykorzystać nazwę zależnego modelu pisaną małymi literami. Można też zastosować słowo kluczowe related_name
(ref: https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.ForeignKey.related_name) na polu student w modelu StudentCard. Pozwoli to nadać dowolną nazwę relacji odwrotnej, czyli wtedy, kiedy z poziomu Student pytamy o legitymację. Gdyby related_name
zawierało card
to zapytanie z poziomu studenta byłoby: student.card.card_id
» ./manage.py shell
Python 3.10.7 (main, Sep 15 2022, 01:51:29) [Clang 14.0.0 (clang-1400.0.29.102)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from accounts.models import StudentCard, Student
>>> student = Student.objects.first()
>>> student.card
<StudentCard: John Doe 123456>
>>>
Wracając do pliku models.py, na uwagę zasługuje atrybut on_delete
na polu student w modelu StudentCard. Jest odpowiedzialny za utrzymanie spójności danych. Przykładem może być usunięcie rekordu legitymacji studenckiej w przypadku usunięcia studenta, usunięcie rejestracji samochodowej, gdy auto uległo kasacji, itp. Atrybut jest wymagany przy relacjach jeden do jednego i jeden do wielu.
Atrybut on_delete
może przyjmować następujące wartości:
- CASCADE — zachowuje się jak SQL "ON_DELETE_CASCADE" - czyli usuwa powiązane modele.
- PROTECT — zabezpiecza obiekt przed usunięciem, jeśli relacja istnieje. Tutaj Django zwróci błąd przy próbie usunięcia studenta, który posiada legitymację.
- SET_NULL - ustawi wartość NULL w przypadku usunięcia studenta.
- SET_DEFAULT - ustawi wartość domyślną, która musi być zdefiniowana na polu zawierającym relacje poprzez atrybut "default".
- SET() - podobne do default, tylko bardziej zaawansowane. Pozwala na przekazanie funkcji, której wynik będzie użyty w momencie usunięcia obiektu (studenta)
- DO_NOTHING - nie rób nic. Jeśli baza danych oczekuje spójności, spowoduje błąd, IntegrityError.
Ze wszystkich powyższych najczęściej spotykanym jest CASCADE i SET_NULL - z naciskiem na pierwsze. W naszym przykładzie usunięcie studenta (na przykład skreślenie z listy studentów) spowoduje usunięcie legitymacji.
W następnym wpisie omówię, z przykładami, relację jeden do wielu — będzie ciekawie. Serdecznie zapraszam.
Zapisz się na newsletter by otrzymać informacje, kiedy pojawi się nowy post w tej serii. Zapraszam!
Ps. Spodobał Ci się post? Udostępnij go na swoich kanałach.
PS2. Masz uwagi do posta, chcesz porozmawiać, szukasz pomocy z Pythonem i Django? Napisz do mnie!
1 thought on “Zrozumieć Django: Relacje pomiędzy modelami”
Comments are closed.