Связи между моделями
Данные блога связаны: у поста много комментариев, у пользователя — много постов. В 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("связь")), фильтр и сортировка по аннотации.