Skip to content

Instantly share code, notes, and snippets.

@vdboor
Last active February 18, 2016 09:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vdboor/a4629d7f3d4158f3a229 to your computer and use it in GitHub Desktop.
Save vdboor/a4629d7f3d4158f3a229 to your computer and use it in GitHub Desktop.
USE https://github.com/edoburu/django-parler-rest/ NOW! THIS GIST IS DEPRECATED. It was a test to share code before making the package ----- Combining django-parler with django-rest-framework. Please tell me how that code is working for you, and if you have any improvements. The final version could be included into a 'contrib' folder of django-p…
from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers
from rest_framework.serializers import SortedDictWithMetadata
from .utils import create_translated_fields_serializer
class TranslatedFieldsField(serializers.WritableField):
"""
Exposing translated fields for a TranslatableModel in REST style.
"""
def __init__(self, *args, **kwargs):
self.serializer_class = kwargs.pop('serializer_class', None)
self.shared_model = kwargs.pop('shared_model', None)
super(TranslatedFieldsField, self).__init__(*args, **kwargs)
def initialize(self, parent, field_name):
super(TranslatedFieldsField, self).initialize(parent, field_name)
self._serializers = {}
# Expect 1-on-1 for now.
related_name = field_name
# This could all be done in __init__(), but by moving the code here,
# it's possible to auto-detect the parent model.
if self.shared_model is not None and self.serializer_class is not None:
return
# Fill in the blanks
if self.serializer_class is None:
# Auto detect parent model
if self.shared_model is None:
self.shared_model = self.parent.opts.model
# Create serializer based on shared model.
translated_model = self.shared_model._parler_meta[related_name]
self.serializer_class = create_translated_fields_serializer(self.shared_model, related_name=related_name, meta=dict(
fields = translated_model.get_translated_fields()
))
else:
self.shared_model = self.serializer_class.Meta.model
# Don't need to have a 'language_code', it will be split up already,
# so this should avoid redundant output.
if 'language_code' in self.serializer_class.base_fields:
raise ImproperlyConfigured("Serializer may not have a 'language_code' field")
def to_native(self, value):
"""
Serialize to REST format.
"""
if value is None:
return None
# Only need one serializer to create the native objects
serializer = self.serializer_class()
# Split into a dictionary per language
ret = SortedDictWithMetadata()
for translation in value.all():
ret[translation.language_code] = serializer.to_native(translation)
return ret
def from_native(self, data, files=None):
"""
Deserialize primitives -> objects.
"""
self._errors = {}
self._serializers = {}
if data is None:
return None
elif isinstance(data, dict):
# Very similar code to ModelSerializer.from_native():
translations = self.restore_fields(data, files)
if translations is not None:
translations = self.perform_validation(translations)
else:
raise serializers.ValidationError(self.error_messages['invalid'])
if not self._errors:
return translations
# No 'master' object known yet, can't store fields.
#return self.restore_object(translations)
def restore_fields(self, data, files):
translations = {}
for lang_code, model_fields in data.iteritems():
# Create a serializer per language, so errors can be stored per serializer instance.
self._serializers[lang_code] = serializer = self.serializer_class()
serializer._errors = {} # because it's .from_native() is skipped.
translations[lang_code] = serializer.restore_fields(model_fields, files)
return translations
def perform_validation(self, data):
# Runs `validate_<fieldname>()` and `validate()` methods on the serializer
for lang_code, model_fields in data.iteritems():
self._serializers[lang_code].perform_validation(model_fields)
return data
# def restore_object(self, data):
# master = self.parent.object
# for lang_code, model_fields in data.iteritems():
# translation = master._get_translated_model(lang_code, auto_create=True)
# self._serializers[lang_code].restore_object(model_fields, instance=translation)
def validate(self, data):
super(TranslatedFieldsField, self).validate(data) # checks 'required' state.
for lang_code, model_fields in data.iteritems():
self._serializers[lang_code].validate(model_fields)
from rest_framework import serializers
from .fields import TranslatedFieldsField
from myapp.modes import Region
class TranslatableModelSerializer(serializers.ModelSerializer):
"""
Serializer that makes sure that translations
from the :class:`TranslatedFieldsField` are properly saved.
"""
def save_object(self, obj, **kwargs):
"""
Extract the translations, store these into the django-parler model data.
"""
for meta in obj._parler_meta:
translations = obj._related_data.pop(meta.rel_name, {})
if translations:
for lang_code, model_fields in translations.iteritems():
translations = obj._get_translated_model(lang_code, auto_create=True, meta=meta)
for field, value in model_fields.iteritems():
setattr(translations, field, value)
return super(TranslatableModelSerializer, self).save_object(obj, **kwargs)
class RegionSerializer(TranslatableModelSerializer):
"""
A model with translated fields.
"""
translations = TranslatedFieldsField(shared_model=Region)
class Meta:
model = Region
fields = ('id', 'foo', 'bar', 'translations',)
from rest_framework import serializers
def create_translated_fields_serializer(shared_model, meta=None, related_name=None, **fields):
"""
Create a REST framework serializer class for a translated fields model.
:param shared_model: The shared model.
:type shared_model: :class:`parler.models.TranslatableModel`
"""
if not related_name:
translated_model = shared_model._parler_meta.root_model
else:
translated_model = shared_model._parler_meta[related_name].model
# Define inner Meta class
if not meta:
meta = {}
meta['model'] = translated_model
meta.setdefault('fields', ['language_code'] + translated_model.get_translated_fields())
# Define serialize class attributes
attrs = {}
attrs.update(fields)
attrs['Meta'] = type('Meta', (), meta)
# Dynamically create the serializer class
return type('{0}Serializer'.format(translated_model.__name__), (serializers.ModelSerializer,), attrs)
@vdboor
Copy link
Author

vdboor commented Sep 11, 2014

Please tell me how that code is working for you, and if you have any improvements. The final version could be included into a 'contrib' folder of django-parler package or be released as separate package.

@igolkotek
Copy link

Hi,
Unfortunately, when I take simple
class SubjectSerializer(TranslatableModelSerializer):
translations = TranslatedFieldsField(shared_model=Subject)

class Meta:
    model = Subject
    fields = ('subject_id', 'translations',)

on serializer.data I get error:

/usr/local/lib/python2.7/dist-packages/djangorestframework-2.4.2-py2.7.egg/rest_framework/serializers.pyc in data(self)
570 self._data = [self.to_native(item) for item in obj]
571 else:
--> 572 self._data = self.to_native(obj)
573
574 return self._data

/usr/local/lib/python2.7/dist-packages/djangorestframework-2.4.2-py2.7.egg/rest_framework/serializers.pyc in to_native(self, obj)
349 field.initialize(parent=self, field_name=field_name)
350 key = self.get_field_key(field_name)
--> 351 value = field.field_to_native(obj, field_name)
352 method = getattr(self, 'transform_%s' % field_name, None)
353 if callable(method):

/usr/local/lib/python2.7/dist-packages/djangorestframework-2.4.2-py2.7.egg/rest_framework/fields.pyc in field_to_native(self, obj, field_name)
334 if self.write_only:
335 return None
--> 336 return super(WritableField, self).field_to_native(obj, field_name)
337
338 def field_from_native(self, data, files, field_name, into):

/usr/local/lib/python2.7/dist-packages/djangorestframework-2.4.2-py2.7.egg/rest_framework/fields.pyc in field_to_native(self, obj, field_name)
205
206 for component in source.split('.'):
--> 207 value = get_component(value, component)
208 if value is None:
209 break

/usr/local/lib/python2.7/dist-packages/djangorestframework-2.4.2-py2.7.egg/rest_framework/fields.pyc in get_component(obj, attr_name)
56 val = obj.get(attr_name)
57 else:
---> 58 val = getattr(obj, attr_name)
59
60 if is_simple_callable(val):

AttributeError: 'TranslatableQuerySet' object has no attribute 'subject_id'

Regards
Iho

@patrick91
Copy link

Works for me :)

Thanks!

Copy link

ghost commented Oct 22, 2014

Too works for me. Thanks.

@yellowcap
Copy link

Thanks a lot for posting this! Your code worked fine for me, but for my application I wanted a simpler version, where only one language is returned and the serialization of the translated fields looks as if they were normal fields.

Inspired by your code, I was able to create a simle read-only version that allows returning the data in a more familiar json format. Here is a gist with the described approach

https://gist.github.com/yellowcap/81c6d5f3ea1426689c80

@vdboor
Copy link
Author

vdboor commented Nov 3, 2014

Thanks for the feedback! :)

I've just updated the Gist for django-parler 1.2, which replaced the private _translations_model and _translations_field with a new _parler_meta object that supports adding translated fields on multiple object levels.

@grudelsud
Copy link

hey, nice piece of code thank you. unfortunately it doesn't work with rest framework 3.0 as they got rid of to_ and from_ native.

(btw, 👋 @patrick91 😄 )

@vdboor
Copy link
Author

vdboor commented Feb 18, 2016

@grudelsud: you can use https://github.com/edoburu/django-parler-rest/ now, this code was used as basis to create the package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment