На предыдущем уроке были рассмотрены связи "один к одному" и "один ко многим". Тогда же был упомянут и вид связи "многие ко многим".
Для этого вида связи тоже есть специальный тип поля: ManyToManyField. Этот вид связи подразумевает, что у объектов обеих моделей будет RelatedManager, отражающий множество связанных сущностей. Более того, это будет отдельный вид менеджера — ManyRelatedManager
.
Например, у нас есть следущие модели:
class Tag(models.TimestampedModel):
name = models.CharField(max_length=30)
class Post(models.TimestampedModel):
title = models.CharField(max_length=100)
body = models.CharField(max_length=300)
tags = models.ManyToManyField(Tag)
У любого поста может быть несколько тегов, а может не быть ни одного. И одним тегом можно пометить более чем один пост. Поэтому пост и тег соотносятся как "многие ко многим". Заметьте, что опция on_delete
не указана: кажется неверным удалять посты, если вдруг будет удалён тег, и уж точно не следует удалять теги при удалении помеченного ими поста.
Работают с такого рода связью следующим образом:
intro = Tag.objects.create(name='Introduction')
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
также есть ManyRelatedManager
, позволяющий работать с постами, связанными с конкретным тегом.
# Создание нескольких тегов
python_tag = Tag.objects.create(name='Python')
django_tag = Tag.objects.create(name='Django')
orm_tag = Tag.objects.create(name='ORM')
# Создание поста с несколькими тегами
post = Post.objects.create(
title='Django ORM Tips',
body='Useful tips for working with Django ORM...'
)
post.tags.add(python_tag, django_tag, orm_tag)
# Поиск всех постов с определенным тегом
django_posts = Post.objects.filter(tags=django_tag)
# Поиск всех тегов поста
post_tags = post.tags.all()
# Получение постов с несколькими тегами одновременно
advanced_posts = Post.objects.filter(tags__in=[python_tag, orm_tag]).distinct()
Разорвать связь между объектами можно с помощью метода .remove()
у любого из двух ManyRelatedManager
, передав в аргументах метода перечень тегов для поста и наоборот. Но помните, что сами теги при этом удалены не будут. Объекты, связанные как "многие ко многим" удалять следует с помощью их собственных менеджеров, а не с помощью ManyRelatedManager
.
Связь через выделенную модель
По умолчанию на каждый ManyToManyField
Django ORM создаст по вспомогательной таблице, в которой будет два столбца типа FOREIGN KEY
, которые и будут ссылаться на сущности в связываемых таблицах. Такое неявное использование таблиц удобно в большинстве случаев.
Однако встречаются ситуации, когда факт связи между двумя сущностями хочется сопроводить какой-либо дополнительной информацией. Например, хочется знать, в какой момент времени некий тег был прикреплён к некоторому посту в блоге или какой пользователь этот тег посту присвоил. Здесь пригодилась бы отдельная модель, но не хочется терять все те удобства, которые даёт использование ManyRelatedManager
.
Специально для подобных случаев ManyToManyField
позволяет с помощью аргумента through
указать конкретную модель, которая будет выступать связью. И разумеется, если модель будет указана, то ORM лишнюю таблицу создавать не будет.
Этот подвид связи достаточно интересен, но применяется наиболее редко. Более подробно почитать про особенности использования вспомогательной модели вы сможете в специальном разделе документации.
Самостоятельная работа
- Изучите связь между
Post
иTag
. - Откройте REPL и создайте несколько постов и тегов. Свяжите каждый тег с одним-двумя постами.
- Попробуйте описать проверку, которая выясняет, соседствуют ли два заданных тега хотя бы в одном посте (хотя бы один пост имеет ссылки на оба тега). Подсказка:
.filter(tags=tag1)
выбирает сущности, связанные сtag1
и такие фильтры можно компоновать!
Дополнительные материалы

Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.