На предыдущем уроке были рассмотрены связи "один к одному" и "один ко многим". Тогда же был упомянут и вид связи "многие ко многим".
Для этого вида связи тоже есть специальный тип поля: ManyToManyField. Этот вид связи подразумевает, что у объектов обеих моделей будет RelatedManager, отражающий множество связанных сущностей. Более того, это будет отдельный вид менеджера — ManyRelatedManager
.
В учебном проекте вы можете найти модель Tag
, на которую Post
ссылается следующим образом:
class Tag(models.TimestampedModel):
"""A tag for the group of posts."""
# ...
class Post(models.TimestampedModel):
"""A blog post."""
# ...
tags = models.ManyToManyField(Tag)
У любого поста может быть несколько тегов, а может не быть ни одного. И одним тегом можно пометить более чем один пост. Поэтому пост и тег соотносятся как "многие ко многим". Заметьте, что опция on_delete
не указана: кажется неверным удалять посты, если вдруг будет удалён тег, и уж точно не следует удалять теги при удалении помеченного ими поста.
Работают с такого рода связью следующим образом:
intro = Tag.objects.create(title='Introduction')
# INSERT INTO "blog_tag" ...
# Execution time: 0.010531s [Database: default]
Post.objects.get(title='Intro').tags.add(intro)
# SELECT "blog_post". ...
# Execution time: 0.000412s [Database: default]
# BEGIN
# Execution time: 0.000046s [Database: default]
# INSERT
# OR
# IGNORE INTO "blog_post_tags" ("post_id", "tag_id") SELECT 1,
# 1
Заметьте, что вставка производится в таблицу "blog_post_tags" — это вспомогательная таблица, которую создает Django ORM, чтобы связать таблицы "blog_post" и "blog_tag". В проекте для неё вы не найдёте соответствующей модели. Как видите, ORM может скрывать даже части схемы базы и берёт на себя всю работу!
Также обратите внимание на то, как происходит связывание тега и поста: ManyRelatedManager в атрибуте .tags
экземпляра модели Post
имеет специальный метод .add()
. Он принимает произвольное количество тегов с которыми нужно связать данный пост.
У Tag
атрибут, соответствующий связи тега с постом, будет создан автоматически и будет иметь имя post_set
. Это тоже будет ManyRelatedManager
, с помощью которого можно добавить уже посты к тегу:
update = Tag.objects.create(title='Update')
# INSERT INTO "blog_tag" ...
# Execution time: 0.009486s [Database: default]
update.post_set.add(Post.objects.get(title='Update'))
# SELECT "blog_post". ...
# Execution time: 0.000216s [Database: default]
# BEGIN
# Execution time: 0.000046s [Database: default]
# INSERT
# OR
# IGNORE INTO "blog_post_tags" ("post_id", "tag_id") SELECT 2,
# 2
# Execution time: 0.000337s [Database: default]
update.post_set.count()
# SELECT COUNT ...
# Execution time: 0.000151s [Database: default]
# 1
Разорвать связь между объектами можно с помощью метода .remove()
у любого из двух ManyRelatedManager
, передав в аргументах метода перечень тегов для поста и наоборот. Но помните, что сами теги при этом удалены не будут! Объекты, связанные как "многие ко многим" удалять следует с помощью их собственных менеджеров, а не с помощью ManyRelatedManager
.
Связь через выделенную модель
По умолчанию на каждый ManyToManyField
Django ORM создаст по вспомогательной таблице, в которой будет два столбца типа FOREIGN KEY
, которые и будут ссылаться на сущности в связываемых таблицах. Такое неявное использование таблиц удобно в большинстве случаев.
Однако встречаются ситуации, когда факт связи между двумя сущностями хочется сопроводить какой-либо дополнительной информацией. Например, хочется знать, в какой момент времени некий тег был прикреплён к некоторому посту в блоге или какой пользователь этот тег посту присвоил. Здесь пригодилась бы отдельная модель, но не хочется терять все те удобства, которые даёт использование ManyRelatedManager
.
Специально для подобных случаев ManyToManyField
позволяет с помощью аргумента through
указать конкретную модель, которая будет выступать связью. И разумеется, если модель будет указана, то ORM лишнюю таблицу создавать не будет.
Этот подвид связи достаточно интересен, но применяется наиболее редко. Более подробно почитать про особенности использования вспомогательной модели вы сможете в специальном разделе документации.
Самостоятельная работа
- Изучите связь между
Post
иTag
. - Откройте REPL и создайте несколько постов и тегов. Свяжите каждый тег с одним-двумя постами.
- Попробуйте описать проверку, которая выясняет, соседствуют ли два заданных тега хотя бы в одном посте (хотя бы один пост имеет ссылки на оба тега). Подсказка:
.filter(tags=tag1)
выбирает сущности, связанные сtag1
и такие фильтры можно компоновать!
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.