django-parler-rest icon indicating copy to clipboard operation
django-parler-rest copied to clipboard

Serializer : Load only languages requested in headers

Open remychvn opened this issue 6 years ago • 3 comments

I really like this implementation. It seems much more qualitative than the one we implemented on our project.

However, It is possible to load only languages requested in accept_language headers ? And maybe the default language if the string is not translated into the desired language?

Example

{
    "id": 528,
    "country_code": "NL",
    "translations": {
        "default_language_code": {
            "name": "English",
            "welcome_message": "Welcome !"
        },
        "header_language_code": {
            "name": "French",
            "welcome_message": "Bienvenue"
        },
    }
}

Because it can quickly produce heavy schemas to return strings in all enabled languages. Thank you in advance

remychvn avatar Sep 12 '18 21:09 remychvn

It would be great feature and strong optimization. I have plans to fetch from DB whole and big articles, only for desired language. In django tastypie it works like a charm.

JurajKavka avatar Dec 30 '19 21:12 JurajKavka

Maybe this mixin will help you. Depending on given accept-language header, it returns a flat representation of the model without the translations sub-fields. If the language was not found, the fallback value is used:

`from django.conf import settings

class TranslatedSerializerMixin(object): """ Mixin for selecting only requested translation with django-parler-rest """

def to_representation(self, instance):
    inst_rep = super().to_representation(instance)
    request = self.context.get('request')
    lang_code = request.META.get('HTTP_ACCEPT_LANGUAGE', None)

    # only use the first two chars for language code
    if lang_code and '-' in lang_code:
        lang_code = lang_code.split('-')[0]

    result = {}
    for field_name, field in self.get_fields().items():
        # add normal field to resulting representation
        if field_name is not 'translations':
            field_value = inst_rep.pop(field_name)
            result.update({field_name: field_value})

        if field_name is 'translations':
            translations = inst_rep.pop(field_name)
            if lang_code not in translations:
                # use fallback setting in PARLER_LANGUAGES
                parler_default_settings = settings.PARLER_LANGUAGES['default']
                if 'fallback' in parler_default_settings:
                    lang_code = parler_default_settings.get('fallback')

                if 'fallbacks' in parler_default_settings:
                    lang_code = parler_default_settings.get('fallbacks')[0]

            for lang, translation_fields in translations.items():
                if lang == lang_code:
                    trans_rep = translation_fields.copy()  # make copy to use pop() from
                    for trans_field_name, trans_field in translation_fields.items():
                        field_value = trans_rep.pop(trans_field_name)
                        result.update({trans_field_name: field_value})

    return result

`

chrda81 avatar Jul 31 '20 00:07 chrda81

Thanks @chrda81, your solution is very impressive.

I made some changes on your code to take a different type of output.


class TranslatedSerializerMixin(object):
    """
    Get values without "translations" key.
    For selecting language; add "Accept-Language" into request header.
    There is no dependence to django-parler-rest library.
    
    Usage:

    class MyModel(models.Model):
        image = ...
        translations = TranslatedFields(
            name = models.CharField(...),
            description = models.TextField(...)
        )

    class MySerializer(TranslatedSerializerMixin, serializers.ModelSerializer):
        class Meta:
            model = MyModel
            fields = (
                'id',
                'image',
                'name',
                'description',
            )
            
    result:

    [
        {
            id: 1,
            image: '/images/001.png,
            name: '<value for the selected language>'
            description: '<value for the selected language>'
        },
        ...
    ]
    """

    def to_representation(self, instance):
        inst_rep = super().to_representation(instance)
        request = self.context.get('request')
        lang_code = request.META.get('HTTP_ACCEPT_LANGUAGE', None)

        # Only use the first two chars for language code
        if lang_code and '-' in lang_code:
            lang_code = lang_code.split('-')[0]

        result = {}
        translation_fields = self.get_translations_fields()
        translation_model = self.Meta.model._parler_meta.root_model
        translate = None
        
        # No need to get translate values while language is default
        if lang_code != DEFAULT_LANG:
            try:
                translate = translation_model.objects.get(
                    master=instance,
                    language_code=lang_code
                )
            except:
                ...

        for field_name, field in self.get_fields().items():
            field_value = inst_rep.pop(field_name)
            
            if translate and field_name in translation_fields:
                translate_value = getattr(translate, field_name)
                if translate_value is not None:
                    field_value = translate_value

            result[field_name] = field_value
        return result

    def get_translations_fields(self):
        """ Return list of translate fields name"""
        return self.Meta.model._parler_meta.get_all_fields()```

ishakoktn avatar Nov 20 '21 19:11 ishakoktn