Организация связей между таблицами
Зачем нужны связи
Данные почти никогда не живут изолированно: у поста есть автор и комментарии, у товара — категория, у пользователя — профиль. В реляционной базе это выражается связями между таблицами. В Django есть три типа связей.
Один ко многим: ForeignKey
У книги один автор, у автора — много книг:
class Author(models.Model):
name = models.CharField(max_length=100)
country = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
В базе появится столбец author_id с внешним ключом.
on_delete — что делать при удалении
| Значение | Поведение при удалении автора |
|---|---|
CASCADE |
Удалить и все его книги |
PROTECT |
Запретить удаление, пока есть книги (ProtectedError) |
SET_NULL |
Записать NULL в author (нужно null=True) |
SET_DEFAULT |
Записать значение по умолчанию |
DO_NOTHING |
Ничего (опасно — нарушится целостность) |
Работа со связью
pushkin = Author.objects.create(name="Пушкин", country="Россия")
book = Book.objects.create(title="Евгений Онегин", author=pushkin)
book.author # объект Author
book.author.name # "Пушкин"
book.author_id # число — без лишнего запроса
Обратная связь и related_name
От автора можно получить его книги через обратный менеджер:
pushkin.books.all() # благодаря related_name="books"
pushkin.books.count()
pushkin.books.create(title="Капитанская дочка")
Без related_name менеджер назывался бы book_set: pushkin.book_set.all(). Понятное имя делает код читаемее.
Многие ко многим: ManyToManyField
У поста много тегов, у тега много постов:
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, related_name="posts", blank=True)
Django создаст промежуточную таблицу blog_post_tags с парами (post_id, tag_id).
django = Tag.objects.create(name="django")
python = Tag.objects.create(name="python")
post.tags.add(django, python) # добавить
post.tags.remove(python) # убрать
post.tags.set([django]) # заменить набор целиком
post.tags.clear() # убрать все
post.tags.all() # теги поста
django.posts.all() # посты с тегом (обратная сторона)
Если в промежуточной таблице нужны свои поля (например, дата добавления), указывают модель-посредник: ManyToManyField(Tag, through="PostTag").
Один к одному: OneToOneField
Каждому пользователю — ровно один профиль:
from django.conf import settings
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile")
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to="avatars/", blank=True)
user.profile.bio # обратная сторона — не менеджер, а сам объект
profile.user.username
Так расширяют встроенную модель пользователя — подробно в модуле про аутентификацию.
Ссылка на модель строкой
Если модель объявлена ниже или в другом приложении, указывайте её строкой: models.ForeignKey("Author", ...) или models.ForeignKey("library.Author", ...). На пользователя ссылаются через settings.AUTH_USER_MODEL.
Запросы через связи
Двойное подчёркивание позволяет «шагать» по связям в фильтрах:
Book.objects.filter(author__name="Пушкин")
Book.objects.filter(author__country="Россия")
Post.objects.filter(tags__name="django")
Author.objects.filter(books__title__icontains="война") # авторы, у которых есть такие книги
ORM сам построит нужные JOIN.
Проблема N+1
Цикл for book in Book.objects.all(): print(book.author.name) делает отдельный запрос за каждым автором. Решение — select_related("author") для ForeignKey и prefetch_related("tags") для ManyToMany. Подробно — в уроке про оптимизацию запросов.
Итог
ForeignKey— один ко многим, ставится на стороне «многих».ManyToManyField— многие ко многим, управление черезadd/remove/set/clear.OneToOneField— один к одному, например профиль пользователя.on_deleteобязателен дляForeignKeyиOneToOneField.related_nameзадаёт имя обратной связи; фильтры по связям — через__.
Практика решено 0 из 7
У книги один автор, у автора много книг. Какое поле добавить в модель Book?
Пост может иметь много тегов, тег — много постов. Какая это связь?
Опишите модель Comment со связью на Post: при удалении поста комментарии удаляются,
а у поста комментарии доступны как post.comments. Добавьте поле body (TextField).
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
body = models.TextField()
Что делает on_delete=models.PROTECT?
Получите все книги, автор которых родился в стране Россия (у модели Author есть поле country,
у Book — ForeignKey с именем author). Результат — в books.
books = Book.objects.filter(author__country="Россия")
Модель Post имеет tags = models.ManyToManyField(Tag). Какие операции корректны?
Как по умолчанию (без related_name) называется обратный менеджер у объекта Author для модели Book? (например, author.???)
Войдите, чтобы отмечать пройденные уроки.