Кверисет пользовательской модели 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']