Skip to content

Commit

Permalink
Configuration correctness tests on ModelSerializer
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie committed Sep 18, 2014
1 parent 5b7e4af commit 87734be
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 3 deletions.
30 changes: 27 additions & 3 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
2. The process of marshalling between python primitives and request and
response content is handled by parsers and renderers.
"""
from django.core.exceptions import ValidationError
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models
from django.utils import six
from django.utils.datastructures import SortedDict
Expand Down Expand Up @@ -358,6 +358,7 @@ def _get_base_fields(self):
model = getattr(self.Meta, 'model')
fields = getattr(self.Meta, 'fields', None)
depth = getattr(self.Meta, 'depth', 0)
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})

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

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

else:
raise ImproperlyConfigured(
'Field name `%s` is not valid for model `%s`.' %
(field_name, model.__class__.__name__)
)

# Check that any fields declared on the class are
# also explicity 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`
kwargs.update(extra_kwargs.get(field_name, {}))

# Create the serializer field.
ret[field_name] = field_cls(**kwargs)

return ret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
These tests deal with ensuring that we correctly map the model fields onto
an appropriate set of serializer fields for each case.
"""
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.test import TestCase
from rest_framework import serializers
Expand Down Expand Up @@ -37,6 +38,9 @@ class RegularFieldsModel(models.Model):
time_field = models.TimeField()
url_field = models.URLField(max_length=100)

def method(self):
return 'method'


class TestRegularFieldMappings(TestCase):
def test_regular_fields(self):
Expand Down Expand Up @@ -69,6 +73,87 @@ class Meta:

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

def test_method_field(self):
"""
Properties and methods on the model should be allowed as `Meta.fields`
values, and should map to `ReadOnlyField`.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('auto_field', 'method')

expected = dedent("""
TestSerializer():
auto_field = IntegerField(read_only=True)
method = ReadOnlyField()
""")
self.assertEqual(repr(TestSerializer()), expected)

def test_pk_fields(self):
"""
Both `pk` and the actual primary key name are valid in `Meta.fields`.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('pk', 'auto_field')

expected = dedent("""
TestSerializer():
pk = IntegerField(label='Auto field', read_only=True)
auto_field = IntegerField(read_only=True)
""")
self.assertEqual(repr(TestSerializer()), expected)

def test_extra_field_kwargs(self):
"""
Ensure `extra_kwargs` are passed to generated fields.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('pk', 'char_field')
extra_kwargs = {'char_field': {'default': 'extra'}}

expected = dedent("""
TestSerializer():
pk = IntegerField(label='Auto field', read_only=True)
char_field = CharField(default='extra', max_length=100)
""")
self.assertEqual(repr(TestSerializer()), expected)

def test_invalid_field(self):
"""
Field names that do not map to a model field or relationship should
raise a configuration errror.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('auto_field', 'invalid')

with self.assertRaises(ImproperlyConfigured) as excinfo:
TestSerializer()
expected = 'Field name `invalid` is not valid for model `ModelBase`.'
assert str(excinfo.exception) == expected

def test_missing_field(self):
class TestSerializer(serializers.ModelSerializer):
missing = serializers.ReadOnlyField()

class Meta:
model = RegularFieldsModel
fields = ('auto_field',)

with self.assertRaises(ImproperlyConfigured) as excinfo:
TestSerializer()
expected = (
'Field `missing` has been declared on serializer '
'`TestSerializer`, but is missing from `Meta.fields`.'
)
assert str(excinfo.exception) == expected


# Testing relational field mappings

Expand Down

0 comments on commit 87734be

Please sign in to comment.