mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	Allow missing fields option for inherited serializers. Closes #2388.
This commit is contained in:
		
							parent
							
								
									fdeef89ba7
								
							
						
					
					
						commit
						da6ef3d0b0
					
				|  | @ -20,7 +20,7 @@ def unicode_repr(instance): | ||||||
|     # Get the repr of an instance, but ensure it is a unicode string |     # Get the repr of an instance, but ensure it is a unicode string | ||||||
|     # on both python 3 (already the case) and 2 (not the case). |     # on both python 3 (already the case) and 2 (not the case). | ||||||
|     if six.PY2: |     if six.PY2: | ||||||
|         repr(instance).decode('utf-8') |         return repr(instance).decode('utf-8') | ||||||
|     return repr(instance) |     return repr(instance) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -253,7 +253,7 @@ class SerializerMetaclass(type): | ||||||
|         # If this class is subclassing another Serializer, add that Serializer's |         # If this class is subclassing another Serializer, add that Serializer's | ||||||
|         # fields.  Note that we loop over the bases in *reverse*. This is necessary |         # fields.  Note that we loop over the bases in *reverse*. This is necessary | ||||||
|         # in order to maintain the correct order of fields. |         # in order to maintain the correct order of fields. | ||||||
|         for base in bases[::-1]: |         for base in reversed(bases): | ||||||
|             if hasattr(base, '_declared_fields'): |             if hasattr(base, '_declared_fields'): | ||||||
|                 fields = list(base._declared_fields.items()) + fields |                 fields = list(base._declared_fields.items()) + fields | ||||||
| 
 | 
 | ||||||
|  | @ -880,8 +880,8 @@ class ModelSerializer(Serializer): | ||||||
|         # Retrieve metadata about fields & relationships on the model class. |         # Retrieve metadata about fields & relationships on the model class. | ||||||
|         info = model_meta.get_field_info(model) |         info = model_meta.get_field_info(model) | ||||||
| 
 | 
 | ||||||
|         # Use the default set of field names if none is supplied explicitly. |  | ||||||
|         if fields is None: |         if fields is None: | ||||||
|  |             # Use the default set of field names if none is supplied explicitly. | ||||||
|             fields = self._get_default_field_names(declared_fields, info) |             fields = self._get_default_field_names(declared_fields, info) | ||||||
|             exclude = getattr(self.Meta, 'exclude', None) |             exclude = getattr(self.Meta, 'exclude', None) | ||||||
|             if exclude is not None: |             if exclude is not None: | ||||||
|  | @ -891,6 +891,23 @@ class ModelSerializer(Serializer): | ||||||
|                         field_name |                         field_name | ||||||
|                     ) |                     ) | ||||||
|                     fields.remove(field_name) |                     fields.remove(field_name) | ||||||
|  |         else: | ||||||
|  |             # Check that any fields declared on the class are | ||||||
|  |             # also explicitly included in `Meta.fields`. | ||||||
|  | 
 | ||||||
|  |             # Note that we ignore any fields that were declared on a parent | ||||||
|  |             # class, in order to support only including a subset of fields | ||||||
|  |             # when subclassing serializers. | ||||||
|  |             declared_field_names = set(declared_fields.keys()) | ||||||
|  |             for cls in self.__class__.__bases__: | ||||||
|  |                 declared_field_names -= set(getattr(cls, '_declared_fields', [])) | ||||||
|  | 
 | ||||||
|  |             missing_fields = declared_field_names - set(fields) | ||||||
|  |             assert not missing_fields, ( | ||||||
|  |                 'Field `%s` has been declared on serializer `%s`, but ' | ||||||
|  |                 'is missing from `Meta.fields`.' % | ||||||
|  |                 (list(missing_fields)[0], self.__class__.__name__) | ||||||
|  |             ) | ||||||
| 
 | 
 | ||||||
|         # Determine the set of model fields, and the fields that they map to. |         # Determine the set of model fields, and the fields that they map to. | ||||||
|         # We actually only need this to deal with the slightly awkward case |         # We actually only need this to deal with the slightly awkward case | ||||||
|  | @ -1024,17 +1041,6 @@ class ModelSerializer(Serializer): | ||||||
|                     (field_name, model.__class__.__name__) |                     (field_name, model.__class__.__name__) | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|             # Check that any fields declared on the class are |  | ||||||
|             # also explicitly included in `Meta.fields`. |  | ||||||
|             missing_fields = set(declared_fields.keys()) - set(fields) |  | ||||||
|             if missing_fields: |  | ||||||
|                 missing_field = list(missing_fields)[0] |  | ||||||
|                 raise ImproperlyConfigured( |  | ||||||
|                     'Field `%s` has been declared on serializer `%s`, but ' |  | ||||||
|                     'is missing from `Meta.fields`.' % |  | ||||||
|                     (missing_field, self.__class__.__name__) |  | ||||||
|                 ) |  | ||||||
| 
 |  | ||||||
|             # Populate any kwargs defined in `Meta.extra_kwargs` |             # Populate any kwargs defined in `Meta.extra_kwargs` | ||||||
|             extras = extra_kwargs.get(field_name, {}) |             extras = extra_kwargs.get(field_name, {}) | ||||||
|             if extras.get('read_only', False): |             if extras.get('read_only', False): | ||||||
|  |  | ||||||
|  | @ -105,3 +105,6 @@ class BindingDict(collections.MutableMapping): | ||||||
| 
 | 
 | ||||||
|     def __len__(self): |     def __len__(self): | ||||||
|         return len(self.fields) |         return len(self.fields) | ||||||
|  | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return dict.__repr__(self.fields) | ||||||
|  |  | ||||||
|  | @ -5,11 +5,14 @@ shortcuts for automatically creating serializers based on a given model class. | ||||||
| These tests deal with ensuring that we correctly map the model fields onto | These tests deal with ensuring that we correctly map the model fields onto | ||||||
| an appropriate set of serializer fields for each case. | an appropriate set of serializer fields for each case. | ||||||
| """ | """ | ||||||
|  | from __future__ import unicode_literals | ||||||
| from django.core.exceptions import ImproperlyConfigured | from django.core.exceptions import ImproperlyConfigured | ||||||
| from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator | from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
|  | from django.utils import six | ||||||
| from rest_framework import serializers | from rest_framework import serializers | ||||||
|  | from rest_framework.compat import unicode_repr | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def dedent(blocktext): | def dedent(blocktext): | ||||||
|  | @ -124,7 +127,7 @@ class TestRegularFieldMappings(TestCase): | ||||||
|                 url_field = URLField(max_length=100) |                 url_field = URLField(max_length=100) | ||||||
|                 custom_field = ModelField(model_field=<tests.test_model_serializer.CustomField: custom_field>) |                 custom_field = ModelField(model_field=<tests.test_model_serializer.CustomField: custom_field>) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_field_options(self): |     def test_field_options(self): | ||||||
|         class TestSerializer(serializers.ModelSerializer): |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  | @ -142,7 +145,14 @@ class TestRegularFieldMappings(TestCase): | ||||||
|                 descriptive_field = IntegerField(help_text='Some help text', label='A label') |                 descriptive_field = IntegerField(help_text='Some help text', label='A label') | ||||||
|                 choices_field = ChoiceField(choices=[('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')]) |                 choices_field = ChoiceField(choices=[('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')]) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         if six.PY2: | ||||||
|  |             # This particular case is too awkward to resolve fully across | ||||||
|  |             # both py2 and py3. | ||||||
|  |             expected = expected.replace( | ||||||
|  |                 "('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')", | ||||||
|  |                 "(u'red', u'Red'), (u'blue', u'Blue'), (u'green', u'Green')" | ||||||
|  |             ) | ||||||
|  |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_method_field(self): |     def test_method_field(self): | ||||||
|         """ |         """ | ||||||
|  | @ -221,7 +231,7 @@ class TestRegularFieldMappings(TestCase): | ||||||
|                 model = RegularFieldsModel |                 model = RegularFieldsModel | ||||||
|                 fields = ('auto_field',) |                 fields = ('auto_field',) | ||||||
| 
 | 
 | ||||||
|         with self.assertRaises(ImproperlyConfigured) as excinfo: |         with self.assertRaises(AssertionError) as excinfo: | ||||||
|             TestSerializer().fields |             TestSerializer().fields | ||||||
|         expected = ( |         expected = ( | ||||||
|             'Field `missing` has been declared on serializer ' |             'Field `missing` has been declared on serializer ' | ||||||
|  | @ -229,6 +239,26 @@ class TestRegularFieldMappings(TestCase): | ||||||
|         ) |         ) | ||||||
|         assert str(excinfo.exception) == expected |         assert str(excinfo.exception) == expected | ||||||
| 
 | 
 | ||||||
|  |     def test_missing_superclass_field(self): | ||||||
|  |         """ | ||||||
|  |         Fields that have been declared on a parent of the serializer class may | ||||||
|  |         be excluded from the `Meta.fields` option. | ||||||
|  |         """ | ||||||
|  |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  |             missing = serializers.ReadOnlyField() | ||||||
|  | 
 | ||||||
|  |             class Meta: | ||||||
|  |                 model = RegularFieldsModel | ||||||
|  | 
 | ||||||
|  |         class ChildSerializer(TestSerializer): | ||||||
|  |             missing = serializers.ReadOnlyField() | ||||||
|  | 
 | ||||||
|  |             class Meta: | ||||||
|  |                 model = RegularFieldsModel | ||||||
|  |                 fields = ('auto_field',) | ||||||
|  | 
 | ||||||
|  |         ChildSerializer().fields | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # Tests for relational field mappings. | # Tests for relational field mappings. | ||||||
| # ------------------------------------ | # ------------------------------------ | ||||||
|  | @ -276,7 +306,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                 many_to_many = PrimaryKeyRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all()) |                 many_to_many = PrimaryKeyRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all()) | ||||||
|                 through = PrimaryKeyRelatedField(many=True, read_only=True) |                 through = PrimaryKeyRelatedField(many=True, read_only=True) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_nested_relations(self): |     def test_nested_relations(self): | ||||||
|         class TestSerializer(serializers.ModelSerializer): |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  | @ -300,7 +330,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                     id = IntegerField(label='ID', read_only=True) |                     id = IntegerField(label='ID', read_only=True) | ||||||
|                     name = CharField(max_length=100) |                     name = CharField(max_length=100) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_hyperlinked_relations(self): |     def test_hyperlinked_relations(self): | ||||||
|         class TestSerializer(serializers.HyperlinkedModelSerializer): |         class TestSerializer(serializers.HyperlinkedModelSerializer): | ||||||
|  | @ -315,7 +345,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                 many_to_many = HyperlinkedRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail') |                 many_to_many = HyperlinkedRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail') | ||||||
|                 through = HyperlinkedRelatedField(many=True, read_only=True, view_name='throughtargetmodel-detail') |                 through = HyperlinkedRelatedField(many=True, read_only=True, view_name='throughtargetmodel-detail') | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_nested_hyperlinked_relations(self): |     def test_nested_hyperlinked_relations(self): | ||||||
|         class TestSerializer(serializers.HyperlinkedModelSerializer): |         class TestSerializer(serializers.HyperlinkedModelSerializer): | ||||||
|  | @ -339,7 +369,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                     url = HyperlinkedIdentityField(view_name='throughtargetmodel-detail') |                     url = HyperlinkedIdentityField(view_name='throughtargetmodel-detail') | ||||||
|                     name = CharField(max_length=100) |                     name = CharField(max_length=100) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_pk_reverse_foreign_key(self): |     def test_pk_reverse_foreign_key(self): | ||||||
|         class TestSerializer(serializers.ModelSerializer): |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  | @ -353,7 +383,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                 name = CharField(max_length=100) |                 name = CharField(max_length=100) | ||||||
|                 reverse_foreign_key = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) |                 reverse_foreign_key = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_pk_reverse_one_to_one(self): |     def test_pk_reverse_one_to_one(self): | ||||||
|         class TestSerializer(serializers.ModelSerializer): |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  | @ -367,7 +397,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                 name = CharField(max_length=100) |                 name = CharField(max_length=100) | ||||||
|                 reverse_one_to_one = PrimaryKeyRelatedField(queryset=RelationalModel.objects.all()) |                 reverse_one_to_one = PrimaryKeyRelatedField(queryset=RelationalModel.objects.all()) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_pk_reverse_many_to_many(self): |     def test_pk_reverse_many_to_many(self): | ||||||
|         class TestSerializer(serializers.ModelSerializer): |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  | @ -381,7 +411,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                 name = CharField(max_length=100) |                 name = CharField(max_length=100) | ||||||
|                 reverse_many_to_many = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) |                 reverse_many_to_many = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
|     def test_pk_reverse_through(self): |     def test_pk_reverse_through(self): | ||||||
|         class TestSerializer(serializers.ModelSerializer): |         class TestSerializer(serializers.ModelSerializer): | ||||||
|  | @ -395,7 +425,7 @@ class TestRelationalFieldMappings(TestCase): | ||||||
|                 name = CharField(max_length=100) |                 name = CharField(max_length=100) | ||||||
|                 reverse_through = PrimaryKeyRelatedField(many=True, read_only=True) |                 reverse_through = PrimaryKeyRelatedField(many=True, read_only=True) | ||||||
|         """) |         """) | ||||||
|         self.assertEqual(repr(TestSerializer()), expected) |         self.assertEqual(unicode_repr(TestSerializer()), expected) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestIntegration(TestCase): | class TestIntegration(TestCase): | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user