Skip to content

Commit 87734be

Browse files
committed
Configuration correctness tests on ModelSerializer
1 parent 5b7e4af commit 87734be

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

rest_framework/serializers.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
2. The process of marshalling between python primitives and request and
1111
response content is handled by parsers and renderers.
1212
"""
13-
from django.core.exceptions import ValidationError
13+
from django.core.exceptions import ImproperlyConfigured, ValidationError
1414
from django.db import models
1515
from django.utils import six
1616
from django.utils.datastructures import SortedDict
@@ -358,6 +358,7 @@ def _get_base_fields(self):
358358
model = getattr(self.Meta, 'model')
359359
fields = getattr(self.Meta, 'fields', None)
360360
depth = getattr(self.Meta, 'depth', 0)
361+
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
361362

362363
# Retrieve metadata about fields & relationships on the model class.
363364
info = model_meta.get_field_info(model)
@@ -405,9 +406,32 @@ def _get_base_fields(self):
405406
if not issubclass(field_cls, HyperlinkedRelatedField):
406407
kwargs.pop('view_name', None)
407408

408-
else:
409-
assert False, 'Field name `%s` is not valid.' % field_name
409+
elif hasattr(model, field_name):
410+
# Create a read only field for model methods and properties.
411+
field_cls = ReadOnlyField
412+
kwargs = {}
410413

414+
else:
415+
raise ImproperlyConfigured(
416+
'Field name `%s` is not valid for model `%s`.' %
417+
(field_name, model.__class__.__name__)
418+
)
419+
420+
# Check that any fields declared on the class are
421+
# also explicity included in `Meta.fields`.
422+
missing_fields = set(declared_fields.keys()) - set(fields)
423+
if missing_fields:
424+
missing_field = list(missing_fields)[0]
425+
raise ImproperlyConfigured(
426+
'Field `%s` has been declared on serializer `%s`, but '
427+
'is missing from `Meta.fields`.' %
428+
(missing_field, self.__class__.__name__)
429+
)
430+
431+
# Populate any kwargs defined in `Meta.extra_kwargs`
432+
kwargs.update(extra_kwargs.get(field_name, {}))
433+
434+
# Create the serializer field.
411435
ret[field_name] = field_cls(**kwargs)
412436

413437
return ret

tests/test_model_field_mappings.py renamed to tests/test_model_serializer.py

+85
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
These tests deal with ensuring that we correctly map the model fields onto
66
an appropriate set of serializer fields for each case.
77
"""
8+
from django.core.exceptions import ImproperlyConfigured
89
from django.db import models
910
from django.test import TestCase
1011
from rest_framework import serializers
@@ -37,6 +38,9 @@ class RegularFieldsModel(models.Model):
3738
time_field = models.TimeField()
3839
url_field = models.URLField(max_length=100)
3940

41+
def method(self):
42+
return 'method'
43+
4044

4145
class TestRegularFieldMappings(TestCase):
4246
def test_regular_fields(self):
@@ -69,6 +73,87 @@ class Meta:
6973

7074
self.assertEqual(repr(TestSerializer()), expected)
7175

76+
def test_method_field(self):
77+
"""
78+
Properties and methods on the model should be allowed as `Meta.fields`
79+
values, and should map to `ReadOnlyField`.
80+
"""
81+
class TestSerializer(serializers.ModelSerializer):
82+
class Meta:
83+
model = RegularFieldsModel
84+
fields = ('auto_field', 'method')
85+
86+
expected = dedent("""
87+
TestSerializer():
88+
auto_field = IntegerField(read_only=True)
89+
method = ReadOnlyField()
90+
""")
91+
self.assertEqual(repr(TestSerializer()), expected)
92+
93+
def test_pk_fields(self):
94+
"""
95+
Both `pk` and the actual primary key name are valid in `Meta.fields`.
96+
"""
97+
class TestSerializer(serializers.ModelSerializer):
98+
class Meta:
99+
model = RegularFieldsModel
100+
fields = ('pk', 'auto_field')
101+
102+
expected = dedent("""
103+
TestSerializer():
104+
pk = IntegerField(label='Auto field', read_only=True)
105+
auto_field = IntegerField(read_only=True)
106+
""")
107+
self.assertEqual(repr(TestSerializer()), expected)
108+
109+
def test_extra_field_kwargs(self):
110+
"""
111+
Ensure `extra_kwargs` are passed to generated fields.
112+
"""
113+
class TestSerializer(serializers.ModelSerializer):
114+
class Meta:
115+
model = RegularFieldsModel
116+
fields = ('pk', 'char_field')
117+
extra_kwargs = {'char_field': {'default': 'extra'}}
118+
119+
expected = dedent("""
120+
TestSerializer():
121+
pk = IntegerField(label='Auto field', read_only=True)
122+
char_field = CharField(default='extra', max_length=100)
123+
""")
124+
self.assertEqual(repr(TestSerializer()), expected)
125+
126+
def test_invalid_field(self):
127+
"""
128+
Field names that do not map to a model field or relationship should
129+
raise a configuration errror.
130+
"""
131+
class TestSerializer(serializers.ModelSerializer):
132+
class Meta:
133+
model = RegularFieldsModel
134+
fields = ('auto_field', 'invalid')
135+
136+
with self.assertRaises(ImproperlyConfigured) as excinfo:
137+
TestSerializer()
138+
expected = 'Field name `invalid` is not valid for model `ModelBase`.'
139+
assert str(excinfo.exception) == expected
140+
141+
def test_missing_field(self):
142+
class TestSerializer(serializers.ModelSerializer):
143+
missing = serializers.ReadOnlyField()
144+
145+
class Meta:
146+
model = RegularFieldsModel
147+
fields = ('auto_field',)
148+
149+
with self.assertRaises(ImproperlyConfigured) as excinfo:
150+
TestSerializer()
151+
expected = (
152+
'Field `missing` has been declared on serializer '
153+
'`TestSerializer`, but is missing from `Meta.fields`.'
154+
)
155+
assert str(excinfo.exception) == expected
156+
72157

73158
# Testing relational field mappings
74159

0 commit comments

Comments
 (0)