mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 03:23:59 +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