django-modeltranslation icon indicating copy to clipboard operation
django-modeltranslation copied to clipboard

I wrote a Mixin for a DRF-API-View to dynamically support django-modeltranslation

Open iekadou opened this issue 8 years ago • 7 comments

I wrote a Mixin for the django-restframework API View to automatically deliver translations. Here is an example for German and English activated:

class AchievementSerializer(TranslatableSerializerMixin, serializers.ModelSerializer):
    class Meta:
        model = Achievement
        fields = ('slug', 'title',)

When you now make a GET request to it, you will get this as a return:

{
	"count": 6,
	"current_page": 1,
	"results": [{
		"slug": "campaign",
		"title": "Kampagne",
		"title_de": "Kampagne",
		"title_en": "Campaign"
	}, {
		"slug": "profile",
		"title": "Profil",
		"title_de": "Profil",
		"title_en": "Profile"
	}, {
		"slug": "promote-project",
		"title": "Projekt bekannt machen",
		"title_de": "Projekt bekannt machen",
		"title_en": "Promote Project"
	}, {
		"slug": "company",
		"title": "Unternehmen",
		"title_de": "Unternehmen",
		"title_en": "Company"
	}, {
		"slug": "organization",
		"title": "Organisation",
		"title_de": "Organisation",
		"title_en": "Organization"
	}, {
		"slug": "project",
		"title": "Projekt",
		"title_de": "Projekt",
		"title_en": "Project"
	}],
	"links": {
		"previous": null,
		"next": null
	}
}

You can also POST to it like normal.

iekadou avatar Apr 06 '17 14:04 iekadou

I'm really interested in this topic. Could you share the TranslatableSerializerMixin code?

Thanks in advance!

pablolmedorado avatar May 08 '17 09:05 pablolmedorado

@iekadou How we can see the code of your mixin? Why didn't you create a pull request?

emorozov avatar May 28 '17 09:05 emorozov

bump, can you share the mixin @iekadou ?

JakeAustwick avatar Apr 20 '18 11:04 JakeAustwick

Here's a simple implementation of the above (mixin renamed). By default, it will provide all translations of the serializer fields. However, if the language get parameter is provided to the request as a comma delimited string, then only those specified translations will be returned.

from django.conf import settings
from modeltranslation.manager import get_translatable_fields_for_model
from rest_framework import serializers


class TranslatedModelSerializerMixin(serializers.ModelSerializer):
    def get_field_names(self, declared_fields, info):
        fields = super().get_field_names(declared_fields, info)
        trans_fields = get_translatable_fields_for_model(self.Meta.model)
        all_fields = []

        requested_langs = []
        if 'request' in self.context:
            lang_param = self.context['request'].query_params.get('lang', None)
            requested_langs = lang_param.split(',') if lang_param else []

        for f in fields:
            if f not in trans_fields:
                all_fields.append(f)
            else:
                for l in settings.LANGUAGES:
                    if not requested_langs or l[0] in requested_langs:
                        all_fields.append("{}_{}".format(f, l[0]))

        return all_fields

ilikerobots avatar Jan 16 '19 11:01 ilikerobots

I assume this is not compatible with newer versions of DRF and Django? I get an error when running this:

  File "/usr/local/lib/python3.8/site-packages/rest_framework/serializers.py", line 1038, in get_fields
    field_names = self.get_field_names(declared_fields, info)
  File "/code/ddp/utils/translatedmodelserializer.py", line 18, in get_field_names
    if f not in trans_fields:
TypeError: argument of type 'NoneType' is not iterable

winsmith avatar Feb 20 '20 11:02 winsmith

Here's a simple implementation of the above (mixin renamed). By default, it will provide all translations of the serializer fields. However, if the language get parameter is provided to the request as a comma delimited string, then only those specified translations will be returned.

from django.conf import settings
from modeltranslation.manager import get_translatable_fields_for_model
from rest_framework import serializers


class TranslatedModelSerializerMixin(serializers.ModelSerializer):
    def get_field_names(self, declared_fields, info):
        fields = super().get_field_names(declared_fields, info)
        trans_fields = get_translatable_fields_for_model(self.Meta.model)
        all_fields = []

        requested_langs = []
        if 'request' in self.context:
            lang_param = self.context['request'].query_params.get('lang', None)
            requested_langs = lang_param.split(',') if lang_param else []

        for f in fields:
            if f not in trans_fields:
                all_fields.append(f)
            else:
                for l in settings.LANGUAGES:
                    if not requested_langs or l[0] in requested_langs:
                        all_fields.append("{}_{}".format(f, l[0]))

        return all_fields

Here is an up to date version of this serializer :

class TranslationModelSerializer(serializers.ModelSerializer):
    def get_field_names(self, declared_fields, info):
        fields = super().get_field_names(declared_fields, info)
        lang = (
            self.context.get("request").LANGUAGE_CODE
            if self.context.get("request")
            else None
        )
        if lang:
            trans_fields = get_translatable_fields_for_model(self.Meta.model)
            result = []
            for f in fields:
                if f not in trans_fields:
                    result.append(f)
                else:
                    result.append("{}_{}".format(f, lang))
        else:
            result = fields
        return result

Depending on how your serializer is instantiated, you might need to add the request to your serializer.

I think it might make sense to commit this serializer to the repository, but it should be a little bit more adaptable.

EDIT : However serialization translation seems to work out of the box, so you should not need this class.

gbip avatar Mar 17 '23 12:03 gbip

I used this as base an extended it, with support to read_only_fields. Isn't really "dynamic", but may be useful in some contexts.

from modeltranslation.utils import (
    build_localized_fieldname,
    get_translation_fields,
)
from modeltranslation.manager import (
    get_translatable_fields_for_model,
)


class TranslatableModelSerializerMixin(object):
    # TODO: Support exclude and __ALL__ field option

    def get_field_names(self, declared_fields, info):
        
        fields = super().get_field_names( declared_fields, info)
        model_translatable_fields = get_translatable_fields_for_model(self.Meta.model)
        
        for field in model_translatable_fields:
            if field in fields:
                fields += tuple(get_translation_fields(field))
        return fields

    def get_extra_kwargs(self):
        """
        Return a dictionary mapping field names to a dictionary of
        additional keyword arguments.
        This class extends the original method to add tranlated fields
        to read_only_fields if the original field is read_only
        """
        extra_kwargs = super().get_extra_kwargs()

        read_only_fields = getattr(self.Meta, 'read_only_fields', None)

        if read_only_fields is not None:
            model_translatable_fields = get_translatable_fields_for_model(self.Meta.model)
            for field_name in read_only_fields:
                if field_name in model_translatable_fields:
                    for translation_field_name in get_translation_fields(field_name):
                        kwargs = extra_kwargs.get(translation_field_name, {})
                        kwargs['read_only'] = True
                        extra_kwargs[translation_field_name] = kwargs

        return extra_kwargs

brunosmartin avatar Nov 15 '23 00:11 brunosmartin