ForeignKey в Django: связи между моделями
ForeignKey в моделях Django: параметры on_delete, related_name, null, to_field, обратная связь, запросы через __, self, миграции, формы и админка.
Содержание · 14 разделов
- Как объявить ForeignKey
- Параметры ForeignKey
- on_delete: что происходит при удалении родителя
- DB_CASCADE, DB_SET_NULL и DB_SET_DEFAULT в Django 6.1
- related_name и related_query_name
- null и blank: необязательная связь
- limit_choices_to
- to_field
- db_constraint и db_index
- Обратная связь: _set и related_name
- Запросы через связи: двойное подчёркивание
- select_related и prefetch_related
- ForeignKey, OneToOneField или ManyToManyField
- Ссылка на модель строкой и AUTH_USER_MODEL
- Связь модели с самой собой (‘self’)
- Миграции: ForeignKey в таблице с данными
- ForeignKey в формах
- ForeignKey в админке: raw_id_fields и autocomplete_fields
- Частые ошибки
- fields.E304: Reverse accessor clashes
- fields.E320: SET_NULL без null=True
- ValueError: unsaved related object
- IntegrityError: FOREIGN KEY constraint failed
- Частые вопросы
- Как получить id связанного объекта без запроса к базе?
- Срабатывают ли сигналы и delete() при каскадном удалении?
- Нужна ли миграция при смене on_delete?
- Можно ли сделать ForeignKey на модель без числового первичного ключа?
- Чем ForeignKey отличается от простого IntegerField с id?
- Итог
ForeignKey в моделях Django описывает связь «многие к одному»: у книги один автор, у автора много книг. Поле объявляют в «зависимой» модели и передают ему модель-родителя и обязательный параметр on_delete:
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
В базе появляется колонка author_id с внешним ключом и индексом. В коде book.author возвращает объект автора, а author.books.all() — все его книги. Ниже — все параметры django models foreign key с таблицей вариантов on_delete, обратная связь, запросы через __, выбор между ForeignKey, OneToOneField и ManyToManyField, связь модели с самой собой, миграции для таблиц с данными, формы и админка. Примеры проверены на Django 6.1 и SQLite и работают в Django 5.2 LTS, кроме отмеченных вариантов DB_*. Общие сведения о полях и Meta — в статье про модели Django.
Как объявить ForeignKey
Модели для всех примеров статьи — библиотека с авторами, издательствами и рубриками:
# library/models.py
from django.conf import settings
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Publisher(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
publisher = models.ForeignKey(
Publisher,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="books",
)
added_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="added_books",
)
def __str__(self):
return self.title
Первый аргумент — модель, на которую ссылается поле: класс или строка с его именем. Второй — on_delete, его можно передать позиционно или по имени. Без on_delete Django не создаст поле:
TypeError: ForeignKey.__init__() missing 1 required positional argument: 'on_delete'
После makemigrations команда sqlmigrate показывает, что получится в SQLite:
CREATE TABLE "library_book" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(300) NOT NULL,
"added_by_id" integer NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
"author_id" bigint NOT NULL REFERENCES "library_author" ("id") DEFERRABLE INITIALLY DEFERRED,
"publisher_id" bigint NULL REFERENCES "library_publisher" ("id") DEFERRABLE INITIALLY DEFERRED
);
CREATE INDEX "library_book_author_id_d9a3b67e" ON "library_book" ("author_id");
Django добавляет к имени поля суффикс _id, создаёт ограничение внешнего ключа (REFERENCES) и индекс. В Python доступны оба имени:
war = Book.objects.create(title="Война и мир", author=tolstoy)
anna = Book.objects.create(title="Анна Каренина", author_id=tolstoy.pk)
print(war.author, war.author_id)
# → Лев Толстой 1
war.author_id читает значение из уже загруженной строки, а первое обращение к war.author выполняет отдельный запрос за объектом автора. Если нужен только идентификатор, используйте author_id.
Параметры ForeignKey
| Параметр | По умолчанию | Что делает |
|---|---|---|
on_delete | обязательный | что делать со ссылающимися записями при удалении родителя |
related_name | <модель>_set | имя обратной связи у родителя: author.books |
related_query_name | как related_name | имя связи в фильтрах родителя: Author.objects.filter(book__...) |
null | False | разрешает NULL в колонке — связь становится необязательной в базе |
blank | False | разрешает пустое значение в формах и админке |
limit_choices_to | нет | ограничивает варианты выбора в ModelForm и админке |
to_field | первичный ключ | поле родителя, на которое ссылается ключ; оно должно быть unique=True |
db_constraint | True | создавать ли ограничение внешнего ключа в базе |
db_index | True | создавать ли индекс по колонке |
db_column | <поле>_id | имя колонки в таблице |
default | нет | значение по умолчанию — первичный ключ родителя, а не объект |
on_delete: что происходит при удалении родителя
on_delete срабатывает в одну сторону: при удалении автора Django решает, что делать с его книгами. Удаление книги на автора не влияет.
| Вариант | Что происходит со ссылающимися записями | Требования | Когда выбирать |
|---|---|---|---|
CASCADE | удаляются вместе с родителем | — | запись бессмысленна без родителя: комментарии, строки заказа |
PROTECT | удаление родителя запрещено, исключение ProtectedError | — | справочники: издательства, валюты, статусы |
RESTRICT | удаление запрещено (RestrictedError), если только ссылающаяся запись не удаляется в той же операции каскадом по другой связи | — | сложные графы связей, где PROTECT слишком строг |
SET_NULL | ссылка становится NULL | null=True | необязательная связь: книги удалённого пользователя |
SET_DEFAULT | ссылка получает значение default | default | есть запись-заглушка с известным ключом |
SET(...) | ссылка получает переданное значение или результат вызова функции | — | заглушка, которую нужно найти или создать |
DO_NOTHING | Django ничего не делает | своё ON DELETE в базе | целостность обеспечивает сама СУБД |
DB_CASCADE | удаляет база через ON DELETE CASCADE | Django 6.1 | большие объёмы, сигналы не нужны |
DB_SET_NULL | NULL проставляет база | Django 6.1, null=True | то же, что SET_NULL, без загрузки объектов в Python |
DB_SET_DEFAULT | база проставляет db_default | Django 6.1, db_default; нет в MySQL и MariaDB | заглушка на уровне базы |
PROTECT в действии:
pub = Publisher.objects.create(name="Азбука")
Book.objects.create(title="Война и мир", author=tolstoy, publisher=pub)
pub.delete()
# → ProtectedError: ("Cannot delete some instances of model 'Publisher' because they
# are referenced through protected foreign keys: 'Book.publisher'.", {<Book: Война и мир>})
CASCADE возвращает число удалённых объектов по моделям:
dostoevsky.delete() # у автора три книги
# → (4, {'library.Book': 3, 'library.Author': 1})
При CASCADE Django сам находит и удаляет зависимые записи. Сигналы pre_delete и post_delete отправляются для каждой из них, но метод delete() связанных моделей не вызывается: логику удаления, которая должна срабатывать и при каскаде, переносите в обработчики сигналов.
Разница между PROTECT и RESTRICT видна на трёх моделях из приложения lab: песня ссылается на исполнителя (CASCADE) и на альбом (RESTRICT):
class Artist(models.Model):
name = models.CharField(max_length=10)
class Album(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
class Song(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
album = models.ForeignKey(Album, on_delete=models.RESTRICT)
album.delete()
# → RestrictedError: Cannot delete some instances of model 'Album' because they
# are referenced through restricted foreign keys: 'Song.album'.
artist.delete()
# → (3, {'lab.Song': 1, 'lab.Album': 1, 'lab.Artist': 1})
Альбом напрямую удалить нельзя, а исполнителя можно: песня всё равно удаляется каскадом через Song.artist. С PROTECT удаление исполнителя тоже завершилось бы ошибкой.
Для SET() передавайте функцию, а не готовый объект: иначе запрос к базе выполнится при импорте models.py.
from django.contrib.auth import get_user_model
def get_deleted_user():
return get_user_model().objects.get_or_create(username="deleted")[0]
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET(get_deleted_user))
DB_CASCADE, DB_SET_NULL и DB_SET_DEFAULT в Django 6.1
Варианты DB_* передают работу базе данных: Django прописывает ON DELETE в ограничение внешнего ключа и не загружает зависимые объекты в Python. Пример из того же приложения lab:
class Board(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
board = models.ForeignKey(Board, on_delete=models.DB_CASCADE, related_name="posts")
"board_id" bigint NOT NULL REFERENCES "lab_board" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
board.delete()
# → (1, {'lab.Board': 1})
# SQL: DELETE FROM "lab_board" WHERE "lab_board"."id" IN (1)
Посты удалены базой, поэтому их нет в счётчике, а сигналы pre_delete и post_delete для них не отправляются. Ограничения:
- В одной модели нельзя смешивать варианты
DB_*и обычные (кромеDO_NOTHING) — ошибкаmodels.E050. - Если
Postудаляется базой, внешние ключи наPostтоже должны бытьDB_*— иначе ошибкаfields.E323. - Переход с
CASCADEнаDB_CASCADEменяет схему: миграция пересоздаёт ограничение. Смена обычных вариантов между собой (CASCADE→PROTECT) даёт миграцию без SQL.
related_name и related_query_name
related_name задаёт имя, по которому родитель обращается к зависимым записям. Без него Django использует <модель>_set: author.book_set.all(). Явное имя читается лучше и обязательно, когда в модели несколько ключей на одну и ту же модель:
class Match(models.Model):
home = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="home_matches")
away = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="away_matches")
winner = models.ForeignKey(
Team, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
)
Значение "+" отключает обратную связь: у Team не будет атрибута для winner.
related_query_name меняет имя связи только в фильтрах. По умолчанию оно совпадает с related_name:
class Player(models.Model):
team = models.ForeignKey(
Team, on_delete=models.CASCADE, related_name="players", related_query_name="player"
)
team.players.all() # атрибут — related_name
Team.objects.filter(player__name="Иванов") # фильтр — related_query_name
Для всех ключей модели сразу имя задают через Meta.default_related_name. В абстрактных моделях используйте шаблон related_name="%(app_label)s_%(class)s_set", чтобы у наследников не возникло одинаковых имён.
null и blank: необязательная связь
null=True разрешает NULL в колонке, blank=True — пустое значение в формах и админке. Для необязательной связи нужны оба:
publisher = models.ForeignKey(
Publisher, on_delete=models.PROTECT, null=True, blank=True, related_name="books"
)
Только null=True даст необязательное поле в базе, но обязательное в форме. Только blank=True пропустит пустую форму и упадёт на IntegrityError при сохранении.
limit_choices_to
Ограничивает список объектов, которые предлагают ModelForm и админка. Принимает словарь, объект Q или функцию, которая их возвращает:
added_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
limit_choices_to={"is_staff": True},
)
В форме останутся только сотрудники. На ORM-запросы и прямое присваивание book.added_by = user параметр не влияет. Функцию Django вызывает при создании каждой формы — так можно ограничить выбор, например, по текущей дате. Изменение limit_choices_to создаёт миграцию без SQL.
to_field
По умолчанию ключ ссылается на первичный ключ родителя. to_field указывает другое поле, и оно должно быть уникальным:
class Team(models.Model):
code = models.CharField(max_length=3, unique=True)
class Player(models.Model):
team = models.ForeignKey(Team, on_delete=models.CASCADE, to_field="code")
Player.objects.create(name="Иванов", team=zenit).team_id
# → 'ZEN'
Колонка team_id получает тип поля code — varchar(3). Пригодится при интеграции со старой базой, где связи построены на кодах или артикулах.
db_constraint и db_index
db_constraint=False создаёт колонку и индекс без ограничения REFERENCES. База перестаёт проверять, что родитель существует, и при обращении к несуществующему родителю Django выбросит DoesNotExist:
class Log(models.Model):
team = models.ForeignKey(Team, on_delete=models.DO_NOTHING, db_constraint=False)
log = Log.objects.create(team_id=999) # база не возражает
log.team
# → Team.DoesNotExist: Team matching query does not exist.
Применяйте это для заведомо «грязных» исторических данных или при шардинге базы. Индекс создаётся автоматически; db_index=False отключает его, если вы строите свой составной или частичный индекс через Meta.indexes.
Обратная связь: _set и related_name
У родителя появляется менеджер связанных объектов (RelatedManager). Он умеет всё, что умеет обычный менеджер, и ещё несколько методов для управления связью:
tolstoy.books.all()
# → <QuerySet [<Book: Война и мир>, <Book: Анна Каренина>]>
tolstoy.books.count()
# → 2
idiot = dostoevsky.books.create(title="Идиот") # author проставится сам
idiot.author
# → Фёдор Достоевский
| Метод | Что делает |
|---|---|
create(**kwargs) | создаёт объект, уже привязанный к родителю |
add(*objs) | привязывает существующие объекты (UPDATE); несохранённые — с bulk=False |
remove(*objs) | отвязывает объекты, проставляя NULL |
clear() | отвязывает все объекты |
set(objs) | приводит набор к переданному списку; при null=False только добавляет |
add() переносит объект от прежнего родителя. Несохранённый объект add() сохранит только с bulk=False:
book = Book(title="Бесы", author=tolstoy)
dostoevsky.books.add(book, bulk=False)
book.author
# → Фёдор Достоевский
remove() и clear() есть только у связей с null=True: ключ становится NULL, сами записи остаются в базе. У tolstoy.books этих методов нет, у pub.books — есть:
pub.books.remove(war)
war.refresh_from_db()
war.publisher
# → None
Запросы через связи: двойное подчёркивание
В базе для примеров раздела: у Льва Толстого «Война и мир» и «Анна Каренина», у Фёдора Достоевского «Идиот» и «Бесы», у Джейн Остин книг нет. Поля родителя в фильтрах указывают через __ — Django сделает JOIN:
Book.objects.filter(author__name="Лев Толстой")
# → <QuerySet [<Book: Война и мир>, <Book: Анна Каренина>]>
Book.objects.order_by("author__name", "title").values_list("title", flat=True)
# → <QuerySet ['Анна Каренина', 'Война и мир', 'Бесы', 'Идиот']>
filter(author=tolstoy), filter(author_id=1) и filter(author__id=1) дают одинаковый SQL без JOIN: условие проверяется по колонке author_id.
В обратную сторону фильтр идёт через related_query_name — здесь это books:
Author.objects.filter(books__title__startswith="Война")
# → <QuerySet [<Author: Лев Толстой>]>
Author.objects.filter(books__isnull=True) # авторы без книг
# → <QuerySet [<Author: Джейн Остин>]>
Фильтр по обратной связи возвращает родителя столько раз, сколько у него подходящих записей. Уберите дубли через distinct():
Author.objects.filter(books__title__contains="а")
# → <QuerySet [<Author: Лев Толстой>, <Author: Лев Толстой>]>
Author.objects.filter(books__title__contains="а").distinct()
# → <QuerySet [<Author: Лев Толстой>]>
Количество связанных записей считают через annotate():
from django.db.models import Count
Author.objects.annotate(n=Count("books")).values_list("name", "n")
# → <QuerySet [('Лев Толстой', 2), ('Фёдор Достоевский', 2), ('Джейн Остин', 0)]>
Lookups, Q, F и поведение цепочек filter() на связях «один-ко-многим» подробно разобраны в статье о фильтрации в Django ORM.
select_related и prefetch_related
Каждое обращение к book.author в цикле — отдельный запрос. На четырёх книгах замер дал 5 запросов вместо одного:
for book in Book.objects.all():
book.author.name
# → 5 запросов: 1 за книги + 4 за авторов
for book in Book.objects.select_related("author"):
book.author.name
# → 1 запрос с JOIN
for author in Author.objects.prefetch_related("books"):
list(author.books.all())
# → 2 запроса: авторы и все их книги через IN
select_related подходит для прямого ForeignKey и OneToOneField, prefetch_related — для обратных связей и ManyToManyField. Как находить такие лишние запросы и использовать Prefetch(), разобрано в статье о проблеме N+1 в Django ORM.
ForeignKey, OneToOneField или ManyToManyField
| Критерий | ForeignKey | OneToOneField | ManyToManyField |
|---|---|---|---|
| Тип связи | многие к одному | один к одному | многие ко многим |
| Пример | книга → автор | профиль → пользователь | книга ↔ жанры |
| Что в базе | колонка <поле>_id | колонка <поле>_id с UNIQUE | отдельная промежуточная таблица |
| Обратная сторона | менеджер: author.books.all() | один объект: user.profile | менеджер с обеих сторон |
on_delete | обязателен | обязателен | не нужен |
| Дополнительные данные о связи | в самой модели | в самой модели | через модель through |
| Подгрузка без N+1 | select_related | select_related | prefetch_related |
ForeignKey(unique=True) работает как OneToOneField, и Django предупреждает об этом (fields.W342). Разница в обратной стороне: OneToOneField возвращает объект, а не менеджер, поэтому выбирайте его. Если у записи может быть несколько родителей одного типа (у книги несколько жанров), нужен ManyToManyField. Если связь «многие ко многим» должна хранить дату или роль, сделайте промежуточную модель с двумя ForeignKey и укажите её в through.
Ссылка на модель строкой и AUTH_USER_MODEL
Вместо класса можно передать строку — Django найдёт модель после загрузки всех приложений:
class Car(models.Model):
# модель из этого же приложения, объявленная ниже
manufacturer = models.ForeignKey("Manufacturer", on_delete=models.CASCADE)
# модель из другого приложения: "метка_приложения.Модель"
dealer = models.ForeignKey("sales.Dealer", on_delete=models.PROTECT)
Строка решает две задачи: ссылку на модель, которая объявлена ниже в файле, и циклический импорт, когда два приложения ссылаются друг на друга.
Для связи с пользователем всегда используйте settings.AUTH_USER_MODEL, а не from django.contrib.auth.models import User:
from django.conf import settings
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
Так связь продолжит работать после перехода на свою модель пользователя, а миграции будут ссылаться на настройку, а не на конкретный класс. В коде вне models.py модель пользователя получают через get_user_model(). Как заменить стандартного пользователя — в статье про кастомную модель пользователя.
Связь модели с самой собой (‘self’)
Рубрики с подрубриками, сотрудники с руководителями, ветки комментариев описывают ключом на свою же модель. Первым аргументом передают строку "self":
class Category(models.Model):
name = models.CharField(max_length=100)
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="children",
)
У корневых рубрик parent пустой, поэтому null=True обязателен.
prog = Category.objects.create(name="Программирование")
py = Category.objects.create(name="Python", parent=prog)
dj = Category.objects.create(name="Django", parent=py)
prog.children.all()
# → <QuerySet [<Category: Python>]>
dj.parent.parent
# → Программирование
Category.objects.filter(parent__isnull=True) # корневые рубрики
# → <QuerySet [<Category: Программирование>]>
Category.objects.filter(parent__parent=prog) # «внуки»
# → <QuerySet [<Category: Django>]>
Каждый уровень вложенности — новый JOIN или запрос. Для дерева из двух-трёх уровней этого хватает. Для глубоких деревьев с выборкой всех потомков используют рекурсивные CTE в SQL или пакеты вроде django-treebeard.
Миграции: ForeignKey в таблице с данными
Если добавить обязательный ForeignKey в таблицу, где уже есть строки, makemigrations спросит, что записать в существующие записи:
It is impossible to add a non-nullable field 'series' to book without specifying a default.
This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Вариант 1 опасен: введённый идентификатор (например, 1) должен существовать в таблице родителя, иначе миграция упадёт на IntegrityError. Надёжнее добавить связь в три шага.
-
Добавьте поле с
null=Trueи создайте миграцию:series = models.ForeignKey(Series, on_delete=models.PROTECT, null=True)python manage.py makemigrations library -n book_series -
Создайте пустую миграцию и заполните ключ в
RunPython:python manage.py makemigrations library --empty -n fill_book_seriesfrom django.db import migrations def fill_series(apps, schema_editor): Series = apps.get_model("library", "Series") Book = apps.get_model("library", "Book") default, _ = Series.objects.get_or_create(name="Без серии") Book.objects.filter(series__isnull=True).update(series=default) class Migration(migrations.Migration): dependencies = [("library", "0002_book_series")] operations = [ migrations.RunPython(fill_series, migrations.RunPython.noop), ] -
Уберите
null=Trueи снова выполнитеmakemigrations. На вопрос о существующихNULLвыберите2) Ignore for now— данные уже заполнены предыдущей миграцией. Затемpython manage.py migrate.
На Windows вместо python можно использовать py, на macOS и Linux — python3, если python не указывает на Python 3. Подробнее о миграциях данных и откате — в статье про миграции Django.
ForeignKey в формах
В ModelForm поле ForeignKey превращается в ModelChoiceField — выпадающий список объектов родителя. Поле обязательно, если у модели нет blank=True. Если прислать несуществующий идентификатор, форма вернёт ошибку Select a valid choice. That choice is not one of the available choices.
Список вариантов сужают через limit_choices_to в модели или через queryset в конструкторе формы, когда условие зависит от запроса:
from django import forms
from .models import Author, Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ["title", "author", "added_by"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["author"].queryset = Author.objects.order_by("name")
self.fields["author"].label_from_instance = lambda obj: obj.name.upper()
label_from_instance меняет подпись варианта, не трогая __str__ модели. Валидация, виджеты и обработка формы во view — в статье про формы Django.
ForeignKey в админке: raw_id_fields и autocomplete_fields
По умолчанию админка рисует для ForeignKey выпадающий список со всеми объектами родителя. На тысячах записей страница начинает грузиться медленно. Есть два решения:
raw_id_fields— текстовое поле для идентификатора и кнопка поиска во всплывающем окне;autocomplete_fields— список с поиском, который подгружает варианты по мере ввода. Работает, только если в админке родителя заданыsearch_fields.
# library/admin.py
from django.contrib import admin
from .models import Author, Book
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
search_fields = ["name"] # нужно для autocomplete_fields
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ["title", "author", "publisher"]
list_select_related = ["author", "publisher"]
list_filter = ["author"]
autocomplete_fields = ["author"]
raw_id_fields = ["publisher"]
Без search_fields у AuthorAdmin проверка Django остановит запуск:
<class 'library.admin.BookAdmin'>: (admin.E040) AuthorAdmin must define "search_fields",
because it's referenced by BookAdmin.autocomplete_fields.
list_select_related убирает N+1 в списке объектов, где выводится автор. limit_choices_to в виде объекта Q не действует на поле из raw_id_fields. Чтобы редактировать книги прямо на странице автора, используйте инлайны в Django Admin.
Частые ошибки
fields.E304: Reverse accessor clashes
Два ForeignKey на одну модель без related_name претендуют на одно имя match_set:
lab.Match.away: (fields.E304) Reverse accessor 'Team.match_set' for 'lab.Match.away'
clashes with reverse accessor for 'lab.Match.home'.
HINT: Add or change a related_name argument to the definition for 'lab.Match.away' or 'lab.Match.home'.
Задайте каждому полю свой related_name или отключите обратную связь через "+".
fields.E320: SET_NULL без null=True
lab.Match.winner: (fields.E320) Field specifies on_delete=SET_NULL, but cannot be null.
HINT: Set null=True argument on the field, or change the on_delete rule.
Добавьте null=True (и blank=True для форм) или выберите другой on_delete.
ValueError: unsaved related object
ValueError: save() prohibited to prevent data loss due to unsaved related object 'author'.
Родителя создали через Author(name=...), но не сохранили, поэтому у него нет первичного ключа. Сначала вызовите author.save() или создайте его через Author.objects.create().
IntegrityError: FOREIGN KEY constraint failed
База не нашла родителя с переданным идентификатором: Book.objects.create(author_id=999) при отсутствии автора 999. Проверьте значение, которое пришло из формы или API, и порядок создания записей. В SQLite это сообщение FOREIGN KEY constraint failed, в PostgreSQL — violates foreign key constraint.
Частые вопросы
Как получить id связанного объекта без запроса к базе?
Читайте атрибут <поле>_id: book.author_id. Он хранится в строке книги, тогда как book.author.id сначала загрузит автора. То же для присваивания: book.author_id = 5 не требует объекта автора.
Срабатывают ли сигналы и delete() при каскадном удалении?
При CASCADE Django отправляет pre_delete и post_delete для каждого удаляемого объекта, но не вызывает переопределённый delete() связанных моделей. QuerySet.delete() тоже не вызывает delete() у объектов. При DB_CASCADE удаляет база, и сигналы для зависимых записей не отправляются.
Нужна ли миграция при смене on_delete?
makemigrations создаст миграцию в любом случае, потому что параметры поля записываются в историю миграций. Для обычных вариантов (CASCADE, PROTECT, SET_NULL и других) sqlmigrate покажет (no-op) — схема не меняется. Переход на DB_CASCADE или DB_SET_NULL пересоздаёт ограничение внешнего ключа, а в SQLite — всю таблицу.
Можно ли сделать ForeignKey на модель без числового первичного ключа?
Да. Ключ получает тип первичного ключа родителя: для UUIDField(primary_key=True) колонка будет хранить UUID. Чтобы ссылаться на другое уникальное поле, например код или артикул, используйте to_field.
Чем ForeignKey отличается от простого IntegerField с id?
IntegerField хранит число, но база не проверяет, что такой родитель существует, нет on_delete, обратной связи, фильтров через __, select_related и выпадающего списка в формах. Обычное число оправдано только для ссылок на данные в другой базе или внешнем сервисе.
Итог
ForeignKey — основной способ связать модели Django по схеме «многие к одному». Минимальный набор решений для каждого ключа:
- выберите
on_deleteосознанно:CASCADEдля зависимых данных,PROTECTдля справочников,SET_NULLдля необязательных связей,DB_*в Django 6.1, когда важна скорость удаления больших объёмов; - задайте понятный
related_name— особенно если ключей на одну модель несколько; - для необязательной связи ставьте
null=Trueиblank=True; - ссылайтесь на пользователя через
settings.AUTH_USER_MODEL, а на модели других приложений — строкой"app.Model"; - добавляйте обязательный ключ в таблицу с данными в три миграции;
- подгружайте связи через
select_relatedиprefetch_related, в админке включайтеautocomplete_fieldsилиraw_id_fields.
Остальные методы QuerySet — get(), update(), агрегаты и транзакции — собраны в шпаргалке по Django ORM. Полный список параметров поля — в справочнике полей Django.
- #Django
- #Django ORM
- #ForeignKey
- #Модели
- #Связи моделей