Многие ко Многим

В этой статье мы поговорим о связи многие-ко-многим. Перед чтением нужно обязательно познакомиться со связью один-ко-многим.

Начнём сразу с примера, у вас есть блог, в котором есть пользователи и посты:

class Post(models.Model):
    author = models.ForeignKey("User", on_delete=models.CASCADE)
    ...

class User(models.Model):
    login = models.CharField(max_length=200)
    email = models.EmailField()
    ...

Теперь вы решили приделать к блогу комментарии. Комментарии очень похожи на посты. У них тоже есть автор, текст, дата создания и так далее. Они отличаются только тем, что всегда привязаны к посту:

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    text = models.TextField()
    ...

Теперь хочется добавить в блог лайки. Их можно хранить так же, как и комментарии: у них тоже есть автор и они привязаны к посту. Лайк каждого человека хранится как отдельная запись в базе: лайк какого-нибудь Серёжи под постом про космонавтику — это просто объект модели Like:

class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

У вас получилась модель Like, в которой есть только два ForeignKey. С их помощью модель Like связывает пользователей и посты отношением многие-ко-многим: у каждого пользователя может быть много лайков под постами, а у каждого поста может быть много лайкнувших пользователей. Такие модели встречаются так часто, что в Django есть поле ManyToManyField. Оно само создаёт таблицы, похожие на модель Like, вам остаётся только указать что с чем связать. Код ниже делает то же самое, что и наверху, но с помощью ManyToManyField:

class Post(models.Model):
    author = models.ForeignKey("User", on_delete=models.CASCADE)
    liked_by = models.ManyToManyField("User", related_name="liked_posts")
    ...

liked_by — это новое поле поста, которое возвращает всех людей, которые его лайкнули. Что интересно, у User тоже появилось новое поле liked_posts, которое возвращает все посты, понравившиеся этому пользователю. Оно появится само, автоматически.

Под капотом ManyToManyField тоже создаёт модель, один в один такую же, как описанная выше модель Like. Просто благодаря ManyToManyField она стала как будто бы полем моделей Post и User. Сравните использование ManyToManyField и модели Like, созданной вручную:

# Сколько лайков на посте?
likes_amount = post.liked_by.count()
likes_amount = Like.objects.filter(post=post).count()

# Что лайкал этот юзер?
likes = user.liked_posts
likes = Like.objects.filter(author=user)

# Поставить лайк посту
post.liked_by.add(user)
Like.objects.create(author=user, post=post)

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Хочу код-ревью