Как в Django ORM определить связь many-to-many между моделями

Ответ

Связь «многие-ко-многим» в Django ORM создается с помощью поля models.ManyToManyField. Django автоматически создает промежуточную (join) таблицу для управления этими отношениями.

Это позволяет, например, связать одного автора с несколькими книгами и одну книгу с несколькими авторами.

Базовый пример

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=100)
    # Определение связи many-to-many
    authors = models.ManyToManyField(Author, related_name='books')

    def __str__(self):
        return self.title

Основные операции

Для управления связями используются специальные методы менеджера:

  • add(): добавляет объекты в связь.
  • remove(): удаляет объекты из связи.
  • set(): полностью заменяет набор связанных объектов.
  • clear(): удаляет все объекты из связи.
# Получаем объекты
author1 = Author.objects.get(name='Лев Толстой')
book = Book.objects.get(title='Война и мир')

# Добавляем автора к книге
book.authors.add(author1)

# Фильтрация по связанной модели
books_by_tolstoy = Book.objects.filter(authors__name='Лев Толстой')

Пользовательская промежуточная модель (through)

Если вам нужно хранить дополнительные данные о самой связи (например, дату вступления участника в группу), используется опция through.

class Membership(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
    # Дополнительное поле
    role = models.CharField(max_length=50, default='author')

# Модель Book теперь ссылается на Membership
class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author, through='Membership')

Ответ 18+ 🔞

А, ManyToManyField! Ну это ж классика, блядь, ёпта! Сейчас объясню, как эта штука работает, а то некоторые думают, что это какая-то хуйня непонятная.

Смотри, представь себе авторов и книги. Один автор может написать дохуя книг, да? А одна книга может быть написана дохуя авторами — вот тебе и связь «многие-ко-многим». Django, умная жопа, сам создаёт промежуточную таблицу, чтобы это всё связать. Не надо руками в SQL лезть и JOIN'ы городить — всё за тебя сделают, красота!

Вот смотри, как это выглядит в коде, блядь:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=100)
    # Вот она, мать её, связь! Говорим: "у книги много авторов"
    authors = models.ManyToManyField(Author, related_name='books')

    def __str__(self):
        return self.title

Видишь? Всё просто, как три копейки! Авторы отдельно, книги отдельно, а связь между ними — магия, блядь!

А теперь самое интересное — как этим пользоваться. Тут есть специальные методы, которые делают всё за тебя. Запоминай, а то потом будешь голову ломать!

# Нашли нашего графомана
author1 = Author.objects.get(name='Лев Толстой')
# И его творение
book = Book.objects.get(title='Война и мир')

# Привязываем автора к книге — раз и готово!
book.authors.add(author1)

# А теперь хотим все книги Толстого найти — фильтруем по связанной модели
books_by_tolstoy = Book.objects.filter(authors__name='Лев Толстой')

Но это ещё цветочки, блядь! Бывает, что в самой связи нужно хранить дополнительные данные. Например, не просто «автор написал книгу», а «автор написал книгу в роли главного редактора» или там «соавтора». Вот тут на сцену выходит опция through — пользовательская промежуточная модель, ёпта!

class Membership(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
    # А вот и доп поле! Теперь можем указать роль
    role = models.CharField(max_length=50, default='author')

# И в модели Book теперь ссылаемся через Membership
class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author, through='Membership')

Вот так вот, блядь! Теперь у тебя в связи есть дополнительные поля, и ты можешь хранить там что угодно — дату, роль, процент авторского взноса, хоть цвет носков автора в момент написания! Главное — не перемудрить, а то потом сам не разберёшься, что накодил, в рот меня чих-пых!

Короче, ManyToManyField — это мощный инструмент, который экономит кучу времени. Используй на здоровье, только не забудь миграции сделать, а то ничего не заработает, и будешь сидеть, чесать репу, как тот самый Герасим, который только «Му-му» говорить умел!