From 0e8760eaafffea3d5197e3841ab83b21b5f857cd Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Thu, 12 Dec 2013 00:42:08 -0600 Subject: [PATCH 1/9] Add compound field types --- rest_framework/fields.py | 75 +++++++++++++++++++++++++++++++++++ rest_framework/serializers.py | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f1de447c7..73d792749 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1012,3 +1012,78 @@ class SerializerMethodField(Field): def field_to_native(self, obj, field_name): value = getattr(self.parent, self.method_name)(obj) return self.to_native(value) + + +##### Compound Typed Fields ##### + + +class ListField(WritableField): + """ + A field whose values are lists of items described by the given item field. The item field can + be another field type (e.g., CharField) or a serializer. + """ + + def __init__(self, item_field, *args, **kwargs): + super(ListField, self).__init__(*args, **kwargs) + self.item_field = item_field + + def to_native(self, obj): + if obj: + return [ + self.item_field.to_native(item) + for item in obj + ] + + def from_native(self, data): + if data: + return [ + self.item_field.from_native(item_data) + for item_data in data + ] + + +class ListOrObjectField(WritableField): + """ + A field whose values are either objects or lists of items described by the given item field. + The item field can be another field type (e.g., CharField) or a serializer. + """ + + def __init__(self, item_field, *args, **kwargs): + super(ListOrObjectField, self).__init__(*args, **kwargs) + self.item_field = item_field + self.list_field = ListField(item_field) + + def to_native(self, obj): + if isinstance(obj, list): + return self.list_field.to_native(obj) + return self.item_field.to_native(obj) + + def from_native(self, data): + if isinstance(obj, list): + return self.list_field.from_native(obj) + return self.item_field.from_native(obj) + + +class DictField(WritableField): + """ + A field whose values are dicts of values described by the given value field. The value field + can be another field type (e.g., CharField) or a serializer. + """ + + def __init__(self, value_field, *args, **kwargs): + super(DictField, self).__init__(*args, **kwargs) + self.value_field = value_field + + def to_native(self, obj): + if obj: + return { + key:self.value_field.to_native(value) + for key, value in obj.items() + } + + def from_native(self, data): + if data: + return { + key:self.value_field.from_native(value) + for key, value in data.items() + } diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8351b3df6..27db7b962 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -331,7 +331,7 @@ class BaseSerializer(WritableField): return ret - def from_native(self, data, files): + def from_native(self, data, files=None): """ Deserialize primitives -> objects. """ From b0027e52fa3efead56452fff87dc2dc2a8e2d936 Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Thu, 12 Dec 2013 22:36:19 -0600 Subject: [PATCH 2/9] Updates for pull request review comments * Remove ListOrObjectField * Allow item_field & value_field to be optional * Undo from_native API change; flex invocation in compound fields * In DictField, coerce key to a string * If list or dict is None or empty, just return input preserving empty containers * Remove dict comprehension for Python 2.6 compat --- rest_framework/compound_fields.py | 71 +++++++++++++++++++++++++++++ rest_framework/fields.py | 75 ------------------------------- rest_framework/serializers.py | 2 +- 3 files changed, 72 insertions(+), 76 deletions(-) create mode 100644 rest_framework/compound_fields.py diff --git a/rest_framework/compound_fields.py b/rest_framework/compound_fields.py new file mode 100644 index 000000000..cbdb95988 --- /dev/null +++ b/rest_framework/compound_fields.py @@ -0,0 +1,71 @@ +""" +Compound fields for processing values that are lists and dicts of values described by embedded +fields. +""" +from .fields import WritableField +from .serializers import BaseSerializer + + +def field_or_serializer_from_native(field_or_serializer, data): + if isinstance(field_or_serializer, BaseSerializer): + return field_or_serializer.from_native(data, None) + return field_or_serializer.from_native(data) + + +class ListField(WritableField): + """ + A field whose values are lists of items described by the given item field. The item field can + be another field type (e.g., CharField) or a serializer. + """ + + def __init__(self, item_field=None, *args, **kwargs): + super(ListField, self).__init__(*args, **kwargs) + self.item_field = item_field + + def to_native(self, obj): + if obj: + return [ + self.item_field.to_native(item) if self.item_field else item + for item in obj + ] + return obj + + def from_native(self, data): + if data: + return [ + field_or_serializer_from_native(self.item_field, item_data) + if self.item_field else item_data + for item_data in data + ] + return data + + +class DictField(WritableField): + """ + A field whose values are dicts of values described by the given value field. The value field + can be another field type (e.g., CharField) or a serializer. + """ + + def __init__(self, value_field=None, *args, **kwargs): + super(DictField, self).__init__(*args, **kwargs) + self.value_field = value_field + + def to_native(self, obj): + if obj: + return dict( + (key, self.value_field.to_native(value) if self.value_field else value) + for key, value in obj.items() + ) + return obj + + def from_native(self, data): + if data: + return dict( + ( + unicode(key), + field_or_serializer_from_native(self.value_field, value) + if self.value_field else value + ) + for key, value in data.items() + ) + return data diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 73d792749..f1de447c7 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1012,78 +1012,3 @@ class SerializerMethodField(Field): def field_to_native(self, obj, field_name): value = getattr(self.parent, self.method_name)(obj) return self.to_native(value) - - -##### Compound Typed Fields ##### - - -class ListField(WritableField): - """ - A field whose values are lists of items described by the given item field. The item field can - be another field type (e.g., CharField) or a serializer. - """ - - def __init__(self, item_field, *args, **kwargs): - super(ListField, self).__init__(*args, **kwargs) - self.item_field = item_field - - def to_native(self, obj): - if obj: - return [ - self.item_field.to_native(item) - for item in obj - ] - - def from_native(self, data): - if data: - return [ - self.item_field.from_native(item_data) - for item_data in data - ] - - -class ListOrObjectField(WritableField): - """ - A field whose values are either objects or lists of items described by the given item field. - The item field can be another field type (e.g., CharField) or a serializer. - """ - - def __init__(self, item_field, *args, **kwargs): - super(ListOrObjectField, self).__init__(*args, **kwargs) - self.item_field = item_field - self.list_field = ListField(item_field) - - def to_native(self, obj): - if isinstance(obj, list): - return self.list_field.to_native(obj) - return self.item_field.to_native(obj) - - def from_native(self, data): - if isinstance(obj, list): - return self.list_field.from_native(obj) - return self.item_field.from_native(obj) - - -class DictField(WritableField): - """ - A field whose values are dicts of values described by the given value field. The value field - can be another field type (e.g., CharField) or a serializer. - """ - - def __init__(self, value_field, *args, **kwargs): - super(DictField, self).__init__(*args, **kwargs) - self.value_field = value_field - - def to_native(self, obj): - if obj: - return { - key:self.value_field.to_native(value) - for key, value in obj.items() - } - - def from_native(self, data): - if data: - return { - key:self.value_field.from_native(value) - for key, value in data.items() - } diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 27db7b962..8351b3df6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -331,7 +331,7 @@ class BaseSerializer(WritableField): return ret - def from_native(self, data, files=None): + def from_native(self, data, files): """ Deserialize primitives -> objects. """ From f21f009fd1cd63ed9b4c28861f7fb61c9f856501 Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Fri, 13 Dec 2013 23:00:54 -0600 Subject: [PATCH 3/9] Put default files=None back on BaseSerializer.from_native * In ListField and DictField methods, place field check before list or dict construction --- rest_framework/compound_fields.py | 27 ++++++++------------------- rest_framework/serializers.py | 2 +- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/rest_framework/compound_fields.py b/rest_framework/compound_fields.py index cbdb95988..95c67ddc8 100644 --- a/rest_framework/compound_fields.py +++ b/rest_framework/compound_fields.py @@ -6,12 +6,6 @@ from .fields import WritableField from .serializers import BaseSerializer -def field_or_serializer_from_native(field_or_serializer, data): - if isinstance(field_or_serializer, BaseSerializer): - return field_or_serializer.from_native(data, None) - return field_or_serializer.from_native(data) - - class ListField(WritableField): """ A field whose values are lists of items described by the given item field. The item field can @@ -23,18 +17,17 @@ class ListField(WritableField): self.item_field = item_field def to_native(self, obj): - if obj: + if self.item_field and obj: return [ - self.item_field.to_native(item) if self.item_field else item + self.item_field.to_native(item) for item in obj ] return obj def from_native(self, data): - if data: + if self.item_field and data: return [ - field_or_serializer_from_native(self.item_field, item_data) - if self.item_field else item_data + self.item_field.from_native(item_data) for item_data in data ] return data @@ -51,21 +44,17 @@ class DictField(WritableField): self.value_field = value_field def to_native(self, obj): - if obj: + if self.value_field and obj: return dict( - (key, self.value_field.to_native(value) if self.value_field else value) + (unicode(key), self.value_field.to_native(value)) for key, value in obj.items() ) return obj def from_native(self, data): - if data: + if self.value_field and data: return dict( - ( - unicode(key), - field_or_serializer_from_native(self.value_field, value) - if self.value_field else value - ) + (unicode(key), self.value_field.from_native(value)) for key, value in data.items() ) return data diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8351b3df6..27db7b962 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -331,7 +331,7 @@ class BaseSerializer(WritableField): return ret - def from_native(self, data, files): + def from_native(self, data, files=None): """ Deserialize primitives -> objects. """ From e802d75efe342e0618c61c3671ecb3f8feeadf5d Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Fri, 27 Dec 2013 10:55:38 -0600 Subject: [PATCH 4/9] Add unicode_options parameter to DictType to allow for customizing key processing to a unicode string --- rest_framework/compound_fields.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_framework/compound_fields.py b/rest_framework/compound_fields.py index 95c67ddc8..b839d71f3 100644 --- a/rest_framework/compound_fields.py +++ b/rest_framework/compound_fields.py @@ -39,14 +39,15 @@ class DictField(WritableField): can be another field type (e.g., CharField) or a serializer. """ - def __init__(self, value_field=None, *args, **kwargs): + def __init__(self, value_field=None, unicode_options=None, *args, **kwargs): super(DictField, self).__init__(*args, **kwargs) self.value_field = value_field + self.unicode_options = unicode_options or {} def to_native(self, obj): if self.value_field and obj: return dict( - (unicode(key), self.value_field.to_native(value)) + (unicode(key, **self.unicode_options), self.value_field.to_native(value)) for key, value in obj.items() ) return obj @@ -54,7 +55,7 @@ class DictField(WritableField): def from_native(self, data): if self.value_field and data: return dict( - (unicode(key), self.value_field.from_native(value)) + (unicode(key, **self.unicode_options), self.value_field.from_native(value)) for key, value in data.items() ) return data From 3494d4d912614b05dc1d45b0c7ab0c25e3442924 Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Fri, 27 Dec 2013 10:56:06 -0600 Subject: [PATCH 5/9] Document compound types: DictField, ListField --- docs/api-guide/fields.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index e05c03061..be41a2c79 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -297,6 +297,35 @@ Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files. --- +# Compound Fields + +These fields represent compound datatypes, which build on other fields with some additional aspect such collecting multiple elements. + +## ListField + +A list representation, whose elements are described by a given item field. This means that all elements must meet the definition of +that field. The item field can be another field type (e.g., CharField) or a serializer. If `item_field` is not given, then the +list-items are passed through as-is, and can be anything. Note that in this case, any non-native list elements wouldn't be properly +prepared for data rendering. + +**Signature:** `ListField(item_field=None)` + +## DictField + +A dictionary representation, whose values are described by a given value field. This means that all values must meet the definition of +that field. The value field can be another field type (e.g., CharField) or a serializer. If `value_field` is not given, then the `dict` +values are passed through-as-is, and can be anything. Note that in this case, any non-native `dict` values wouldn't be properly +prepared for data rendering. + +Dictionary keys are presumed to be character strings or convertible to such, and so during processing are casted to `unicode`. If +necessary, options for unicode conversion (such as the encoding, or error processing) can be provided to a `DictField`. For more info, +see [py_unicode]. + +**Signature:** `DictField(value_field=None, unicode_options=None)` + +If given, unicode_options must be a dict providing options per the [unicode](http://docs.python.org/2/library/functions.html#unicode) +function. + # Custom fields If you want to create a custom field, you'll probably want to override either one or both of the `.to_native()` and `.from_native()` methods. These two methods are used to convert between the initial datatype, and a primitive, serializable datatype. Primitive datatypes may be any of a number, string, date/time/datetime or None. They may also be any list or dictionary like object that only contains other primitive objects. @@ -345,3 +374,4 @@ As an example, let's create a field that can be used represent the class name of [ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 [strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior [iso8601]: http://www.w3.org/TR/NOTE-datetime +[py_unicode]: http://docs.python.org/2/howto/unicode.html From e237d5b65c4714fdff81cfb1c5f2d355d4d658d6 Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Fri, 27 Dec 2013 11:28:30 -0600 Subject: [PATCH 6/9] Remove obsolete import --- rest_framework/compound_fields.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_framework/compound_fields.py b/rest_framework/compound_fields.py index b839d71f3..fce6eb84f 100644 --- a/rest_framework/compound_fields.py +++ b/rest_framework/compound_fields.py @@ -3,7 +3,6 @@ Compound fields for processing values that are lists and dicts of values describ fields. """ from .fields import WritableField -from .serializers import BaseSerializer class ListField(WritableField): From c3586b6be4570c6a78ab51550a498f951348bb8d Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Sat, 28 Dec 2013 21:19:22 -0600 Subject: [PATCH 7/9] Add validation for ListField and DictField --- rest_framework/compound_fields.py | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/rest_framework/compound_fields.py b/rest_framework/compound_fields.py index fce6eb84f..1766ccae9 100644 --- a/rest_framework/compound_fields.py +++ b/rest_framework/compound_fields.py @@ -2,7 +2,11 @@ Compound fields for processing values that are lists and dicts of values described by embedded fields. """ +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ + from .fields import WritableField +from rest_framework.compat import six class ListField(WritableField): @@ -11,6 +15,10 @@ class ListField(WritableField): be another field type (e.g., CharField) or a serializer. """ + default_error_messages = { + 'invalid_type': _('%(value)s is not a list.'), + } + def __init__(self, item_field=None, *args, **kwargs): super(ListField, self).__init__(*args, **kwargs) self.item_field = item_field @@ -31,6 +39,24 @@ class ListField(WritableField): ] return data + def validate(self, value): + super(ListField, self).validate(value) + + if not isinstance(value, list): + raise ValidationError(self.error_messages['invalid_type'] % {'value': value}) + + if self.item_field: + errors = {} + for index, item in enumerate(value): + try: + self.item_field.validate(item) + self.item_field.run_validators(item) + except ValidationError as e: + errors[index] = [e] + + if errors: + raise ValidationError(errors) + class DictField(WritableField): """ @@ -38,6 +64,10 @@ class DictField(WritableField): can be another field type (e.g., CharField) or a serializer. """ + default_error_messages = { + 'invalid_type': _('%(value)s is not a dict.'), + } + def __init__(self, value_field=None, unicode_options=None, *args, **kwargs): super(DictField, self).__init__(*args, **kwargs) self.value_field = value_field @@ -58,3 +88,21 @@ class DictField(WritableField): for key, value in data.items() ) return data + + def validate(self, value): + super(DictField, self).validate(value) + + if not isinstance(value, dict): + raise ValidationError(self.error_messages['invalid_type'] % {'value': value}) + + if self.value_field: + errors = {} + for k, v in six.iteritems(value): + try: + self.value_field.validate(v) + self.value_field.run_validators(v) + except ValidationError as e: + errors[k] = [e] + + if errors: + raise ValidationError(errors) From fd4c87d67a7cda8f84bb19c5c03a9d4efbd0496b Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Sat, 28 Dec 2013 21:21:03 -0600 Subject: [PATCH 8/9] Add tests of ListField and DictField --- rest_framework/tests/test_compound_fields.py | 203 +++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 rest_framework/tests/test_compound_fields.py diff --git a/rest_framework/tests/test_compound_fields.py b/rest_framework/tests/test_compound_fields.py new file mode 100644 index 000000000..606abc779 --- /dev/null +++ b/rest_framework/tests/test_compound_fields.py @@ -0,0 +1,203 @@ +""" +General serializer field tests. +""" + + +from datetime import date +import unittest + +from django.core.exceptions import ValidationError + +from rest_framework import ISO_8601 +from rest_framework.compound_fields import DictField +from rest_framework.compound_fields import ListField +from rest_framework.fields import CharField +from rest_framework.fields import DateField + + +class ListFieldTests(unittest.TestCase): + """ + Tests for the ListField behavior + """ + + def test_from_native_no_item_field(self): + """ + When a ListField has no item-field, from_native should return the data it was given + un-processed. + """ + field = ListField() + data = range(5) + obj = field.from_native(data) + self.assertEqual(data, obj) + + def test_to_native_no_item_field(self): + """ + When a ListField has no item-field, to_native should return the data it was given + un-processed. + """ + field = ListField() + obj = range(5) + data = field.to_native(obj) + self.assertEqual(obj, data) + + def test_from_native_with_item_field(self): + """ + When a ListField has an item-field, from_native should return a list of elements resulting + from the application of the item-field's from_native method to each element of the input + data list. + """ + field = ListField(DateField()) + data = ["2000-01-01", "2000-01-02"] + obj = field.from_native(data) + self.assertEqual([date(2000, 1, 1), date(2000, 1, 2)], obj) + + def test_to_native_with_item_field(self): + """ + When a ListField has an item-field, to_native should return a list of elements resulting + from the application of the item-field's to_native method to each element of the input + object list. + """ + field = ListField(DateField(format=ISO_8601)) + obj = [date(2000, 1, 1), date(2000, 1, 2)] + data = field.to_native(obj) + self.assertEqual(["2000-01-01", "2000-01-02"], data) + + def test_missing_required_list(self): + """ + When a ListField requires a value, then validate will raise a ValidationError on a missing + (None) value. + """ + field = ListField() + with self.assertRaises(ValidationError): + field.validate(None) + + def test_validate_non_list(self): + """ + When a ListField is given a non-list value, then validate will raise a ValidationError. + """ + field = ListField() + with self.assertRaises(ValidationError): + field.validate('notAList') + + def test_validate_empty_list(self): + """ + When a ListField requires a value, then validate will raise a ValidationError on an empty + value. + """ + field = ListField() + with self.assertRaises(ValidationError): + field.validate([]) + + def test_validate_elements_valid(self): + """ + When a ListField is given a list whose elements are valid for the item-field, then validate + will not raise a ValidationError. + """ + field = ListField(CharField(max_length=5)) + try: + field.validate(["a", "b", "c"]) + except ValidationError: + self.fail("ValidationError was raised") + + def test_validate_elements_invalid(self): + """ + When a ListField is given a list containing elements that are invalid for the item-field, + then validate will raise a ValidationError. + """ + field = ListField(CharField(max_length=5)) + with self.assertRaises(ValidationError): + field.validate(["012345", "012345"]) + + +class DictFieldTests(unittest.TestCase): + """ + Tests for the DictField behavior + """ + + def test_from_native_no_value_field(self): + """ + When a DictField has no value-field, from_native should return the data it was given + un-processed. + """ + field = DictField() + data = {"a": 1, "b": 2} + obj = field.from_native(data) + self.assertEqual(data, obj) + + def test_to_native_no_value_field(self): + """ + When a DictField has no value-field, to_native should return the data it was given + un-processed. + """ + field = DictField() + obj = {"a": 1, "b": 2} + data = field.to_native(obj) + self.assertEqual(obj, data) + + def test_from_native_with_value_field(self): + """ + When a DictField has an value-field, from_native should return a dict of elements resulting + from the application of the value-field's from_native method to each value of the input + data dict. + """ + field = DictField(DateField()) + data = {"a": "2000-01-01", "b": "2000-01-02"} + obj = field.from_native(data) + self.assertEqual({"a": date(2000, 1, 1), "b": date(2000, 1, 2)}, obj) + + def test_to_native_with_value_field(self): + """ + When a DictField has an value-field, to_native should return a dict of elements resulting + from the application of the value-field's to_native method to each value of the input + object dict. + """ + field = DictField(DateField(format=ISO_8601)) + obj = {"a": date(2000, 1, 1), "b": date(2000, 1, 2)} + data = field.to_native(obj) + self.assertEqual({"a": "2000-01-01", "b": "2000-01-02"}, data) + + def test_missing_required_dict(self): + """ + When a DictField requires a value, then validate will raise a ValidationError on a missing + (None) value. + """ + field = DictField() + with self.assertRaises(ValidationError): + field.validate(None) + + def test_validate_non_dict(self): + """ + When a DictField is given a non-dict value, then validate will raise a ValidationError. + """ + field = DictField() + with self.assertRaises(ValidationError): + field.validate('notADict') + + def test_validate_empty_dict(self): + """ + When a DictField requires a value, then validate will raise a ValidationError on an empty + value. + """ + field = DictField() + with self.assertRaises(ValidationError): + field.validate({}) + + def test_validate_elements_valid(self): + """ + When a DictField is given a dict whose values are valid for the value-field, then validate + will not raise a ValidationError. + """ + field = DictField(CharField(max_length=5)) + try: + field.validate({"a": "a", "b": "b", "c": "c"}) + except ValidationError: + self.fail("ValidationError was raised") + + def test_validate_elements_invalid(self): + """ + When a DictField is given a dict containing values that are invalid for the value-field, + then validate will raise a ValidationError. + """ + field = DictField(CharField(max_length=5)) + with self.assertRaises(ValidationError): + field.validate({"a": "012345", "b": "012345"}) From 877ef5c9edf416545312b37b3a92e9ddee88b2b2 Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Sun, 29 Dec 2013 12:34:14 -0600 Subject: [PATCH 9/9] Tweaks for compat with Python 2.6 and Python 3.X --- rest_framework/compound_fields.py | 4 ++-- rest_framework/tests/test_compound_fields.py | 24 +++++++------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/rest_framework/compound_fields.py b/rest_framework/compound_fields.py index 1766ccae9..9084464f4 100644 --- a/rest_framework/compound_fields.py +++ b/rest_framework/compound_fields.py @@ -76,7 +76,7 @@ class DictField(WritableField): def to_native(self, obj): if self.value_field and obj: return dict( - (unicode(key, **self.unicode_options), self.value_field.to_native(value)) + (six.text_type(key, **self.unicode_options), self.value_field.to_native(value)) for key, value in obj.items() ) return obj @@ -84,7 +84,7 @@ class DictField(WritableField): def from_native(self, data): if self.value_field and data: return dict( - (unicode(key, **self.unicode_options), self.value_field.from_native(value)) + (six.text_type(key, **self.unicode_options), self.value_field.from_native(value)) for key, value in data.items() ) return data diff --git a/rest_framework/tests/test_compound_fields.py b/rest_framework/tests/test_compound_fields.py index 606abc779..c069c12a1 100644 --- a/rest_framework/tests/test_compound_fields.py +++ b/rest_framework/tests/test_compound_fields.py @@ -68,16 +68,14 @@ class ListFieldTests(unittest.TestCase): (None) value. """ field = ListField() - with self.assertRaises(ValidationError): - field.validate(None) + self.assertRaises(ValidationError, field.validate, None) def test_validate_non_list(self): """ When a ListField is given a non-list value, then validate will raise a ValidationError. """ field = ListField() - with self.assertRaises(ValidationError): - field.validate('notAList') + self.assertRaises(ValidationError, field.validate, 'notAList') def test_validate_empty_list(self): """ @@ -85,8 +83,7 @@ class ListFieldTests(unittest.TestCase): value. """ field = ListField() - with self.assertRaises(ValidationError): - field.validate([]) + self.assertRaises(ValidationError, field.validate, []) def test_validate_elements_valid(self): """ @@ -105,8 +102,7 @@ class ListFieldTests(unittest.TestCase): then validate will raise a ValidationError. """ field = ListField(CharField(max_length=5)) - with self.assertRaises(ValidationError): - field.validate(["012345", "012345"]) + self.assertRaises(ValidationError, field.validate, ["012345", "012345"]) class DictFieldTests(unittest.TestCase): @@ -162,16 +158,14 @@ class DictFieldTests(unittest.TestCase): (None) value. """ field = DictField() - with self.assertRaises(ValidationError): - field.validate(None) + self.assertRaises(ValidationError, field.validate, None) def test_validate_non_dict(self): """ When a DictField is given a non-dict value, then validate will raise a ValidationError. """ field = DictField() - with self.assertRaises(ValidationError): - field.validate('notADict') + self.assertRaises(ValidationError, field.validate, 'notADict') def test_validate_empty_dict(self): """ @@ -179,8 +173,7 @@ class DictFieldTests(unittest.TestCase): value. """ field = DictField() - with self.assertRaises(ValidationError): - field.validate({}) + self.assertRaises(ValidationError, field.validate, {}) def test_validate_elements_valid(self): """ @@ -199,5 +192,4 @@ class DictFieldTests(unittest.TestCase): then validate will raise a ValidationError. """ field = DictField(CharField(max_length=5)) - with self.assertRaises(ValidationError): - field.validate({"a": "012345", "b": "012345"}) + self.assertRaises(ValidationError, field.validate, {"a": "012345", "b": "012345"})