diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 31cfa34474..03880804a5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -106,11 +106,29 @@ def _get_declared_fields(bases, attrs): # If this class is subclassing another Serializer, add that Serializer's # fields. Note that we loop over the bases in *reverse*. This is necessary # in order to maintain the correct order of fields. + # Note: The 'seen' dict here ensures that the fields from the 'first' base + # take precedence over fields from later bases. + # (otherwise SortedDict will squash the first-base field and + # use the field from a later base instead.) + seen = set() + base_fields_map = {} + base_fields_order = [] for base in bases[::-1]: if hasattr(base, 'base_fields'): - fields = list(base.base_fields.items()) + fields - - return SortedDict(fields) + for name, field in base.base_fields.items(): + base_fields_map[name] = field + base_fields_order = list(base.base_fields.keys()) + base_fields_order + + seen = set() + base_fields = [] + for name in base_fields_order: + if name not in seen: + base_fields.append((name, base_fields_map[name])) + seen.add(name) + + # if there are fields in both base_fields and fields, SortedDict + # uses the *last* one defined. So fields needs to go last. + return SortedDict(base_fields + fields) class SerializerMetaclass(type): diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index c24976603e..85afd8176d 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1643,3 +1643,72 @@ def test_serializer_supports_slug_many_relationships(self): serializer = SimpleSlugSourceModelSerializer(data={'text': 'foo', 'targets': [1, 2]}) self.assertTrue(serializer.is_valid()) self.assertEqual(serializer.data, {'text': 'foo', 'targets': [1, 2]}) + + +### Regression test for #1053 + +class OverriddenFieldsBase1(serializers.Serializer): + a_field = serializers.CharField() + + +class OverriddenFieldsBase2(serializers.Serializer): + a_field = serializers.IntegerField() + + +class OverriddenFieldsWithSingleBase(OverriddenFieldsBase1): + a_field = serializers.FloatField() + + +class OverriddenFieldsMultipleBases1(OverriddenFieldsBase1, OverriddenFieldsBase2): + # first base takes precedence; a_field should be a CharField. + pass + + +class OverriddenFieldsMultipleBases2(OverriddenFieldsBase2, OverriddenFieldsBase1): + # first base takes precedence; a_field should be a IntegerField. + pass + + +class OverriddenFieldsMultipleBasesOverridden(OverriddenFieldsBase1, OverriddenFieldsBase2): + a_field = serializers.FloatField() + + +class SerializerSupportsOverriddenFields(TestCase): + def test_base_fields_unchanged(self): + self.assertIsInstance( + OverriddenFieldsBase1.base_fields['a_field'], + serializers.CharField, + ) + s = OverriddenFieldsBase1() + self.assertIsInstance(s.fields['a_field'], serializers.CharField) + + def test_overridden_fields_single_base(self): + self.assertIsInstance( + OverriddenFieldsWithSingleBase.base_fields['a_field'], + serializers.FloatField, + ) + s = OverriddenFieldsWithSingleBase() + self.assertIsInstance(s.fields['a_field'], serializers.FloatField) + + def test_overridden_fields_multiple_bases(self): + self.assertIsInstance( + OverriddenFieldsMultipleBases1.base_fields['a_field'], + serializers.CharField, + ) + s = OverriddenFieldsMultipleBases1() + self.assertIsInstance(s.fields['a_field'], serializers.CharField) + + self.assertIsInstance( + OverriddenFieldsMultipleBases2.base_fields['a_field'], + serializers.IntegerField, + ) + s = OverriddenFieldsMultipleBases2() + self.assertIsInstance(s.fields['a_field'], serializers.IntegerField) + + def test_overridden_fields_multiple_bases_overridden(self): + self.assertIsInstance( + OverriddenFieldsMultipleBasesOverridden.base_fields['a_field'], + serializers.FloatField, + ) + s = OverriddenFieldsMultipleBasesOverridden() + self.assertIsInstance(s.fields['a_field'], serializers.FloatField)