0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Сущности предметной области существуют не сами по себе. Они часто зависят друг от друга. На уровне базы данных такие связи задаются через внешние ключи или даже промежуточные таблицы, как в случае связи "многие ко многим". ORM, в свою очередь, используют эти ключи для работы со связями, а также добавляют множество полезных методов, которые упрощают работу с зависимыми сущностями: выборкой, добавлением, модификацией и удалением.</p>
1
<p>Сущности предметной области существуют не сами по себе. Они часто зависят друг от друга. На уровне базы данных такие связи задаются через внешние ключи или даже промежуточные таблицы, как в случае связи "многие ко многим". ORM, в свою очередь, используют эти ключи для работы со связями, а также добавляют множество полезных методов, которые упрощают работу с зависимыми сущностями: выборкой, добавлением, модификацией и удалением.</p>
2
<p><a>Учебный проект</a>моделирует предметную область системы для ведения персональных блогов. Сейчас нам интересна модель<a>Post</a>- модель записи (поста) в блоге. Пользователи (модель User) связаны с постами "один ко многим":</p>
2
<p><a>Учебный проект</a>моделирует предметную область системы для ведения персональных блогов. Сейчас нам интересна модель<a>Post</a>- модель записи (поста) в блоге. Пользователи (модель User) связаны с постами "один ко многим":</p>
3
<ul><li>Один пользователь может быть автором<em>множества</em>постов</li>
3
<ul><li>Один пользователь может быть автором<em>множества</em>постов</li>
4
<li>У каждого поста всегда<em>один</em>автор</li>
4
<li>У каждого поста всегда<em>один</em>автор</li>
5
</ul><h2>Описание связи между моделями</h2>
5
</ul><h2>Описание связи между моделями</h2>
6
<p>Для того чтобы связать пост и автора, было использовано поле типа ForeignKey, которое в терминах баз данных представляет собой<em>внешний ключ</em>. Ниже соответствующий фрагмент:</p>
6
<p>Для того чтобы связать пост и автора, было использовано поле типа ForeignKey, которое в терминах баз данных представляет собой<em>внешний ключ</em>. Ниже соответствующий фрагмент:</p>
7
<p>Такого описания достаточно для Django ORM: он будет знать, какие запросы следует сделать в БД, чтобы при обращении к полю creator экземпляра модели Post вы получали уже экземпляр пользователя. При этом ORM экономит ресурсы системы и по умолчанию не пытается получить сразу все возможные данные: пока вы не запросите доступ к автору поста, автор не будет запрошен из базы!</p>
7
<p>Такого описания достаточно для Django ORM: он будет знать, какие запросы следует сделать в БД, чтобы при обращении к полю creator экземпляра модели Post вы получали уже экземпляр пользователя. При этом ORM экономит ресурсы системы и по умолчанию не пытается получить сразу все возможные данные: пока вы не запросите доступ к автору поста, автор не будет запрошен из базы!</p>
8
<blockquote><p>Опция on_delete=models.Cascade описана ниже.</p>
8
<blockquote><p>Опция on_delete=models.Cascade описана ниже.</p>
9
</blockquote><h2>Создание связей между объектами</h2>
9
</blockquote><h2>Создание связей между объектами</h2>
10
<p>При создании объекта модели, имеющей внешний ключ, в качестве значения поля обычно указывают уже созданный (сохранённый в БД) экземпляр другой модели. Такое возможно, например, при создании объектов с помощью менеджера - вы можете даже создавать несколько связанных объектов в одном выражении:</p>
10
<p>При создании объекта модели, имеющей внешний ключ, в качестве значения поля обычно указывают уже созданный (сохранённый в БД) экземпляр другой модели. Такое возможно, например, при создании объектов с помощью менеджера - вы можете даже создавать несколько связанных объектов в одном выражении:</p>
11
<p>Пример показывает, что сначала был создан объект User, а потом уже зависящий от него объект Post. В этом случае вычисление аргументов вызова функции или метода перед вызовом самого метода приходится очень кстати. Впрочем, можно было и заранее создать или запросить User и уже потом передать в вызов Post.objects.create().</p>
11
<p>Пример показывает, что сначала был создан объект User, а потом уже зависящий от него объект Post. В этом случае вычисление аргументов вызова функции или метода перед вызовом самого метода приходится очень кстати. Впрочем, можно было и заранее создать или запросить User и уже потом передать в вызов Post.objects.create().</p>
12
<p>Передача экземпляра в роли значения поля не всегда бывает удобна. Порой вы будете иметь на руках только id связанного объекта и делать запрос .get(id=id) будет не слишком оправдано. В таких ситуациях можно использовать автоматически генерируемое поле с тем же именем, что и у внешнего ключа, но с суффиксом _id: это поле принимает в качестве значения id объекта:</p>
12
<p>Передача экземпляра в роли значения поля не всегда бывает удобна. Порой вы будете иметь на руках только id связанного объекта и делать запрос .get(id=id) будет не слишком оправдано. В таких ситуациях можно использовать автоматически генерируемое поле с тем же именем, что и у внешнего ключа, но с суффиксом _id: это поле принимает в качестве значения id объекта:</p>
13
<p>Поле xyz_id доступно не только при создании объекта, но имеется и у загружаемых из базы объектов. При обращении к этому полю запрос к связанной таблице<em>не производится</em>: это поле физически расположено в таблице текущей модели и является тем самым столбцом типа FOREIGN KEY, который бы вы использовали, если бы писали SQL вручную.</p>
13
<p>Поле xyz_id доступно не только при создании объекта, но имеется и у загружаемых из базы объектов. При обращении к этому полю запрос к связанной таблице<em>не производится</em>: это поле физически расположено в таблице текущей модели и является тем самым столбцом типа FOREIGN KEY, который бы вы использовали, если бы писали SQL вручную.</p>
14
<h2>Получение данных из связанных моделей</h2>
14
<h2>Получение данных из связанных моделей</h2>
15
<p>С точки зрения поста запрос данных пользователя максимально прост: вы просто обращаетесь к полям вложенного объекта. При<em>первом</em>обращении к любым данным пользователя будет выполнен запрос в базу, затем данные пользователя будут запомнены и новых запросов ORM делать не будет:</p>
15
<p>С точки зрения поста запрос данных пользователя максимально прост: вы просто обращаетесь к полям вложенного объекта. При<em>первом</em>обращении к любым данным пользователя будет выполнен запрос в базу, затем данные пользователя будут запомнены и новых запросов ORM делать не будет:</p>
16
<p>С полем Post.creator, кажется, всё понятно. Но как получить доступ к постам пользователя, или, проще говоря, сделать запрос в обратную сторону? Всегда остаётся возможность написать что-то вроде Post.objects.filter(creator=...). Но авторы Django ORM предусмотрели и более простой способ:</p>
16
<p>С полем Post.creator, кажется, всё понятно. Но как получить доступ к постам пользователя, или, проще говоря, сделать запрос в обратную сторону? Всегда остаётся возможность написать что-то вроде Post.objects.filter(creator=...). Но авторы Django ORM предусмотрели и более простой способ:</p>
17
<p>Как видно, у объекта модели User имеется атрибут post_set, значением которого выступает объект типа <..длинное имя..>.RelatedManager. Не стоит переживать из-за имени типа: это лишь менеджер, но менеджер связанной модели. Работать с этим менеджером можно так же, как с любым другим - накладывать фильтры, сортировку. Разница будет заключаться только в том, что в запросах всегда будет присутствовать условие, выбирающее лишь посты конкретного пользователя.</p>
17
<p>Как видно, у объекта модели User имеется атрибут post_set, значением которого выступает объект типа <..длинное имя..>.RelatedManager. Не стоит переживать из-за имени типа: это лишь менеджер, но менеджер связанной модели. Работать с этим менеджером можно так же, как с любым другим - накладывать фильтры, сортировку. Разница будет заключаться только в том, что в запросах всегда будет присутствовать условие, выбирающее лишь посты конкретного пользователя.</p>
18
<p>Что же касается имени атрибута, соответствующего RelatedManager, то<em>по умолчанию</em>оно получается прибавлением к имени связанной модели в нижнем регистре суффикса _set. Но можно при описании поля указать опцию related_name="желаемое_имя", тогда у связанной модели атрибут получит уже заданное вами имя.</p>
18
<p>Что же касается имени атрибута, соответствующего RelatedManager, то<em>по умолчанию</em>оно получается прибавлением к имени связанной модели в нижнем регистре суффикса _set. Но можно при описании поля указать опцию related_name="желаемое_имя", тогда у связанной модели атрибут получит уже заданное вами имя.</p>
19
<h2>Удаление связанных сущностей</h2>
19
<h2>Удаление связанных сущностей</h2>
20
<p>Связь "один ко многим" никак не ограничивает удаление отдельных постов пользователя. Можно даже удалить все посты некоторого пользователя запросом some_user.post_set.delete(). Или удалить посты, удовлетворяющие некоторому условию, если перед вызовом .delete() как-то ограничить выборку.</p>
20
<p>Связь "один ко многим" никак не ограничивает удаление отдельных постов пользователя. Можно даже удалить все посты некоторого пользователя запросом some_user.post_set.delete(). Или удалить посты, удовлетворяющие некоторому условию, если перед вызовом .delete() как-то ограничить выборку.</p>
21
<p>А вот удаление пользователя могло бы привести к нарушению целостности базы данных. Поэтому по умолчанию Django ORM не позволяет выполнить такие опасные действия: при попытке выполнить запрос вы получите ошибку.</p>
21
<p>А вот удаление пользователя могло бы привести к нарушению целостности базы данных. Поэтому по умолчанию Django ORM не позволяет выполнить такие опасные действия: при попытке выполнить запрос вы получите ошибку.</p>
22
<p>Чаще всего удаление пользователя подразумевает удаление и всех его постов, поэтому Django ORM позволяет указать опцию on_delete=models.CASCADE в описании внешнего ключа. Объекты с такой связью будут автоматически удалены при удалении "родительского" объекта. Существуют и другие варианты реакции на удаление родителя, обо всех возможностях вы можете почитать в документации к ForeignKey (<a>ссылка</a>).</p>
22
<p>Чаще всего удаление пользователя подразумевает удаление и всех его постов, поэтому Django ORM позволяет указать опцию on_delete=models.CASCADE в описании внешнего ключа. Объекты с такой связью будут автоматически удалены при удалении "родительского" объекта. Существуют и другие варианты реакции на удаление родителя, обо всех возможностях вы можете почитать в документации к ForeignKey (<a>ссылка</a>).</p>
23
<h2>Связь "один к одному"</h2>
23
<h2>Связь "один к одному"</h2>
24
<p>Кроме связей вида "один ко многим" существуют ещё связи "один к одному" и "многие ко многим". Последний вид связи мы рассмотрим позже, а пока рассмотрим связь "один к одному".</p>
24
<p>Кроме связей вида "один ко многим" существуют ещё связи "один к одному" и "многие ко многим". Последний вид связи мы рассмотрим позже, а пока рассмотрим связь "один к одному".</p>
25
<p>Этот вид связи в Django ORM реализуется с помощью типа<a>OneToOneField</a>и характерен лишь тем, что у парной модели будет сгенерирован не RelatedManager, а аналог<em>поля</em>, ссылающийся (и запрашивающий)<em>один</em>связанный объект.</p>
25
<p>Этот вид связи в Django ORM реализуется с помощью типа<a>OneToOneField</a>и характерен лишь тем, что у парной модели будет сгенерирован не RelatedManager, а аналог<em>поля</em>, ссылающийся (и запрашивающий)<em>один</em>связанный объект.</p>
26
<p>Примеров связей "один к одному" в учебном проекте нет, потому что в целом такой вид связи встречается редко. Но вы можете себе представить или попробовать реализовать ситуацию, при которой пользователю предоставляется возможность описать собственную автобиографию. С одной стороны автобиография не обязательна, поэтому OneToOneField будет описано в модели User и будет иметь параметр null=True. При этом в рамках заполняемой автобиографии часть информации - несколько полей - будет обязательной к заполнению. Описать в рамках одной модели требования вроде "эти поля обязательны только вместе" гораздо сложнее, чем завести новую модель-дополнение и привязать к первой "один к одному".</p>
26
<p>Примеров связей "один к одному" в учебном проекте нет, потому что в целом такой вид связи встречается редко. Но вы можете себе представить или попробовать реализовать ситуацию, при которой пользователю предоставляется возможность описать собственную автобиографию. С одной стороны автобиография не обязательна, поэтому OneToOneField будет описано в модели User и будет иметь параметр null=True. При этом в рамках заполняемой автобиографии часть информации - несколько полей - будет обязательной к заполнению. Описать в рамках одной модели требования вроде "эти поля обязательны только вместе" гораздо сложнее, чем завести новую модель-дополнение и привязать к первой "один к одному".</p>
27
<h2>Ссылки на себя</h2>
27
<h2>Ссылки на себя</h2>
28
<p>ForeignKey и его производные вроде OneToOneField позволяют модели сослаться на саму себя. Такие ссылки позволяют закодировать родственные связи между людьми или, скажем, описать модель для хранения в БД древовидных структур. И ORM сделает использование таких структур достаточно удобным!</p>
28
<p>ForeignKey и его производные вроде OneToOneField позволяют модели сослаться на саму себя. Такие ссылки позволяют закодировать родственные связи между людьми или, скажем, описать модель для хранения в БД древовидных структур. И ORM сделает использование таких структур достаточно удобным!</p>
29
<p>Однако как при описании поля - атрибута класса - сослаться на сам класс, ведь на момент выполнения кода, описывающего поля, класс ещё не существует? Тут на помощь приходит возможность указывать<em>имена</em>моделей в виде строки "self".</p>
29
<p>Однако как при описании поля - атрибута класса - сослаться на сам класс, ведь на момент выполнения кода, описывающего поля, класс ещё не существует? Тут на помощь приходит возможность указывать<em>имена</em>моделей в виде строки "self".</p>
30
<p>Кроме того, строкой можно сослаться и на модель, если указать её полное имя: "full.app_name.ModelName". Этот приём позволяет делать ссылки между моделями, находящимися в разных приложениях, не делая между модулями перекрёстные импорты. В больших проектах такие импорты могут превратиться в циклические, на которые Python будет жаловаться. Помните об этой возможности!</p>
30
<p>Кроме того, строкой можно сослаться и на модель, если указать её полное имя: "full.app_name.ModelName". Этот приём позволяет делать ссылки между моделями, находящимися в разных приложениях, не делая между модулями перекрёстные импорты. В больших проектах такие импорты могут превратиться в циклические, на которые Python будет жаловаться. Помните об этой возможности!</p>