Skip to content

Commit af53e34

Browse files
committed
Merge pull request #2279 from tomchristie/fix-serializer-repr-unicode-bug
Use unicode internally everywhere for 'repr'.
2 parents 1f6fd92 + dc66cce commit af53e34

File tree

8 files changed

+66
-21
lines changed

8 files changed

+66
-21
lines changed

rest_framework/compat.py

+17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@
1616
import django
1717

1818

19+
def unicode_repr(instance):
20+
# Get the repr of an instance, but ensure it is a unicode string
21+
# on both python 3 (already the case) and 2 (not the case).
22+
if six.PY2:
23+
repr(instance).decode('utf-8')
24+
return repr(instance)
25+
26+
27+
def unicode_to_repr(value):
28+
# Coerce a unicode string to the correct repr return type, depending on
29+
# the Python version. We wrap all our `__repr__` implementations with
30+
# this and then use unicode throughout internally.
31+
if six.PY2:
32+
return value.encode('utf-8')
33+
return value
34+
35+
1936
# OrderedDict only available in Python 2.7.
2037
# This will always be the case in Django 1.7 and above, as these versions
2138
# no longer support Python 2.6.

rest_framework/fields.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import unicode_literals
12
from django.conf import settings
23
from django.core.exceptions import ObjectDoesNotExist
34
from django.core.exceptions import ValidationError as DjangoValidationError
@@ -10,7 +11,8 @@
1011
from rest_framework import ISO_8601
1112
from rest_framework.compat import (
1213
EmailValidator, MinValueValidator, MaxValueValidator,
13-
MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict
14+
MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict,
15+
unicode_repr, unicode_to_repr
1416
)
1517
from rest_framework.exceptions import ValidationError
1618
from rest_framework.settings import api_settings
@@ -113,7 +115,9 @@ def __call__(self):
113115
return self.default
114116

115117
def __repr__(self):
116-
return '%s(%s)' % (self.__class__.__name__, repr(self.default))
118+
return unicode_to_repr(
119+
'%s(%s)' % (self.__class__.__name__, unicode_repr(self.default))
120+
)
117121

118122

119123
class CurrentUserDefault:
@@ -124,7 +128,7 @@ def __call__(self):
124128
return self.user
125129

126130
def __repr__(self):
127-
return '%s()' % self.__class__.__name__
131+
return unicode_to_repr('%s()' % self.__class__.__name__)
128132

129133

130134
class SkipField(Exception):
@@ -463,7 +467,7 @@ def __repr__(self):
463467
This allows us to create descriptive representations for serializer
464468
instances that show all the declared fields on the serializer.
465469
"""
466-
return representation.field_repr(self)
470+
return unicode_to_repr(representation.field_repr(self))
467471

468472

469473
# Boolean types...

rest_framework/serializers.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
2. The process of marshalling between python primitives and request and
1111
response content is handled by parsers and renderers.
1212
"""
13-
import warnings
14-
13+
from __future__ import unicode_literals
1514
from django.db import models
1615
from django.db.models.fields import FieldDoesNotExist
1716
from django.utils.translation import ugettext_lazy as _
18-
17+
from rest_framework.compat import unicode_to_repr
1918
from rest_framework.utils import model_meta
2019
from rest_framework.utils.field_mapping import (
2120
get_url_kwargs, get_field_kwargs,
@@ -29,6 +28,7 @@
2928
UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator,
3029
UniqueTogetherValidator
3130
)
31+
import warnings
3232

3333

3434
# Note: We do the following so that users of the framework can use this style:
@@ -396,7 +396,7 @@ def validate(self, attrs):
396396
return attrs
397397

398398
def __repr__(self):
399-
return representation.serializer_repr(self, indent=1)
399+
return unicode_to_repr(representation.serializer_repr(self, indent=1))
400400

401401
# The following are used for accessing `BoundField` instances on the
402402
# serializer, for the purposes of presenting a form-like API onto the
@@ -564,7 +564,7 @@ def save(self, **kwargs):
564564
return self.instance
565565

566566
def __repr__(self):
567-
return representation.list_repr(self, indent=1)
567+
return unicode_to_repr(representation.list_repr(self, indent=1))
568568

569569
# Include a backlink to the serializer class on return objects.
570570
# Allows renderers such as HTMLFormRenderer to get the full field info.

rest_framework/utils/representation.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
Helper functions for creating user-friendly representations
33
of serializer classes and serializer fields.
44
"""
5+
from __future__ import unicode_literals
56
from django.db import models
67
from django.utils.encoding import force_text
78
from django.utils.functional import Promise
9+
from rest_framework.compat import unicode_repr
810
import re
911

1012

@@ -24,7 +26,7 @@ def smart_repr(value):
2426
if isinstance(value, Promise) and value._delegate_text:
2527
value = force_text(value)
2628

27-
value = repr(value)
29+
value = unicode_repr(value)
2830

2931
# Representations like u'help text'
3032
# should simply be presented as 'help text'

rest_framework/utils/serializer_helpers.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from __future__ import unicode_literals
12
import collections
2-
from rest_framework.compat import OrderedDict
3+
from rest_framework.compat import OrderedDict, unicode_to_repr
34

45

56
class ReturnDict(OrderedDict):
@@ -47,9 +48,9 @@ def _proxy_class(self):
4748
return self._field.__class__
4849

4950
def __repr__(self):
50-
return '<%s value=%s errors=%s>' % (
51+
return unicode_to_repr('<%s value=%s errors=%s>' % (
5152
self.__class__.__name__, self.value, self.errors
52-
)
53+
))
5354

5455

5556
class NestedBoundField(BoundField):

rest_framework/validators.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
object creation, and makes it possible to switch between using the implicit
77
`ModelSerializer` class and an equivalent explicit `Serializer` class.
88
"""
9+
from __future__ import unicode_literals
910
from django.utils.translation import ugettext_lazy as _
11+
from rest_framework.compat import unicode_to_repr
1012
from rest_framework.exceptions import ValidationError
1113
from rest_framework.utils.representation import smart_repr
1214

@@ -59,10 +61,10 @@ def __call__(self, value):
5961
raise ValidationError(self.message)
6062

6163
def __repr__(self):
62-
return '<%s(queryset=%s)>' % (
64+
return unicode_to_repr('<%s(queryset=%s)>' % (
6365
self.__class__.__name__,
6466
smart_repr(self.queryset)
65-
)
67+
))
6668

6769

6870
class UniqueTogetherValidator:
@@ -141,11 +143,11 @@ def __call__(self, attrs):
141143
raise ValidationError(self.message.format(field_names=field_names))
142144

143145
def __repr__(self):
144-
return '<%s(queryset=%s, fields=%s)>' % (
146+
return unicode_to_repr('<%s(queryset=%s, fields=%s)>' % (
145147
self.__class__.__name__,
146148
smart_repr(self.queryset),
147149
smart_repr(self.fields)
148-
)
150+
))
149151

150152

151153
class BaseUniqueForValidator:
@@ -205,12 +207,12 @@ def __call__(self, attrs):
205207
raise ValidationError({self.field: message})
206208

207209
def __repr__(self):
208-
return '<%s(queryset=%s, field=%s, date_field=%s)>' % (
210+
return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % (
209211
self.__class__.__name__,
210212
smart_repr(self.queryset),
211213
smart_repr(self.field),
212214
smart_repr(self.date_field)
213-
)
215+
))
214216

215217

216218
class UniqueForDateValidator(BaseUniqueForValidator):

tests/test_fields.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_allow_blank(self):
6262
"""
6363
field = serializers.CharField(allow_blank=True)
6464
output = field.run_validation('')
65-
assert output is ''
65+
assert output == ''
6666

6767
def test_default(self):
6868
"""
@@ -817,7 +817,7 @@ def test_allow_blank(self):
817817
]
818818
)
819819
output = field.run_validation('')
820-
assert output is ''
820+
assert output == ''
821821

822822

823823
class TestChoiceFieldWithType(FieldValues):

tests/test_serializer.py

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
# coding: utf-8
12
from __future__ import unicode_literals
23
from rest_framework import serializers
4+
from rest_framework.compat import unicode_repr
35
import pytest
46

57

@@ -197,3 +199,20 @@ def __init__(self):
197199
"The serializer field might be named incorrectly and not match any attribute or key on the `ExampleObject` instance.\n"
198200
"Original exception text was:"
199201
)
202+
203+
204+
class TestUnicodeRepr:
205+
def test_unicode_repr(self):
206+
class ExampleSerializer(serializers.Serializer):
207+
example = serializers.CharField()
208+
209+
class ExampleObject:
210+
def __init__(self):
211+
self.example = '한국'
212+
213+
def __repr__(self):
214+
return unicode_repr(self.example)
215+
216+
instance = ExampleObject()
217+
serializer = ExampleSerializer(instance)
218+
repr(serializer) # Should not error.

0 commit comments

Comments
 (0)