Зарегистрируйтесь, чтобы продолжить обучение

Многие ко Многим Python: Django ORM

На предыдущем уроке были рассмотрены связи "один к одному" и "один ко многим". Тогда же был упомянут и вид связи "многие ко многим".

Для этого вида связи тоже есть специальный тип поля: 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 лишнюю таблицу создавать не будет.

Этот подвид связи достаточно интересен, но применяется наиболее редко. Более подробно почитать про особенности использования вспомогательной модели вы сможете в специальном разделе документации.


Самостоятельная работа

  1. Изучите связь между Post и Tag.
  2. Откройте REPL и создайте несколько постов и тегов. Свяжите каждый тег с одним-двумя постами.
  3. Попробуйте описать проверку, которая выясняет, соседствуют ли два заданных тега хотя бы в одном посте (хотя бы один пост имеет ссылки на оба тега). Подсказка: .filter(tags=tag1) выбирает сущности, связанные с tag1 и такие фильтры можно компоновать!

Дополнительные материалы

  1. Model field reference: Relationship fields

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Python, Разработка веб-приложений и сервисов используя Django, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»