diff options
Diffstat (limited to 'rest_framework/fields.py')
| -rw-r--r-- | rest_framework/fields.py | 104 |
1 files changed, 77 insertions, 27 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py index ca9c479f..f3e17b18 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError @@ -5,16 +6,18 @@ from django.core.validators import RegexValidator from django.forms import ImageField as DjangoImageField from django.utils import six, timezone from django.utils.dateparse import parse_date, parse_datetime, parse_time -from django.utils.encoding import is_protected_type +from django.utils.encoding import is_protected_type, smart_text from django.utils.translation import ugettext_lazy as _ from rest_framework import ISO_8601 from rest_framework.compat import ( - smart_text, EmailValidator, MinValueValidator, MaxValueValidator, - MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict + EmailValidator, MinValueValidator, MaxValueValidator, + MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict, + unicode_repr, unicode_to_repr ) from rest_framework.exceptions import ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, representation, humanize_datetime +import collections import copy import datetime import decimal @@ -60,14 +63,12 @@ def get_attribute(instance, attrs): # Break out early if we get `None` at any point in a nested lookup. return None try: - instance = getattr(instance, attr) + if isinstance(instance, collections.Mapping): + instance = instance[attr] + else: + instance = getattr(instance, attr) except ObjectDoesNotExist: return None - except AttributeError as exc: - try: - return instance[attr] - except (KeyError, TypeError, AttributeError): - raise exc if is_simple_callable(instance): instance = instance() return instance @@ -114,7 +115,9 @@ class CreateOnlyDefault: return self.default def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, repr(self.default)) + return unicode_to_repr( + '%s(%s)' % (self.__class__.__name__, unicode_repr(self.default)) + ) class CurrentUserDefault: @@ -125,7 +128,7 @@ class CurrentUserDefault: return self.user def __repr__(self): - return '%s()' % self.__class__.__name__ + return unicode_to_repr('%s()' % self.__class__.__name__) class SkipField(Exception): @@ -275,7 +278,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): """ @@ -294,31 +313,47 @@ class Field(object): return self.default() return self.default - def run_validation(self, data=empty): + def validate_empty_values(self, data): """ - Validate a simple representation and return the internal value. - - The provided data may be `empty` if no representation was included - in the input. - - May raise `SkipField` if the field should not be included in the - validated data. + Validate empty values, and either: + + * Raise `ValidationError`, indicating invalid data. + * Raise `SkipField`, indicating that the field should be ignored. + * Return (True, data), indicating an empty value that should be + returned without any furhter validation being applied. + * Return (False, data), indicating a non-empty value, that should + have validation applied as normal. """ if self.read_only: - return self.get_default() + return (True, self.get_default()) if data is empty: if getattr(self.root, 'partial', False): raise SkipField() if self.required: self.fail('required') - return self.get_default() + return (True, self.get_default()) if data is None: if not self.allow_null: self.fail('null') - return None + return (True, None) + + return (False, data) + + def run_validation(self, data=empty): + """ + Validate a simple representation and return the internal value. + + The provided data may be `empty` if no representation was included + in the input. + May raise `SkipField` if the field should not be included in the + validated data. + """ + (is_empty_value, data) = self.validate_empty_values(data) + if is_empty_value: + return data value = self.to_internal_value(data) self.run_validators(value) return value @@ -351,13 +386,23 @@ class Field(object): """ Transform the *incoming* primitive data into a native value. """ - raise NotImplementedError('to_internal_value() must be implemented.') + raise NotImplementedError( + '{cls}.to_internal_value() must be implemented.'.format( + cls=self.__class__.__name__ + ) + ) def to_representation(self, value): """ Transform the *outgoing* native value into primitive data. """ - raise NotImplementedError('to_representation() must be implemented.') + raise NotImplementedError( + '{cls}.to_representation() must be implemented.\n' + 'If you are upgrading from REST framework version 2 ' + 'you might want `ReadOnlyField`.'.format( + cls=self.__class__.__name__ + ) + ) def fail(self, key, **kwargs): """ @@ -422,7 +467,7 @@ class Field(object): This allows us to create descriptive representations for serializer instances that show all the declared fields on the serializer. """ - return representation.field_repr(self) + return unicode_to_repr(representation.field_repr(self)) # Boolean types... @@ -494,7 +539,7 @@ class CharField(Field): default_error_messages = { 'blank': _('This field may not be blank.'), 'max_length': _('Ensure this field has no more than {max_length} characters.'), - 'min_length': _('Ensure this field has no more than {min_length} characters.') + 'min_length': _('Ensure this field has at least {min_length} characters.') } initial = '' coerce_blank_to_null = False @@ -942,9 +987,14 @@ class ChoiceField(Field): (six.text_type(key), key) for key in self.choices.keys() ]) + self.allow_blank = kwargs.pop('allow_blank', False) + super(ChoiceField, self).__init__(**kwargs) def to_internal_value(self, data): + if data == '' and self.allow_blank: + return '' + try: return self.choice_strings_to_values[six.text_type(data)] except KeyError: |
