Организация связей между таблицами в модели данных
Большинство таблиц в базе данных имеют связи между собой. 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, вы можете легко определить уникальную связь между двумя моделями.