Topic 5 of 6

Course materials are currently available in Russian only.

Связи между моделями

Данные блога связаны: у поста много комментариев, у пользователя — много постов. В Django связи описывают специальными полями.

ForeignKey

class Post(models.Model):
    author = models.CharField(max_length=50)
    title = models.CharField(max_length=200)


class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
    author = models.CharField(max_length=50)
    text = models.TextField()

ForeignKey — связь «многие к одному»: у каждого комментария один пост, у поста — сколько угодно комментариев. В базе у комментария появится столбец post_id.

  • on_delete — что делать при удалении поста: CASCADE — удалить его комментарии, PROTECT — запретить удаление, SET_NULL — обнулить ссылку;
  • related_name="comments" — имя обратной стороны связи у поста.

Другие связи: OneToOneField («один к одному», например профиль пользователя) и ManyToManyField («многие ко многим», например теги).

Работа со связью

post = Post.objects.create(author="anna", title="Привет, мир")
Comment.objects.create(post=post, author="bob", text="Круто!")

c.post.title                             # пост комментария
post.comments.all()                      # комментарии поста
post.comments.count()
post.comments.order_by("-id")[:3]        # три последних

get_object_or_404(Post, pk=pk) — найти запись или бросить Http404, чтобы посетитель увидел «не найдено», а не ошибку сервера.

Поиск через связь

Comment.objects.filter(post__author="anna")        # комментарии к постам anna
Post.objects.filter(comments__author="bob")         # посты, где комментировал bob

Двойное подчёркивание проходит по связи — ORM сам сделает JOIN таблиц. По связям можно идти и дальше: post__author__startswith="a".

Если в цикле нужно поле связанной записи (c.post.title), добавьте .select_related("post") — иначе для каждого комментария уйдёт отдельный запрос в базу.

Подсчёт по связи

from django.db.models import Count

Post.objects.annotate(n=Count("comments"))                         # у каждого поста — поле n
Post.objects.annotate(n=Count("comments")).filter(n__gte=2)        # фильтр по аннотации
Post.objects.annotate(n=Count("comments")).order_by("-n")[:5]      # самые обсуждаемые

Всё это — один SQL-запрос с GROUP BY и HAVING.

Итог

  • ForeignKey(Модель, on_delete=…, related_name=…);
  • c.post, post.comments.all(), get_object_or_404;
  • поиск через связь: post__author, select_related;
  • annotate(n=Count("связь")), фильтр и сортировка по аннотации.