Связи моделей: ForeignKey, M2M, OneToOne
Реальные данные связаны: у поста есть автор, у заказа — товары, у статьи — теги. Django выражает эти связи тремя типами полей-отношений.
Суть: ForeignKey — связь «многие к одному», ManyToManyField — «многие ко многим», OneToOneField — «один к одному». Django автоматически создаёт обратные связи.
Три типа связей
Реляционные базы строятся на связях между таблицами. Django выражает их декларативно:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
Author, on_delete=models.CASCADE, related_name="posts"
)
tags = models.ManyToManyField("Tag", related_name="posts")
class Profile(models.Model):
author = models.OneToOneField(
Author, on_delete=models.CASCADE, related_name="profile"
)
bio = models.TextField()
Связи на уровне таблиц выглядят так:
ForeignKey (многие → один):
Post.author_id ──────▶ Author.id
много постов у одного автора
ManyToManyField (многие ↔ многие):
Post ──┐ ┌── промежуточная таблица ──┐ ┌── Tag
└──▶│ post_id │ tag_id │◀──┘
у поста много тегов, у тега много постов
OneToOneField (один ↔ один):
Profile.author_id ─── уникально ───▶ Author.id
ForeignKey и on_delete
Самая частая связь — ForeignKey. Она хранит ссылку на одну запись родительской таблицы. Обязательный параметр on_delete говорит, что делать при удалении родителя: CASCADE — удалить и потомков, PROTECT — запретить удаление, SET_NULL — обнулить ссылку (нужно null=True). Это важное решение о целостности данных, поэтому Django заставляет указать его явно.
Обратные связи и related_name
Django автоматически создаёт «обратную» сторону связи. Если у Post есть author, то у объекта автора появляется доступ ко всем его постам. Параметр related_name="posts" задаёт имя этого обратного доступа:
author = Author.objects.get(pk=1)
author.posts.all() # все посты автора (обратная связь)
post.author # автор поста (прямая связь)
post.tags.all() # теги поста (M2M)
post.tags.add(tag) # добавить тег
Как это работает под капотом
Под капотом ForeignKey — это просто столбец author_id с числом, ссылающимся на первичный ключ. Связывание объектов через общий ключ — языко-независимая операция «join». Вот как она выглядит на чистом Python:
# Попробуй сам ▶ — join по внешнему ключу вручную
authors = {1: "Анна", 2: "Борис"}
posts = [
{"title": "Django ORM", "author_id": 1},
{"title": "Шаблоны", "author_id": 1},
{"title": "Формы", "author_id": 2},
]
# прямая связь: post.author
for p in posts:
print(f'{p["title"]:14} автор: {authors[p["author_id"]]}')
print("---")
# обратная связь: author.posts.all()
from collections import defaultdict
reverse = defaultdict(list)
for p in posts:
reverse[p["author_id"]].append(p["title"])
for aid, name in authors.items():
print(f'{name}: {reverse[aid]}')
Django делает это эффективнее — через SQL JOIN, но идея ровно та же: одна таблица хранит ключ, другая по нему находится.
ManyToMany и промежуточная таблица
Связь «многие ко многим» нельзя выразить одним столбцом, поэтому Django создаёт скрытую промежуточную таблицу с парами ключей. Если нужны дополнительные поля у связи (например, дата добавления тега), используют параметр through с собственной моделью-связкой.
Частые ошибки
- Забыть on_delete у ForeignKey. Это обязательный параметр, без него ошибка.
- Выбрать CASCADE там, где нужен PROTECT. Удаление автора молча снесёт все его посты.
- Не задать related_name. Тогда обратная связь зовётся
post_set— менее читаемо, и возможны конфликты. - Делать запрос в цикле по связям (N+1). Используйте
select_related/prefetch_related.
Best practices
- Всегда задавайте осмысленный
related_nameв множественном числе для FK и M2M. - Обдуманно выбирайте
on_delete: для критичных данных —PROTECT. - Используйте
select_relatedдля ForeignKey иprefetch_relatedдля ManyToMany, чтобы избежать N+1. - Для M2M с доп. полями применяйте
through-модель.
Итоги
Три типа связей покрывают любые отношения данных: ForeignKey (многие-к-одному), ManyToMany (многие-ко-многим), OneToOne (один-к-одному). Django создаёт обратные связи, а related_name делает их читаемыми. Параметр on_delete определяет целостность. Дальше — как смотреть данные через админку и оптимизировать запросы.