diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 9b7086c54..834292920 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -128,7 +128,7 @@ For example, to nest a pair of links labelled 'prev' and 'next', and set the nam class CustomPaginationSerializer(pagination.BasePaginationSerializer): links = LinksSerializer(source='*') # Takes the page object as the source - total_results = serializers.Field(source='paginator.count') + total_results = serializers.ReadOnlyField(source='paginator.count') results_field = 'objects' diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 080230faf..929a1710c 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -41,7 +41,7 @@ The example above would generate the following URL patterns: **Note**: The `base_name` argument is used to specify the initial part of the view name pattern. In the example above, that's the `user` or `account` part. -Typically you won't *need* to specify the `base-name` argument, but if you have a viewset where you've defined a custom `get_queryset` method, then the viewset may not have a `.queryset` attribute set. If you try to register that viewset you'll see an error like this: +Typically you won't *need* to specify the `base_name` argument, but if you have a viewset where you've defined a custom `get_queryset` method, then the viewset may not have a `.queryset` attribute set. If you try to register that viewset you'll see an error like this: 'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 25122e141..205efd2f8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -274,7 +274,23 @@ class Field(object): Given the *outgoing* object instance, return the primitive value that should be used for this field. """ - return get_attribute(instance, self.source_attrs) + try: + return get_attribute(instance, self.source_attrs) + except (KeyError, AttributeError) as exc: + msg = ( + 'Got {exc_type} when attempting to get a value for field ' + '`{field}` on serializer `{serializer}`.\nThe serializer ' + 'field might be named incorrectly and not match ' + 'any attribute or key on the `{instance}` instance.\n' + 'Original exception text was: {exc}.'.format( + exc_type=type(exc).__name__, + field=self.field_name, + serializer=self.parent.__class__.__name__, + instance=instance.__class__.__name__, + exc=exc + ) + ) + raise type(exc)(msg) def get_default(self): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9226895e0..5adbca3bd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -611,6 +611,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data): # profile = ProfileSerializer() assert not any( isinstance(field, BaseSerializer) and (key in validated_data) + and isinstance(validated_data[key], (list, dict)) for key, field in serializer.fields.items() ), ( 'The `.{method_name}()` method does not support writable nested' @@ -630,6 +631,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data): # address = serializer.CharField('profile.address') assert not any( '.' in field.source and (key in validated_data) + and isinstance(validated_data[key], (list, dict)) for key, field in serializer.fields.items() ), ( 'The `.{method_name}()` method does not support writable dotted-source ' diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 6dabaf42e..56b390956 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from rest_framework import serializers import pytest @@ -175,3 +176,24 @@ class TestStarredSource: instance = {'a': 1, 'b': 2, 'c': 3, 'd': 4} serializer = self.Serializer(instance) assert serializer.data == self.data + + +class TestIncorrectlyConfigured: + def test_incorrect_field_name(self): + class ExampleSerializer(serializers.Serializer): + incorrect_name = serializers.IntegerField() + + class ExampleObject: + def __init__(self): + self.correct_name = 123 + + instance = ExampleObject() + serializer = ExampleSerializer(instance) + with pytest.raises(AttributeError) as exc_info: + serializer.data + msg = str(exc_info.value) + assert msg.startswith( + "Got AttributeError when attempting to get a value for field `incorrect_name` on serializer `ExampleSerializer`.\n" + "The serializer field might be named incorrectly and not match any attribute or key on the `ExampleObject` instance.\n" + "Original exception text was:" + )