← Назад к вопросам

Что такое ManyToManyField в Django?

2.3 Middle🔥 211 комментариев
#Django#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Что такое ManyToManyField в Django?

ManyToManyField — это поле в Django ORM, которое создаёт связь «многие-ко-многим» между двумя моделями. Это означает, что один объект одной модели может быть связан с несколькими объектами другой модели и наоборот.

Основной концепт

Реальный пример: Студенты и Курсы

from django.db import models

class Course(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()
    
    def __str__(self):
        return self.name

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course, related_name='students')
    
    def __str__(self):
        return self.name

Соотношение:

  • Один студент может учиться на многих курсах
  • Один курс может иметь много студентов

Как это работает в базе данных

Django автоматически создаёт промежуточную таблицу:

student (id, name)
├── id | name
├── 1  | Alice
└── 2  | Bob

course (id, name)
├── id | name
├── 1  | Python
└── 2  | Django

student_courses (id, student_id, course_id)  ← Создаёт Django!
├── id | student_id | course_id
├── 1  | 1          | 1          (Alice → Python)
├── 2  | 1          | 2          (Alice → Django)
└── 3  | 2          | 1          (Bob → Python)

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

Добавление связей

# Получаем объекты
alice = Student.objects.get(name='Alice')
python_course = Course.objects.get(name='Python')

# Добавляем курс к студенту
alice.courses.add(python_course)

# Или через ID
alice.courses.add(1)

# Добавляем несколько сразу
alice.courses.add(1, 2, 3)

Удаление связей

# Удалить конкретный курс
alice.courses.remove(python_course)

# Удалить все курсы
alice.courses.clear()

Получение связанных объектов

# Получить все курсы студента
alice = Student.objects.get(name='Alice')
print(alice.courses.all())
# <QuerySet [<Course: Python>, <Course: Django>]>

# Получить всех студентов на курсе (через related_name)
python = Course.objects.get(name='Python')
print(python.students.all())
# <QuerySet [<Student: Alice>, <Student: Bob>]>

Фильтрация

# Студенты, которые учатся на конкретном курсе
students = Student.objects.filter(courses__name='Python')

# Курсы, на которых учится конкретный студент
courses = Course.objects.filter(students__name='Alice')

# Проверка: есть ли у студента конкретный курс
has_course = alice.courses.filter(id=python_course.id).exists()

Кастомная промежуточная таблица (through)

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

class Enrollment(models.Model):
    """Кастомная модель для связи"""
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    enrolled_date = models.DateField(auto_now_add=True)  # Дата зачисления
    grade = models.CharField(max_length=2, null=True)    # Оценка
    
    class Meta:
        unique_together = ('student', 'course')
    
    def __str__(self):
        return f"{self.student.name}{self.course.name}"

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course, through='Enrollment')

Работа с кастомной промежуточной таблицей

# Добавить курс со здравственной датой
enrollment = Enrollment.objects.create(
    student=alice,
    course=python_course,
    enrolled_date='2024-01-15'
)

# Получить курсы с дополнительной информацией
enrollments = Enrollment.objects.filter(student=alice)
for e in enrollments:
    print(f"{e.course.name} - зачислена {e.enrolled_date}")

Различие: ManyToManyField vs ForeignKey

# ForeignKey: один-ко-многим (от дочернего к родительскому)
class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)  # Много комментариев → 1 пост

# ManyToManyField: много-ко-многим (двусторонняя связь)
class Article(models.Model):
    tags = models.ManyToManyField(Tag)  # Много статей → много тегов, и наоборот

Производительность и оптимизация

# Плохо: N+1 проблема
for student in Student.objects.all():
    print(student.courses.all())  # Запрос для каждого студента!

# Хорошо: prefetch_related
students = Student.objects.prefetch_related('courses')
for student in students:
    print(student.courses.all())  # Только 2 запроса!

Практический пример: Социальная сеть

class User(models.Model):
    username = models.CharField(max_length=100, unique=True)
    followers = models.ManyToManyField(
        'self',
        symmetrical=False,
        related_name='following'
    )

# Использование
user = User.objects.get(username='alice')
user.followers.add(bob)  # Alice следит за Bob
print(user.followers.all())  # Кого Alice следит
print(user.following.all())  # Кто следит за Alice (благодаря related_name)

Итоги

  • ManyToManyField создаёт связь много-ко-многим
  • Django автоматически создаёт промежуточную таблицу
  • Используй add(), remove(), clear() для управления связями
  • Для дополнительных данных используй through с кастомной моделью
  • Не забывай про prefetch_related() для оптимизации запросов