Кверисет пользовательской модели Django возвращает поля динамически

У меня есть несколько моделей, которые можно перевести.

Мы реализовали это следующим образом: для каждой переводимой модели существует модель ModelTranslation.
Например, для модели Car существует модель CarTranslation с FK на Car.

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

class Car(models.Model):
  name = models.CharField()
  description = models.TextField()

Тогда CarTranslation выглядит следующим образом:

class CarTranslation(models.Model):
  car = models.ForeignKey(to=Car)
  language = models.CharField(choices=LANGUAGE_CHOICES)
  name = models.CharField()
  description = models.TextField()

В представлениях, возвращающих переводимые данные, мы проверяем, какой язык задал пользователь: если это английский (по умолчанию), мы возвращаем Car как есть; если это не английский, мы возвращаем переведенные данные в CarTranslation для языка пользователя (если он существует, иначе по умолчанию - английский).

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

Но мне это не нравится.

Я думаю, что гораздо лучшим решением будет использование пользовательских менеджеров моделей/наборов моделей.
Я пробую следующее:

from django.db.models.functions import Coalesce
from django.db.models import F, Subquery

class CarQuerySet(models.QuerySet):
  def with_translation(self, language):
    from cars.models import CarTranslation

    if language == "en":
      return self
    translation_query = CarTranslation.objects.filter(language=language, car=OuterRef("pk"))
    return self.annotate(
      name=Coalesce(Subquery(translation_query.values("name")[:1]), F("name")),
      description=Coalesce(Subquery(translation_query.values("description")[:1]), F("description")),
    )

А затем используйте его в представлениях следующим образом:

class CarView(ListAPIView):
  def get_queryset(self):
    user = self.request.user
    language = user.language
    return Car.objects.with_translation(language)

Но он выдает эту ошибку: ValueError: The annotation 'name' conflicts with a field on the model.

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

from django.db.models.functions import Coalesce
from django.db.models import F, Subquery

class CarQuerySet(models.QuerySet):
  def with_translation(self, language):
    from cars.models import CarTranslation

    if language == "en":
      return self
    translation_query = CarTranslation.objects.filter(language=language, car==OuterRef("pk"))
    queryset = self.annotate(
      translated_name=Coalesce(Subquery(translation_query.values("name")[:1]), F("name")),
      translated_description=Coalesce(Subquery(translation_query.values("description")[:1]), F("description")),
    )

    return queryset.values(
      *[field.name for field in self.model._meta.fields if field.name not in ["name", "description"]]
      name=F("translated_name"),
      description=F("translated_description"),
    )

но он выдает ту же самую ошибку.

Есть идеи, как вернуть значение по умолчанию или переведенное значение из пользовательского набора запросов?

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

from django.db.models import F, Subquery
from django.db.models.functions import Coalesce


class CarQuerySet(models.QuerySet):
    def with_translation(self, language):
        from cars.models import CarTranslation

        if language == 'en':
            return self.annotate(custom_name=F('name'), custom_description=F('description'))
        translation_query = CarTranslation.objects.filter(
            language=language, car_id=OuterRef('pk')
        )
        return self.annotate(
            custom_name=Coalesce(
                Subquery(translation_query.values('name')[:1]), F('name')
            ),
            custom_description=Coalesce(
                Subquery(translation_query.values('description')[:1]),
                F('description'),
            ),
        )

Затем в сериализаторе мы можем использовать custom_name и custom_description:

class CarSerializer(serializers.ModelSerializer):
    name = serializers.CharField(source='custom_name')
    description = serializers.CharField(source='custom_description')

    class Meta:
        model = Car
        fields = ['name', 'description']
Вернуться на верх