Repository: encode/django-rest-framework

Issue:
Make sure lazy translatable string get translated by the serializer fields
Right now if a lazy translatable string is rendered in a serializer fields everything dies with a `<django.utils.functional.__proxy__ object at 0x7fac6c074290> is not JSON serializable` error

Tom suggested this could be achieved by changing the code here: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/fields.py#L168

Of course we will need some tests to ensure nothing else is broken. I will try to provide some tests.



Discussion:

Resolving patch:
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -495,3 +495,16 @@ def apply_markdown(text):
     oauth2_provider_forms = None
     oauth2_provider_scope = None
     oauth2_constants = None
+
+# Handle lazy strings
+from django.utils.functional import Promise
+
+if six.PY3:
+    def is_non_str_iterable(obj):
+        if (isinstance(obj, str) or
+            (isinstance(obj, Promise) and obj._delegate_text)):
+            return False
+        return hasattr(obj, '__iter__')
+else:
+    def is_non_str_iterable(obj):
+        return hasattr(obj, '__iter__')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -26,7 +26,7 @@
 from rest_framework.compat import timezone, parse_date, parse_datetime, parse_time
 from rest_framework.compat import BytesIO
 from rest_framework.compat import six
-from rest_framework.compat import smart_text
+from rest_framework.compat import smart_text, force_text, is_non_str_iterable
 from rest_framework.settings import api_settings
 
 
@@ -45,7 +45,6 @@ def is_simple_callable(obj):
     len_defaults = len(defaults) if defaults else 0
     return len_args <= len_defaults
 
-
 def get_component(obj, attr_name):
     """
     Given an object, and an attribute name,
@@ -169,7 +168,8 @@ def to_native(self, value):
 
         if is_protected_type(value):
             return value
-        elif hasattr(value, '__iter__') and not isinstance(value, (dict, six.string_types)):
+        elif (is_non_str_iterable(value) and
+              not isinstance(value, (dict, six.string_types))):
             return [self.to_native(item) for item in value]
         elif isinstance(value, dict):
             # Make sure we preserve field ordering, if it exists
@@ -177,7 +177,7 @@ def to_native(self, value):
             for key, val in value.items():
                 ret[key] = self.to_native(val)
             return ret
-        return smart_text(value)
+        return force_text(value)
 
     def attributes(self):
         """


Test patch:
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,8 +1,9 @@
 from __future__ import unicode_literals
 from django.db import models
 from django.db.models.fields import BLANK_CHOICE_DASH
-from django.utils.datastructures import MultiValueDict
 from django.test import TestCase
+from django.utils.datastructures import MultiValueDict
+from django.utils.translation import ugettext_lazy as _
 from rest_framework import serializers
 from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
     BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
@@ -1323,6 +1324,34 @@ def test_errors_return_as_list(self):
         self.assertEqual(serializer.errors, expected)
 
 
+# test for issue 747
+
+
+class LazyStringModel(object):
+    def __init__(self, lazystring):
+        self.lazystring = lazystring
+
+
+class LazyStringSerializer(serializers.Serializer):
+    lazystring = serializers.Field()
+
+    def restore_object(self, attrs, instance=None):
+        if instance is not None:
+            instance.lazystring = attrs.get('lazystring', instance.lazystring)
+            return instance
+        return LazyStringModel(**attrs)
+
+
+class LazyStringsTestCase(TestCase):
+    def setUp(self):
+        self.model = LazyStringModel(lazystring=_('lazystring'))
+
+    def test_lazy_strings_are_translated(self):
+        serializer = LazyStringSerializer(self.model)
+        self.assertEqual(type(serializer.data['lazystring']),
+                         type('lazystring'))
+
+
 class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
 
     def setUp(self):