-
-
Save grudelsud/0858ee51f084f4862d55 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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',) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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