Skip to content

Instantly share code, notes, and snippets.

@grudelsud
Forked from vdboor/fields.py
Last active August 29, 2015 14:14
Show Gist options
  • Save grudelsud/0858ee51f084f4862d55 to your computer and use it in GitHub Desktop.
Save grudelsud/0858ee51f084f4862d55 to your computer and use it in GitHub Desktop.
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment