aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2014-09-18 12:17:21 +0100
committerTom Christie2014-09-18 12:17:21 +0100
commit87734be5f41de921ac32ad1f6664db243aab6d07 (patch)
tree7c6b8d18dc0d505525154c69d112925a95174b5d
parent5b7e4af0d657a575cb15eea85a63a7100c636085 (diff)
downloaddjango-rest-framework-87734be5f41de921ac32ad1f6664db243aab6d07.tar.bz2
Configuration correctness tests on ModelSerializer
-rw-r--r--rest_framework/serializers.py30
-rw-r--r--tests/test_model_serializer.py (renamed from tests/test_model_field_mappings.py)85
2 files changed, 112 insertions, 3 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 99dcc349..9f3e53fd 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -10,7 +10,7 @@ python primitives.
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
@@ -358,6 +358,7 @@ class ModelSerializer(Serializer):
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)
@@ -405,9 +406,32 @@ class ModelSerializer(Serializer):
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
diff --git a/tests/test_model_field_mappings.py b/tests/test_model_serializer.py
index 6daa574e..63e36452 100644
--- a/tests/test_model_field_mappings.py
+++ b/tests/test_model_serializer.py
@@ -5,6 +5,7 @@ shortcuts for automatically creating serializers based on a given model class.
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
@@ -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):
@@ -69,6 +73,87 @@ class TestRegularFieldMappings(TestCase):
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