djangodjango-rest-frameworkdjango-orm

How to mapping ForeignKey model value in Django Rest Framework


models.py

class Item(models.Model):
    name = models.CharField(max_length=100)
    # other_no_need_translate = models.CharField(max_length=100)

class Itemi18n(models.Model):
    origin = models.ForeignKey(Item, related_name='i18n')
    language = models.CharField(max_length=200, default="")
    name = models.CharField(max_length=100)

view.py

class ItemViewSet(viewsets.GenericViewSet):
    
    def get_queryset(self):
        return Item.objects.annotate(name=F('i18n__name')) # something like this

serializers.py

class I18nField(Field):

    def __init__(self, field_attr, **kwargs):
        self.field_attr = field_attr
        super().__init__(source='*', read_only=True, **kwargs)

    def to_representation(self, value) -> str:
        request = self.context['request']
        if request.query_params.get('lang') == 'es':
            value = ...
        else:
            value = ...
        return value

class ItemSerializer(serializers.ModelSerializer):
    name = I18nField(...)

I want to write a serializer, when i18n is needed, return name from Itemi18n, otherwise return name from Item. I thought of 2 solutions,one is use annotate to override field, another is use Custom Field. I am not sure about best practices and performance issues.


Solution

  • Performance wise, the annotation would be worse, since that would perform aggregation to all objects. The best approach would be to narrow down the number of queries as much as possible of course.

    Since that feature is a mutation of the original value, I belive the best place to implement this is inside the serializer to_representation method.

    from django.utils.translation import get_language
    
    
    class ItemModelSerializer(ModelSerializer):
        class Meta:
            model = Item
            exclude = ["id"]
    
        def to_representation(self, instance):
            representation = super().to_representation(instance)
            local_lang = get_language()
            if local_lang != "en":
                try:
                    translation = instance.i18n.get(language=local_lang)
                    representation["name"] = translation.name
    
                except Itemi18n.DoesNotExist:
                    pass
    
            return representation
    

    P.S. It is also possible to increase performance by prefetching related objects.