Поле модели Django, которое на самом деле является ссылкой на поле в связанной модели - PullRequest
0 голосов
/ 12 ноября 2018

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

Например, предположим, что я управляю отелем для собак.В моем отеле есть номера, и каждая комната назначается клиенту и содержит собаку.

class Customer(Model):
    name = models.CharField(max_length=256, null=False)

class Dog(Model):
    name = models.CharField(max_length=256, null=False)
    customer = model.ForeignKey(Customer, null=True, on_delete=models.CASCADE) # The dog's owner

class Room(Model):
    customer = model.ForeignKey(Owner, null=True, on_delete=models.SET_NULL)
    dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)

Соответствующие таблицы в базе данных выглядят примерно так

CUSTOMER:

| ID | NAME       |
___________________
| 01 | John Smith |
| 02 | Jane Doe   |


DOG:

| ID | NAME  | CUSTOMER_ID |
____________________________
| 01 | Rover |     01      |
| 02 | Fido  |     01      |
| 03 | Spot  |     02      |


ROOM:

| ID | DOG_ID | CUSTOMER_ID |
_____________________________
| 01 |   01   |      01     |
| 02 |   03   |      02     |

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

Для начала я могу преобразовать customer в@property в классе Room с пользовательским геттером:

class Room(Model):
    dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)

    @property
    def customer(self):
        return self.dog.customer

Так что теперь везде в коде, где мы делаем что-то вроде c = room.customer, он продолжает работать.Проблема в том, что база кода полна запросов к полям Django, таких как room__customer, которые перестают работать, когда я превращаю customer в @property из Room.Я мог бы изменить их все на room__dog__customer, который работает нормально, но тогда это изменение не было бы "полностью прозрачным".

Я провел небольшое исследование и попытался реализовать собственный менеджер и аннотировать запросдля комнат:

class RoomManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(customer=F('dog__customer'))

Когда я это сделаю, я могу использовать room__customer в запросах, но не room__customer__name, я полагаю, потому что F() возвращает значение первичного ключа, а не экземпляр модели(https://docs.djangoproject.com/en/2.1/ref/models/expressions/#using-f-with-annotations).

Итак, мой вопрос: есть ли способ сделать эту работу, о которой я просто не знаю? Способ сделать так, чтобы Комната действовала так, как будто имеет прямые внешние ключиКлиент, не сохранив customer_id в таблице номеров?

1 Ответ

0 голосов
/ 12 ноября 2018

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

Тем не менее, я думаю, что ваше решение об измененииroom__customer до room__dog__customer - лучший курс действий.Это изменение делает очевидным для других разработчиков Django, что происходит (вы проходите отношения), и это довольно простое изменение.Да, вам может потребоваться прикоснуться к нескольким файлам, но такова жизнь, когда вы копаетесь в бэкэнд-схеме.

Однако, с этим изменением вам следует учитывать потенциальные последствия для производительности.Возможно, вам потребуется ввести select_related вызовов в ваши запросы, чтобы обеспечить правильное объединение таблиц.В противном случае выполнение поиска room -> dog -> customer может стать дорогостоящим (с дополнительным циклом обращения к базе данных за поиск ; это действительно складывается в цикл).

Удачи!

...