Сьогодні ми перейдемо до більш поширеного типу зв'язку, який активно використовується в реальних застосунках: зв'язок один-до-багатьох з використанням ForeignKey.
Що таке зв'язок один-до-багатьох?
Уявіть, що у вас є папка з документами. Папка — це "батько", вона може містити багато "дітей" (документів), але кожен документ належить тільки до однієї папки. Це і є відношення один-до-багатьох (One-to-Many).
З точки зору реляційної бази даних, зв'язок один-до-багатьох виглядає так:
- Один запис у першій таблиці (батько) може бути пов'язаний із кількома записами у другій таблиці (діти).
- Кожен запис у другій таблиці посилається лише на один запис у першій таблиці.
У Django такий зв'язок реалізується за допомогою поля ForeignKey.
Коли використовувати зв'язок ForeignKey?
ForeignKey є найбільш часто використовуваним типом зв'язку, який чудово підходить у наступних сценаріях:
- Магазин і його товари: один магазин може продавати багато продуктів, але кожен продукт належить одному магазину.
- Статті та автори: один автор може написати багато статей, але кожна стаття написана одним автором.
- Категорії та продукти: одна категорія може містити багато продуктів, але кожен продукт належить лише одній категорії.
Реалізація зв'язку один-до-багатьох через ForeignKey
Давайте реалізуємо простий приклад. Уявімо, що у нас є система управління блогом, яка складається з двох моделей: Author та Post. Один автор може написати багато постів.
Крок 1: створюємо моделі
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
return self.title
Розберемо код:
Модель
Author:- Містить ім'я
nameта emailemailавтора. - Поле
__str__повертає ім'я автора для зручного відображення.
- Містить ім'я
Модель
Post:- Містить заголовок
title, контентcontentта посилання на автора через полеauthor. - Поле
authorвикористовуєForeignKey, вказуючи на модельAuthor.
- Містить заголовок
Ключовий параметр у
ForeignKey—on_delete:- Тут ми вказали
on_delete=models.CASCADE, що означає: якщо запис батьківської таблиціAuthorвидаляється, то всі пов'язані постиPostтакож будуть автоматично видалені.
- Тут ми вказали
Крок 2: виконуємо міграції
Після опису моделей необхідно зробити міграції, щоб зміни вступили в силу в базі даних.
python manage.py makemigrations
python manage.py migrate
Крок 3: додаємо дані
Тепер давайте створимо кілька авторів та постів у Django shell.
python manage.py shell
# Імпортуємо наші моделі
from blog.models import Author, Post
# Створюємо авторів
author_1 = Author.objects.create(name="Іван Іванов", email="ivan@example.com")
author_2 = Author.objects.create(name="Анна Смирнова", email="anna@example.com")
# Створюємо пости
post_1 = Post.objects.create(title="Перша стаття", content="Зміст статті 1", author=author_1)
post_2 = Post.objects.create(title="Друга стаття", content="Зміст статті 2", author=author_1)
post_3 = Post.objects.create(title="Третя стаття", content="Зміст статті 3", author=author_2)
Тепер у нас є два автори (Іван та Анна) і три пости, які відносяться до них.
Крок 4: робота із записами
Отримання пов'язаних об'єктів. За допомогою ForeignKey ви можете легко витягувати пов'язані записи:
# Отримуємо автора статті
post = Post.objects.get(title="Перша стаття")
print(post.author) # Виведе: Іван Іванов
# Отримуємо всі статті автора
author = Author.objects.get(name="Іван Іванов")
posts = author.post_set.all()
print(posts) # Виведе QuerySet з двома статтями
Django автоматично створює related_name у форматі modelname_set (у даному випадку post_set) для зворотного зв'язку.
Створюємо статті через автора
Ви можете створювати статті через модель Author, не вказуючи явно ForeignKey.
author = Author.objects.get(name="Анна Смирнова")
author.post_set.create(title="Четверта стаття", content="Зміст статті 4")
Поведінка при видаленні: on_delete
Важливим моментом при використанні ForeignKey є вказівка поведінки при видаленні пов'язаних об'єктів. Django надає кілька опцій:
CASCADE: видаляє всі пов'язані об'єкти (за замовчуванням).SET_NULL: встановлює значенняNULL, якщо це дозволено полем.PROTECT: видає помилку, якщо видаляються пов'язані записи.DO_NOTHING: нічого не робить (не рекомендується).SET_DEFAULT: встановлює значення за замовчуванням.
Приклад:
author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True, blank=True)
Приклади бізнес-сценаріїв
Інтернет-магазин:
Категорія
Categoryі ТовариProduct:class Category(models.Model): name = models.CharField(max_length=100) class Product(models.Model): name = models.CharField(max_length=100) category = models.ForeignKey(Category, on_delete=models.CASCADE)
Система бронювання:
- Кімната
Roomі БронюванняBooking:class Room(models.Model): number = models.IntegerField() class Booking(models.Model): room = models.ForeignKey(Room, on_delete=models.PROTECT) date = models.DateField()
- Кімната
Часті помилки та особливості
Відсутність міграцій:
- Не забудь зробити міграції після додавання нового поля
ForeignKey, інакше база даних не знатиме про твої зміни.
- Не забудь зробити міграції після додавання нового поля
Помилка при видаленні об'єктів:
- Не вказуй
on_delete=DO_NOTHING, якщо не впевнений у наслідках. Це може призвести до порушення цілісності даних.
- Не вказуй
Зворотній доступ:
- Якщо не вказати
related_name, Django згенерує його автоматично. Але це ім'я може бути незручним. Краще вказувати явно:author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="posts")
- Якщо не вказати
Проблеми з продуктивністю:
- Використовуй
select_related()для оптимізації запитів, якщо тобі потрібно отримати пов'язані дані.
- Використовуй
Практичне застосування
Тепер ти знаєш, як організувати зв'язок один-до-багатьох за допомогою ForeignKey у Django. Цей тип зв'язку — основа більшості веб-додатків, від блогів до систем управління замовленнями чи CRM. Чим більше ти працюєш з моделями, тим більше починаєш цінувати гнучкість і потужність ForeignKey.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ