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

Lines changed: 27 additions & 3 deletions
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

Lines changed: 85 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy