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

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

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

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

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


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

  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
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 27 февраля

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

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

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

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