Аннотируйте новое поле с тем же именем, что и существующее

Мы столкнулись с ситуацией, о которой я хотел бы узнать мнение более опытных пользователей. Мы находимся на Django 4, Python 3.9.

Текущий сценарий:

У нас уже есть наша система, работающая в производстве в течение разумного времени, и нам нужно изменить на Backend некоторые возвращаемые данные для нашего Frontend приложения (Web, мобильного...) на основе выбора пользователей.

Важно следующее: Изменения на стороне Frontend пока что невозможны.

Нам нужно переопределить некоторые атрибуты из одной модели, основываясь на существующих или не соответствующих данных в другой таблице.

Таблицы (Django Models):

AgentSearch (эти данные заменяются каждый день в процессе ETL)

  • id
  • идентификатор_номер (уникальный)
  • член_первое_имя
  • имя_члена
  • mail_члена
  • some_other_attrbs

Агент (Необязательные данные, которые может определить агент)

  • id
  • agent_search_identifier_number (FK OneToOne)
  • first_name
  • last_name
  • email

Для нашего требования сейчас, все наборы запросов будут сделаны, начиная с AgentSearch. Идея заключается в получении данных агента вместо данных AgentSearch каждый раз, когда есть агент, связанный с ним.

Сегодня мы получаем следующее:

AgentSearch.objects.all()

Я знаю, что было бы проще аннотировать новые колонки следующим образом:

        # using a different attribute name
        NEW_member_first_name=Case(
            When(
                agent__id__isnull=True,
                then=F("member_first_name"),
            ),
            default=F("agent__first_name"),
        ),

Это потребует от нас внесения изменений в FrontEnd.

Если мы попробуем:

        # using the same attribute name
        member_first_name=Case(
            When(
                agent__id__isnull=True,
                then=F("member_first_name"),
            ),
            default=F("agent__first_name"),
        ),

Django бросит:

ValueError Exception saying: The annotation 'member_first_name' conflicts with a field on the model.

Мы уже пробовали удалять "переопределяемые" атрибуты из базового запроса. Пример:

        # Tell Django ORM to not get the column from DB
        AgentSearch.objects.all().defer('member_first_name')
        .annotate(
             member_first_name=Case(
                 When(
                     agent__id__isnull=True,
                     then=F("member_first_name"),
                 ),
                 default=F("agent__first_name"),
             ),

По-прежнему:

ValueError Exception saying: The annotation 'member_first_name' conflicts with a field on the model.

Также попробовал принудительно указать значения только для не "переопределяемых" атрибутов из базового запроса. Пример:

        # Tell Django ORM to not get the column from DB
        AgentSearch.objects.all().values('some_other_attrbs')
        .annotate(
             member_first_name=Case(
                 When(
                     agent__id__isnull=True,
                     then=F("member_first_name"),
                 ),
                 default=F("agent__first_name"),
             ),

По-прежнему:

ValueError Exception saying: The annotation 'member_first_name' conflicts with a field on the model.

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

По вашему мнению, какой способ был бы элегантным для этого? Мы нашли решение, но оно кажется очень уродливым.

Как это может работать на данный момент:

class BaseAgentSearch(AbstractBaseModel):
    ...
    class Meta:
        abstract = True

class AgentSearch(BaseAgentSearch):
    class Meta:
        db_table = "app_agentsearch"
        # setting to false, since this model will act only as a "reader"
        managed = False
    ###########################################################
    # Original DB columns are mapped to a temp_ attribute name
    ###########################################################
    temp_member_first_name = models.CharField(
        max_length=64, db_column="member_first_name"
    )
    temp_member_last_name = models.CharField(
        max_length=64, db_column="member_last_name"
    )
    temp_member_email = models.EmailField(
        max_length=64, blank=True, db_column="member_email"
    )


class AgentSearchWrite(BaseAgentSearch):
    class Meta:
        db_table = "app_agentsearch"

    member_first_name = models.CharField(max_length=64)
    member_last_name = models.CharField(max_length=64)
    member_email = models.EmailField(max_length=64, blank=True)

Затем запрос как:

    AgentSearch.objects.all()
    .annotate(
        member_first_name=Case(
            When(
                agent__id__isnull=True,
                then=F("temp_member_first_name"),
            ),
            default=F("agent__first_name"),
        ),
        member_last_name=Case(
            When(agent__id__isnull=True, then=F("temp_member_last_name")),
            default=F("agent__last_name"),
        ),
        member_email=Case(
            When(agent__id__isnull=True, then=F("temp_member_email")),
            default=F("agent__email"),
        ),

Это работает, как и ожидалось. Но выглядит очень странно и уродливо. И еще, для добавления/обновления данных, мы должны теперь использовать AgentSearchWrite модель, которая имеет правильные колонки на db и model.

Вернуться на верх