Связь one-to-one (один к одному)

Курс по Django: https://stepik.org/a/183363

Смотреть материал на YouTube | RuTube

Архив проекта: 30_sitewomen.zip

Последний тип связи, что мы рассмотрим, это связь One To One или один к одному. Ее довольно часто используют для расширения существующих моделей. Например, фреймворк Django изначально предоставляет нам таблицу user для хранения информации о пользователях. И ее достаточно для большинства задач. Однако если возникнет необходимость добавить в нее новые поля, то один из вариантов это сделать – создать еще одну таблицу (модель) и присоединить ее к таблице user связью один к одному.

Но пока мы не будем трогать модель User. Речь о ней еще впереди. А для демонстрации этого типа связи мы расширим модель Women, добавив еще одну модель Husband (муж). Очевидно, в нашей стране или Европе одна женщина может иметь только одного мужа, а мужчина – одну жену. Поэтому связь One To One здесь будет в самый раз.

Вначале определим модель Husband следующим образом:

class Husband(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField(null=True)
 
    def __str__(self):
        return self.name

И добавим в модель Women связь один к одному:

class Women(models.Model):
    ...
    husband = models.OneToOneField('Husband', on_delete=models.SET_NULL, null=True, blank=True, related_name='wuman')
    ...

После этого нам нужно создать файл миграций и применить их:

python manage.py makemigrations

python manage.py migrate

Таблица husband создана, давайте ее наполним каким-либо содержимым. Для этого перейдем в консоль фреймворка Django:

python manage.py shell_plus

и выполним следующие знакомые нам команды:

h1 = Husband.objects.create(name="Брэд Питт", age=59)
h2 = Husband.objects.create(name="Том Акерли", age=31)
h3 = Husband.objects.create(name="Дэниэл Модер")
h4 = Husband.objects.create(name="Кук Марони")

Теперь мы можем этих мужчин распределить по женщинам. Возьмем первую запись по Анджелине Джоли:

w1 = Women.objects.get(pk=1)

У этого объекта имеется атрибут husband:

w1.husband

Сейчас он принимает значение NULL. Давайте присвоим ему объект h1:

w1.husband = h1

Теперь Анджелина Джоли снова замужем за Брэдом Питтом. Но в таблице БД этих изменений еще нет. Для этого нужно выполнить метод save():

w1.save()

Видим, что изменения применились и в поле husband появилось значение 1 – это идентификатор записи из таблицы husband для Брэда Питта. Причем, мы можем получать данные и через объект w1:

w1.husband

и через объект h1:

h1.wuman

Атрибут wuman был сформирован благодаря параметру related_name класса OneToOneField. Если бы мы его не указали, то следовало бы обращаться по имени модели women (малыми буквами).

По идее связь можно было бы установить и с другой стороны (мужа). Предположим, что для второй записи (Марго Робби):

w2 = Women.objects.get(pk=2)

мы бы хотели указать мужа h2 (Том Акерли). Сделать это можно и так:

h2.wuman = w2
w2.save()

Обратите внимание, мы здесь сохраняем именно объект w2, так как в нем хранится внешний ключ husband_id для связи с таблицей husband. То есть, мы должны сохранять ту запись, в которой произошли изменения. Запись h2 напрямую связь не хранит, а потому и сохранять нечего.

Давайте теперь попробуем Джулии Робертс назначить того же самого мужа h2 (Тома Акерли):

w3 = Women.objects.get(pk=3)
w3.husband = h2

Пока никаких ошибок нет. Но при попытке сохранить эту запись:

w3.save()

получаем ошибку:

IntegrityError: UNIQUE constraint failed: women_women.husband_id

Она, как раз, связана с тем, что запись h2 уже ассоциирована с записью w2, поэтому назначить ее h2 еще раз другой записи нельзя. В этом смысл связи One To One (один к одному).

Мужа h2 можно было бы назначить женщине w3, предварительно удалив связь между h2 и w2. Это можно сделать следующим образом:

w2.husband = None
w2.save()

Теперь комбинация команд:

w3.husband = h2
w3.save()

сработают и у Джулии Робертс появился новый муж. Соответственно подробную информацию о муже можно посмотреть, обращаясь к объекту husband. Например:

w1 = Women.objects.get(pk=1)
w1.husband.name
w1.husband.age

Мало того, мы можем прямо через объект husband менять содержимое соответствующей записи:

w1.husband.age = 30
w1.husband.save()

Теперь Брэд Питт имеет возраст 30 лет. Вот так достаточно просто создается связь один к одному и происходит работа с ней.

Курс по Django: https://stepik.org/a/183363

Видео по теме