Django 5 для начинающих

Прогресс по курсу:  9/1004

3.4 Организация связей между таблицами
3 из 11 шагов пройдено
0 из 53 баллов  получено

 Организация связей между таблицами в модели данных

Большинство таблиц в базе данных имеют связи между собой. Django позволяет определить три наиболее употребительных типа отношений: «один-ко-многим»«многие-ко-многим» и «один-к-одному».

 

Организация связей между таблицами "один-к-одному"

При организации связи «один-к-одному» каждая запись из таблицы А может быть ассоциирована только с одной записью таблицы В.

Обычно связь «один-к-одному» легко моделируется в одной таблице. Записи такой таблицы содержат данные, которые находятся в связи «один-к-одному» с первичным ключом.

В редких случаях связь «один-к-одному» моделируется с использованием двух таблиц. Такой вариант иногда необходим, чтобы преодолеть ограничения СУБД, или с целью увеличения производительности (производится, например, вынесение ключевого поля в отдельную таблицу для ускорения поиска по другой таблице).

Или вы сами захотите разнести две сущности, имеющие связь «один-к-одному», по разным таблицам. Например, всю базовую информацию о пользователе (имя, возраст, электронный адрес и пр.) выделить в одну модель, а его учетные данные (логин, пароль, время последнего входа в систему, количество неудачных входов и т. п.) - в другую.

Рассмотрим, как можно связать две таблицы в БД через связанные модели на примере: «пользователь системы - учетные данные пользователя». Также с данной связью мы поработаем на примере блога, при создании профилей пользователей.

Для создания отношения «один-к-одному» применяется тип связи models.OneToOneField().

OneToOneField(<связываемая модель>, on_delete=<поведение при удалении записи>, [<остальные параметры>])

Создадим новое приложение в нашем проекте:

python manage.py startapp onetoone

И добавим модели в приложение onetoone.

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=20)


class Account(models.Model):
    login = models.CharField(max_length=20)
    password = models.CharField(max_length=20)
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)

Зарегистрируем приложение в INSTALLED_APPS и выполним миграции.

python manage.py makemigrations
python manage.py migrate

Здесь мы создали модель пользователя User с одним полем name и модель учетных данных пользователя Account с двумя полями: login и password.
Для создания отношения «один-к-одному» был применен конструктор типа models.OneToOneField().

Его первый параметр указывает, с какой моделью будет ассоциирована эта сущность (в нашем случае ассоциация с моделью User).

Второй его параметр on_delete=models.CASCADE говорит, что данные текущей модели Account будут удаляться в случае удаления связанного объекта главной модели User.

Для параметра on_delete могут использованы следующие значения:

  • models.CASCADE: автоматически удаляет строку из зависимой таблицы, если удаляется связанная строка из главной таблицы.
  • models.PROTECT: блокирует удаление строки из главной таблицы, если с ней связаны какие-либо строки из зависимой таблицы.
  • models.SET_NULL: устанавливает NULL при удалении связанной строки из главной таблицы.
  • models.SET_DEFAULT: устанавливает значение по умолчанию для внешнего ключа в зависимой таблице. В этом случае для этого столбца должно быть задано значение по умолчанию.
  • models.DO_NOTHING: при удалении связанной строки из главной таблицы не производится никаких действий в зависимой таблице.

Третий параметр primary_key=True указывает, что внешний ключ (через который идет связь с главной моделью) одновременно будет выступать и в роли первичного ключа.
И соответственно, создавать отдельное поле для первичного ключа.

В результате миграции в базе данных SQLite будут создаваться следующие таблицы:

CREATE TABLE "onetoone_user" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "name" varchar(20) NOT NULL
)
CREATE TABLE "onetoone_account" (
    "login" varchar(20) NOT NULL, 
    "password" varchar(20) NOT NULL, 
    "user_id" bigint NOT NULL PRIMARY KEY REFERENCES "onetoone_user" ("id") DEFERRABLE INITIALLY DEFERRED
)

Как можно видеть, в таблице onetoone_account нет собственного первичного ключа id - его роль выполняет ключ user_id, который одновременно служит для связи с таблицей onetoone_user.

С помощью свойства user в модели Account мы можем манипулировать связанным объектом модели User. Чтобы проверить связи запустим шелл:

python manage.py shell

И не забываем перед началом работы импортировать нужные модели:

from onetoone.models import *

Теперь начнем вводить команды:

# создадим пользователя Александр
alex = User.objects.create(name="Aлeкcaндp")

# создадим аккаунт пользователя Александр
acc = Account.objects.create(login="1234", password="6565", user=alex)

# изменяем имя пользователя
acc.user.name = "Саша"

# сохраняем изменения в БД
acc.user.save()

И теперь если мы посмотрим содержимое таблиц, мы видим что имя изменилось.

При этом через модель User мы также можем оказывать влияние на связанный объект Account.

Несмотря на то что явным образом в модели User определено только одно свойство - name, при связи "один-к-одному" неявно создается еще одно свойство, которое называется по имени зависимой модели и указывает на связанный объект этой модели.
То есть в нашем случае это свойство будет называться account:

# создадим пользователя Александр
alex = User.objects.create(name="Aлeкcaндp")

# создадим аккаунт пользователя
асс = Account(login="1234", password="6565")
alex.account = асс
alex.account.save()

# обновляем данные
alex.account.login = "qwerty"
alex.account.password = "123456"
alex.account.save()

Подобным образом можно выполнять фильтрацию по обоим моделям и их свойствам:

# получим пользователя

alex = User.objects.get(name="Саша")

         

# получим аккаунт пользователя

alex_acc = Account.objects.get(user=alex)

print(f"login: {alex_acc.login}, password: {alex_acc.password}")

# мы получим login: 1234, password: 6565



# получим аккаунт по имени пользователя

alexander_acc = Account.objects.get(user__name="Aлeкcaндp")

print(f"login: {alexander_acc.login}, password: {alexander_acc.password}")

# мы получим login: qwerty, password: 123456

 Через два знака подчеркивания мы можем указать имя поля второй модели - например, user__name или user__id.

# получим пользователя по логину

user = User.objects.get(account__login="qwerty")

print(user.name)

# мы получим Aлeкcaндp

В заключение, отношения «один к одному» — полезный инструмент для моделирования сложных отношений в Django. Используя OneToOneField, вы можете легко определить уникальную связь между двумя моделями. 


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий
Комментарий закреплён

Понравилась задача, тест или урок? Поставьте лайк, поддержите курс. Ваша поддержка очень важна для нас.

В этом и следующих уроках при миграции указано:

Зарегистрируем приложение в INSTALLED_APPS и выполним миграции.

python manage.py makemigrations
python manage.py migrate

 Хотя ранее упоминалось:

Поэтому в качестве лучшей практики возьмите за правило всегда указывать имя приложения при выполнении команды makemigrations!

Тут данная практика упущена из-за простоты проекта или все таки лучше ее повсеместно использовать с самого начала, т.е. даже в простом учебном проекте указывать имя приложения?

@Нарбеков_Марсель, тут простейший проект, с одним приложением.

Также с данной связью мы поработаем на примере блога, при создании профилей пользователей.

@Георгий_Тимофеев, спасибо, исправил.

user = User.objects.get(account__login="qwerty")
print(user.name)


 

Здесь account__login указывает на поле login, которое находится в модели Account, связанной с моделью User. Это означает, что мы хотим получить пользователя (User), у которого значение поля login в связанной модели Account равно "qwerty".

Если есть две модели, например, User и Account, и между ними существует связь, то использование двойного подчеркивания (__) позволяет обращаться к полям в связанной модели.

В вашем случае, предположим, что у модели User есть поле name, и модель User связана с моделью Account через поле account. Таким образом, account__login обращается к полю login в связанной модели Account, а user.name обращается к полю name в модели User.

Создадим новое приложение в нашем проекте:

python manage.py startapp onetoone

И добавим модели в приложение profile.

Здесь видимо надо поменять profile на onetoone, так как приложения "profile"  в нашем проекте нет

@Евгений_Епишкин, спасибо, исправил.

Не очень понятно вот что
Почему поле вроде как называется 'user' (user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
А потом становится user_id - потому что оно связывается по первичному ключу другой таблицы (id) или потому что само поле user является первичным ключом и к нему автоматически добавляется id?

@Aleksandr_Yolshin, user_id это имя столбца в таблице БД, а не имя поля, суффикс _id добавляет Django.

@Дмитрий_Селезнев, хорошо. А вот суффикс _id добавляется по причине чего? Того, что этот столбец связан со столбом id другой таблицы или потому что он сам является первичным ключом?

@Aleksandr_Yolshin, добавляется потому что это поле имеет связь с внешней таблицей, и при этом само является первичным ключом.

https://docs.djangoproject.com/en/5.0/ref/models/fields/#database-representation

Изменен Дмитрий Селезнев

В последних примерах кода используем двойное подчеркивание для упоминания объекта другой модели (account__login), а объясняется эта часть только на третьем шаге данного урока.

@Иван_Белоусов, Добавил уточнение.

@Илья_Перминов, небольшая опечатка user_name с одним подчеркиванием

Изменен Иван Белоусов

Тут наверное имелось ввиду свойства user, без s на конце?

 

@Максим_Михеев, да, спасибо, исправил.

Вот здесь разве модель не должна быть везде с большой буквы как имя класса?

 

@Максим_Михеев, должно, исправил, спасибо за помощь.