diff options
Diffstat (limited to 'rest_framework/fields.py')
| -rw-r--r-- | rest_framework/fields.py | 1742 | 
1 files changed, 1068 insertions, 674 deletions
| diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f9931887..a80862e8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,31 +1,39 @@ -""" -Serializer fields perform validation on incoming data. - -They are very similar to Django's form fields. -"""  from __future__ import unicode_literals - -import copy -import datetime -import inspect -import re -import warnings -from decimal import Decimal, DecimalException -from django import forms -from django.core import validators -from django.core.exceptions import ValidationError  from django.conf import settings -from django.db.models.fields import BLANK_CHOICE_DASH -from django.forms import widgets -from django.utils.encoding import is_protected_type +from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ValidationError as DjangoValidationError +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, smart_text  from django.utils.translation import ugettext_lazy as _ -from django.utils.datastructures import SortedDict  from rest_framework import ISO_8601  from rest_framework.compat import ( -    timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, -    force_text, is_non_str_iterable +    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 +import inspect +import re +import uuid + + +class empty: +    """ +    This class is used to represent no data being provided for a given input +    or output value. + +    It is required because `None` may be a valid input or output value. +    """ +    pass  def is_simple_callable(obj): @@ -44,615 +52,861 @@ def is_simple_callable(obj):      return len_args <= len_defaults -def get_component(obj, attr_name): -    """ -    Given an object, and an attribute name, -    return that attribute on the object. +def get_attribute(instance, attrs):      """ -    if isinstance(obj, dict): -        val = obj.get(attr_name) -    else: -        val = getattr(obj, attr_name) - -    if is_simple_callable(val): -        return val() -    return val - - -def readable_datetime_formats(formats): -    format = ', '.join(formats).replace(ISO_8601, -             'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') -    return humanize_strptime(format) - - -def readable_date_formats(formats): -    format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') -    return humanize_strptime(format) - - -def readable_time_formats(formats): -    format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') -    return humanize_strptime(format) - - -def humanize_strptime(format_string): -    # Note that we're missing some of the locale specific mappings that -    # don't really make sense. -    mapping = { -        "%Y": "YYYY", -        "%y": "YY", -        "%m": "MM", -        "%b": "[Jan-Dec]", -        "%B": "[January-December]", -        "%d": "DD", -        "%H": "hh", -        "%I": "hh",  # Requires '%p' to differentiate from '%H'. -        "%M": "mm", -        "%S": "ss", -        "%f": "uuuuuu", -        "%a": "[Mon-Sun]", -        "%A": "[Monday-Sunday]", -        "%p": "[AM|PM]", -        "%z": "[+HHMM|-HHMM]" -    } -    for key, val in mapping.items(): -        format_string = format_string.replace(key, val) -    return format_string - +    Similar to Python's built in `getattr(instance, attr)`, +    but takes a list of nested attributes, instead of a single attribute. -def strip_multiple_choice_msg(help_text): +    Also accepts either attribute lookup on objects or dictionary lookups.      """ -    Remove the 'Hold down "control" ...' message that is Django enforces in -    select multiple fields on ModelForms.  (Required for 1.5 and earlier) +    for attr in attrs: +        if instance is None: +            # Break out early if we get `None` at any point in a nested lookup. +            return None +        try: +            if isinstance(instance, collections.Mapping): +                instance = instance[attr] +            else: +                instance = getattr(instance, attr) +        except ObjectDoesNotExist: +            return None +        if is_simple_callable(instance): +            try: +                instance = instance() +            except (AttributeError, KeyError) as exc: +                # If we raised an Attribute or KeyError here it'd get treated +                # as an omitted field in `Field.get_attribute()`. Instead we +                # raise a ValueError to ensure the exception is not masked. +                raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc)) -    See https://code.djangoproject.com/ticket/9321 -    """ -    multiple_choice_msg = _(' Hold down "Control", or "Command" on a Mac, to select more than one.') -    multiple_choice_msg = force_text(multiple_choice_msg) +    return instance -    return help_text.replace(multiple_choice_msg, '') +def set_value(dictionary, keys, value): +    """ +    Similar to Python's built in `dictionary[key] = value`, +    but takes a list of nested keys instead of a single key. -class Field(object): -    read_only = True -    creation_counter = 0 -    empty = '' -    type_name = None -    partial = False -    use_files = False -    form_field_class = forms.CharField -    type_label = 'field' - -    def __init__(self, source=None, label=None, help_text=None): -        self.parent = None +    set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2} +    set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2} +    set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}} +    """ +    if not keys: +        dictionary.update(value) +        return -        self.creation_counter = Field.creation_counter -        Field.creation_counter += 1 +    for key in keys[:-1]: +        if key not in dictionary: +            dictionary[key] = {} +        dictionary = dictionary[key] -        self.source = source +    dictionary[keys[-1]] = value -        if label is not None: -            self.label = smart_text(label) -        if help_text is not None: -            self.help_text = strip_multiple_choice_msg(smart_text(help_text)) +class CreateOnlyDefault(object): +    """ +    This class may be used to provide default values that are only used +    for create operations, but that do not return any value for update +    operations. +    """ +    def __init__(self, default): +        self.default = default -    def initialize(self, parent, field_name): -        """ -        Called to set up a field prior to field_to_native or field_from_native. +    def set_context(self, serializer_field): +        self.is_update = serializer_field.parent.instance is not None +        if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update: +            self.default.set_context(serializer_field) -        parent - The parent serializer. -        model_field - The model field this field corresponds to, if one exists. -        """ -        self.parent = parent -        self.root = parent.root or parent -        self.context = self.root.context -        self.partial = self.root.partial -        if self.partial: -            self.required = False +    def __call__(self): +        if self.is_update: +            raise SkipField() +        if callable(self.default): +            return self.default() +        return self.default -    def field_from_native(self, data, files, field_name, into): -        """ -        Given a dictionary and a field name, updates the dictionary `into`, -        with the field and it's deserialized value. -        """ -        return +    def __repr__(self): +        return unicode_to_repr( +            '%s(%s)' % (self.__class__.__name__, unicode_repr(self.default)) +        ) -    def field_to_native(self, obj, field_name): -        """ -        Given and object and a field name, returns the value that should be -        serialized for that field. -        """ -        if obj is None: -            return self.empty -        if self.source == '*': -            return self.to_native(obj) +class CurrentUserDefault(object): +    def set_context(self, serializer_field): +        self.user = serializer_field.context['request'].user -        source = self.source or field_name -        value = obj +    def __call__(self): +        return self.user -        for component in source.split('.'): -            value = get_component(value, component) -            if value is None: -                break +    def __repr__(self): +        return unicode_to_repr('%s()' % self.__class__.__name__) -        return self.to_native(value) -    def to_native(self, value): -        """ -        Converts the field's value into it's simple representation. -        """ -        if is_simple_callable(value): -            value = value() +class SkipField(Exception): +    pass -        if is_protected_type(value): -            return value -        elif (is_non_str_iterable(value) and -              not isinstance(value, (dict, six.string_types))): -            return [self.to_native(item) for item in value] -        elif isinstance(value, dict): -            # Make sure we preserve field ordering, if it exists -            ret = SortedDict() -            for key, val in value.items(): -                ret[key] = self.to_native(val) -            return ret -        return force_text(value) -    def attributes(self): -        """ -        Returns a dictionary of attributes to be used when serializing to xml. -        """ -        if self.type_name: -            return {'type': self.type_name} -        return {} +NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`' +NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`' +NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`' +USE_READONLYFIELD = 'Field(read_only=True) should be ReadOnlyField' +MISSING_ERROR_MESSAGE = ( +    'ValidationError raised by `{class_name}`, but error key `{key}` does ' +    'not exist in the `error_messages` dictionary.' +) -    def metadata(self): -        metadata = SortedDict() -        metadata['type'] = self.type_label -        metadata['required'] = getattr(self, 'required', False) -        optional_attrs = ['read_only', 'label', 'help_text', -                          'min_length', 'max_length'] -        for attr in optional_attrs: -            value = getattr(self, attr, None) -            if value is not None and value != '': -                metadata[attr] = force_text(value, strings_only=True) -        return metadata +class Field(object): +    _creation_counter = 0 -class WritableField(Field): -    """ -    Base for read/write fields. -    """ -    default_validators = []      default_error_messages = {          'required': _('This field is required.'), -        'invalid': _('Invalid value.'), +        'null': _('This field may not be null.')      } -    widget = widgets.TextInput -    default = None +    default_validators = [] +    default_empty_html = empty +    initial = None -    def __init__(self, source=None, label=None, help_text=None, -                 read_only=False, required=None, -                 validators=[], error_messages=None, widget=None, -                 default=None, blank=None): +    def __init__(self, read_only=False, write_only=False, +                 required=None, default=empty, initial=empty, source=None, +                 label=None, help_text=None, style=None, +                 error_messages=None, validators=None, allow_null=False): +        self._creation_counter = Field._creation_counter +        Field._creation_counter += 1 -        # 'blank' is to be deprecated in favor of 'required' -        if blank is not None: -            warnings.warn('The `blank` keyword argument is deprecated. ' -                          'Use the `required` keyword argument instead.', -                          DeprecationWarning, stacklevel=2) -            required = not(blank) +        # If `required` is unset, then use `True` unless a default is provided. +        if required is None: +            required = default is empty and not read_only -        super(WritableField, self).__init__(source=source, label=label, help_text=help_text) +        # Some combinations of keyword arguments do not make sense. +        assert not (read_only and write_only), NOT_READ_ONLY_WRITE_ONLY +        assert not (read_only and required), NOT_READ_ONLY_REQUIRED +        assert not (required and default is not empty), NOT_REQUIRED_DEFAULT +        assert not (read_only and self.__class__ == Field), USE_READONLYFIELD          self.read_only = read_only -        if required is None: -            self.required = not(read_only) -        else: -            assert not (read_only and required), "Cannot set required=True and read_only=True" -            self.required = required +        self.write_only = write_only +        self.required = required +        self.default = default +        self.source = source +        self.initial = self.initial if (initial is empty) else initial +        self.label = label +        self.help_text = help_text +        self.style = {} if style is None else style +        self.allow_null = allow_null + +        if self.default_empty_html is not empty: +            if not required: +                self.default_empty_html = empty +            elif default is not empty: +                self.default_empty_html = default + +        if validators is not None: +            self.validators = validators[:] + +        # These are set up by `.bind()` when the field is added to a serializer. +        self.field_name = None +        self.parent = None +        # Collect default error message from self and parent classes          messages = {} -        for c in reversed(self.__class__.__mro__): -            messages.update(getattr(c, 'default_error_messages', {})) +        for cls in reversed(self.__class__.__mro__): +            messages.update(getattr(cls, 'default_error_messages', {}))          messages.update(error_messages or {})          self.error_messages = messages -        self.validators = self.default_validators + validators -        self.default = default if default is not None else self.default +    def bind(self, field_name, parent): +        """ +        Initializes the field name and parent for the field instance. +        Called when a field is added to the parent serializer instance. +        """ -        # Widgets are ony used for HTML forms. -        widget = widget or self.widget -        if isinstance(widget, type): -            widget = widget() -        self.widget = widget +        # In order to enforce a consistent style, we error if a redundant +        # 'source' argument has been used. For example: +        # my_field = serializer.CharField(source='my_field') +        assert self.source != field_name, ( +            "It is redundant to specify `source='%s'` on field '%s' in " +            "serializer '%s', because it is the same as the field name. " +            "Remove the `source` keyword argument." % +            (field_name, self.__class__.__name__, parent.__class__.__name__) +        ) + +        self.field_name = field_name +        self.parent = parent -    def __deepcopy__(self, memo): -        result = copy.copy(self) -        memo[id(self)] = result -        result.validators = self.validators[:] -        return result +        # `self.label` should default to being based on the field name. +        if self.label is None: +            self.label = field_name.replace('_', ' ').capitalize() + +        # self.source should default to being the same as the field name. +        if self.source is None: +            self.source = field_name + +        # self.source_attrs is a list of attributes that need to be looked up +        # when serializing the instance, or populating the validated data. +        if self.source == '*': +            self.source_attrs = [] +        else: +            self.source_attrs = self.source.split('.') + +    # .validators is a lazily loaded property, that gets its default +    # value from `get_validators`. +    @property +    def validators(self): +        if not hasattr(self, '_validators'): +            self._validators = self.get_validators() +        return self._validators + +    @validators.setter +    def validators(self, validators): +        self._validators = validators + +    def get_validators(self): +        return self.default_validators[:] + +    def get_initial(self): +        """ +        Return a value to use when the field is being returned as a primitive +        value, without any object instance. +        """ +        return self.initial + +    def get_value(self, dictionary): +        """ +        Given the *incoming* primitive data, return the value for this field +        that should be validated and transformed to a native value. +        """ +        if html.is_html_input(dictionary): +            # HTML forms will represent empty fields as '', and cannot +            # represent None or False values directly. +            if self.field_name not in dictionary: +                if getattr(self.root, 'partial', False): +                    return empty +                return self.default_empty_html +            ret = dictionary[self.field_name] +            if ret == '' and self.allow_null: +                # If the field is blank, and null is a valid value then +                # determine if we should use null instead. +                return '' if getattr(self, 'allow_blank', False) else None +            return ret +        return dictionary.get(self.field_name, empty) + +    def get_attribute(self, instance): +        """ +        Given the *outgoing* object instance, return the primitive value +        that should be used for this field. +        """ +        try: +            return get_attribute(instance, self.source_attrs) +        except (KeyError, AttributeError) as exc: +            if not self.required and self.default is empty: +                raise SkipField() +            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): +        """ +        Return the default value to use when validating data if no input +        is provided for this field. + +        If a default has not been set for this field then this will simply +        return `empty`, indicating that no value should be set in the +        validated data for this field. +        """ +        if self.default is empty: +            raise SkipField() +        if callable(self.default): +            if hasattr(self.default, 'set_context'): +                self.default.set_context(self) +            return self.default() +        return self.default + +    def validate_empty_values(self, 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 (True, self.get_default()) + +        if data is empty: +            if getattr(self.root, 'partial', False): +                raise SkipField() +            if self.required: +                self.fail('required') +            return (True, self.get_default()) -    def validate(self, value): -        if value in validators.EMPTY_VALUES and self.required: -            raise ValidationError(self.error_messages['required']) +        if data is None: +            if not self.allow_null: +                self.fail('null') +            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      def run_validators(self, value): -        if value in validators.EMPTY_VALUES: -            return +        """ +        Test the given value against all the validators on the field, +        and either raise a `ValidationError` or simply return. +        """          errors = [] -        for v in self.validators: +        for validator in self.validators: +            if hasattr(validator, 'set_context'): +                validator.set_context(self) +              try: -                v(value) -            except ValidationError as e: -                if hasattr(e, 'code') and e.code in self.error_messages: -                    message = self.error_messages[e.code] -                    if e.params: -                        message = message % e.params -                    errors.append(message) -                else: -                    errors.extend(e.messages) +                validator(value) +            except ValidationError as exc: +                # If the validation error contains a mapping of fields to +                # errors then simply raise it immediately rather than +                # attempting to accumulate a list of errors. +                if isinstance(exc.detail, dict): +                    raise +                errors.extend(exc.detail) +            except DjangoValidationError as exc: +                errors.extend(exc.messages)          if errors:              raise ValidationError(errors) -    def field_from_native(self, data, files, field_name, into): +    def to_internal_value(self, data):          """ -        Given a dictionary and a field name, updates the dictionary `into`, -        with the field and it's deserialized value. +        Transform the *incoming* primitive data into a native value.          """ -        if self.read_only: -            return +        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( +            '{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): +        """ +        A helper method that simply raises a validation error. +        """          try: -            if self.use_files: -                files = files or {} -                native = files[field_name] -            else: -                native = data[field_name] +            msg = self.error_messages[key]          except KeyError: -            if self.default is not None and not self.partial: -                # Note: partial updates shouldn't set defaults -                if is_simple_callable(self.default): -                    native = self.default() -                else: -                    native = self.default -            else: -                if self.required: -                    raise ValidationError(self.error_messages['required']) -                return - -        value = self.from_native(native) -        if self.source == '*': -            if value: -                into.update(value) -        else: -            self.validate(value) -            self.run_validators(value) -            into[self.source or field_name] = value - -    def from_native(self, value): +            class_name = self.__class__.__name__ +            msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key) +            raise AssertionError(msg) +        message_string = msg.format(**kwargs) +        raise ValidationError(message_string) + +    @property +    def root(self):          """ -        Reverts a simple representation back to the field's value. +        Returns the top-level serializer for this field.          """ -        return value +        root = self +        while root.parent is not None: +            root = root.parent +        return root +    @property +    def context(self): +        """ +        Returns the context as passed to the root serializer on initialization. +        """ +        return getattr(self.root, '_context', {}) -class ModelField(WritableField): -    """ -    A generic field that can be used against an arbitrary model field. -    """ -    def __init__(self, *args, **kwargs): -        try: -            self.model_field = kwargs.pop('model_field') -        except KeyError: -            raise ValueError("ModelField requires 'model_field' kwarg") +    def __new__(cls, *args, **kwargs): +        """ +        When a field is instantiated, we store the arguments that were used, +        so that we can present a helpful representation of the object. +        """ +        instance = super(Field, cls).__new__(cls) +        instance._args = args +        instance._kwargs = kwargs +        return instance -        self.min_length = kwargs.pop('min_length', -                                     getattr(self.model_field, 'min_length', None)) -        self.max_length = kwargs.pop('max_length', -                                     getattr(self.model_field, 'max_length', None)) -        self.min_value = kwargs.pop('min_value', -                                    getattr(self.model_field, 'min_value', None)) -        self.max_value = kwargs.pop('max_value', -                                    getattr(self.model_field, 'max_value', None)) +    def __deepcopy__(self, memo): +        """ +        When cloning fields we instantiate using the arguments it was +        originally created with, rather than copying the complete state. +        """ +        args = copy.deepcopy(self._args) +        kwargs = dict(self._kwargs) +        # Bit ugly, but we need to special case 'validators' as Django's +        # RegexValidator does not support deepcopy. +        # We treat validator callables as immutable objects. +        # See https://github.com/tomchristie/django-rest-framework/issues/1954 +        validators = kwargs.pop('validators', None) +        kwargs = copy.deepcopy(kwargs) +        if validators is not None: +            kwargs['validators'] = validators +        return self.__class__(*args, **kwargs) + +    def __repr__(self): +        """ +        Fields are represented using their initial calling arguments. +        This allows us to create descriptive representations for serializer +        instances that show all the declared fields on the serializer. +        """ +        return unicode_to_repr(representation.field_repr(self)) -        super(ModelField, self).__init__(*args, **kwargs) -        if self.min_length is not None: -            self.validators.append(validators.MinLengthValidator(self.min_length)) -        if self.max_length is not None: -            self.validators.append(validators.MaxLengthValidator(self.max_length)) -        if self.min_value is not None: -            self.validators.append(validators.MinValueValidator(self.min_value)) -        if self.max_value is not None: -            self.validators.append(validators.MaxValueValidator(self.max_value)) +# Boolean types... -    def from_native(self, value): -        rel = getattr(self.model_field, "rel", None) -        if rel is not None: -            return rel.to._meta.get_field(rel.field_name).to_python(value) -        else: -            return self.model_field.to_python(value) +class BooleanField(Field): +    default_error_messages = { +        'invalid': _('"{input}" is not a valid boolean.') +    } +    default_empty_html = False +    initial = False +    TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True)) +    FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False)) -    def field_to_native(self, obj, field_name): -        value = self.model_field._get_val_from_obj(obj) -        if is_protected_type(value): -            return value -        return self.model_field.value_to_string(obj) +    def __init__(self, **kwargs): +        assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.' +        super(BooleanField, self).__init__(**kwargs) -    def attributes(self): -        return { -            "type": self.model_field.get_internal_type() -        } +    def to_internal_value(self, data): +        if data in self.TRUE_VALUES: +            return True +        elif data in self.FALSE_VALUES: +            return False +        self.fail('invalid', input=data) +    def to_representation(self, value): +        if value in self.TRUE_VALUES: +            return True +        elif value in self.FALSE_VALUES: +            return False +        return bool(value) -##### Typed Fields ##### -class BooleanField(WritableField): -    type_name = 'BooleanField' -    type_label = 'boolean' -    form_field_class = forms.BooleanField -    widget = widgets.CheckboxInput +class NullBooleanField(Field):      default_error_messages = { -        'invalid': _("'%s' value must be either True or False."), +        'invalid': _('"{input}" is not a valid boolean.')      } -    empty = False +    initial = None +    TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True)) +    FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False)) +    NULL_VALUES = set(('n', 'N', 'null', 'Null', 'NULL', '', None)) + +    def __init__(self, **kwargs): +        assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.' +        kwargs['allow_null'] = True +        super(NullBooleanField, self).__init__(**kwargs) -    # Note: we set default to `False` in order to fill in missing value not -    # supplied by html form.  TODO: Fix so that only html form input gets -    # this behavior. -    default = False +    def to_internal_value(self, data): +        if data in self.TRUE_VALUES: +            return True +        elif data in self.FALSE_VALUES: +            return False +        elif data in self.NULL_VALUES: +            return None +        self.fail('invalid', input=data) -    def from_native(self, value): -        if value in ('true', 't', 'True', '1'): +    def to_representation(self, value): +        if value in self.NULL_VALUES: +            return None +        if value in self.TRUE_VALUES:              return True -        if value in ('false', 'f', 'False', '0'): +        elif value in self.FALSE_VALUES:              return False          return bool(value) -class CharField(WritableField): -    type_name = 'CharField' -    type_label = 'string' -    form_field_class = forms.CharField +# String types... -    def __init__(self, max_length=None, min_length=None, *args, **kwargs): -        self.max_length, self.min_length = max_length, min_length -        super(CharField, self).__init__(*args, **kwargs) -        if min_length is not None: -            self.validators.append(validators.MinLengthValidator(min_length)) -        if max_length is not None: -            self.validators.append(validators.MaxLengthValidator(max_length)) +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 at least {min_length} characters.') +    } +    initial = '' -    def from_native(self, value): -        if isinstance(value, six.string_types) or value is None: -            return value -        return smart_text(value) +    def __init__(self, **kwargs): +        self.allow_blank = kwargs.pop('allow_blank', False) +        self.trim_whitespace = kwargs.pop('trim_whitespace', True) +        self.max_length = kwargs.pop('max_length', None) +        self.min_length = kwargs.pop('min_length', None) +        super(CharField, self).__init__(**kwargs) +        if self.max_length is not None: +            message = self.error_messages['max_length'].format(max_length=self.max_length) +            self.validators.append(MaxLengthValidator(self.max_length, message=message)) +        if self.min_length is not None: +            message = self.error_messages['min_length'].format(min_length=self.min_length) +            self.validators.append(MinLengthValidator(self.min_length, message=message)) +    def run_validation(self, data=empty): +        # Test for the empty string here so that it does not get validated, +        # and so that subclasses do not need to handle it explicitly +        # inside the `to_internal_value()` method. +        if data == '': +            if not self.allow_blank: +                self.fail('blank') +            return '' +        return super(CharField, self).run_validation(data) -class URLField(CharField): -    type_name = 'URLField' -    type_label = 'url' +    def to_internal_value(self, data): +        value = six.text_type(data) +        return value.strip() if self.trim_whitespace else value + +    def to_representation(self, value): +        return six.text_type(value) + + +class EmailField(CharField): +    default_error_messages = { +        'invalid': _('Enter a valid email address.') +    }      def __init__(self, **kwargs): -        kwargs['validators'] = [validators.URLValidator()] -        super(URLField, self).__init__(**kwargs) +        super(EmailField, self).__init__(**kwargs) +        validator = EmailValidator(message=self.error_messages['invalid']) +        self.validators.append(validator) + + +class RegexField(CharField): +    default_error_messages = { +        'invalid': _('This value does not match the required pattern.') +    } + +    def __init__(self, regex, **kwargs): +        super(RegexField, self).__init__(**kwargs) +        validator = RegexValidator(regex, message=self.error_messages['invalid']) +        self.validators.append(validator)  class SlugField(CharField): -    type_name = 'SlugField' -    type_label = 'slug' -    form_field_class = forms.SlugField +    default_error_messages = { +        'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.') +    } +    def __init__(self, **kwargs): +        super(SlugField, self).__init__(**kwargs) +        slug_regex = re.compile(r'^[-a-zA-Z0-9_]+$') +        validator = RegexValidator(slug_regex, message=self.error_messages['invalid']) +        self.validators.append(validator) + + +class URLField(CharField):      default_error_messages = { -        'invalid': _("Enter a valid 'slug' consisting of letters, numbers," -                     " underscores or hyphens."), +        'invalid': _('Enter a valid URL.')      } -    default_validators = [validators.validate_slug] -    def __init__(self, *args, **kwargs): -        super(SlugField, self).__init__(*args, **kwargs) +    def __init__(self, **kwargs): +        super(URLField, self).__init__(**kwargs) +        validator = URLValidator(message=self.error_messages['invalid']) +        self.validators.append(validator) -class ChoiceField(WritableField): -    type_name = 'ChoiceField' -    type_label = 'multiple choice' -    form_field_class = forms.ChoiceField -    widget = widgets.Select +class UUIDField(Field):      default_error_messages = { -        'invalid_choice': _('Select a valid choice. %(value)s is not one of ' -                            'the available choices.'), +        'invalid': _('"{value}" is not a valid UUID.'),      } -    def __init__(self, choices=(), *args, **kwargs): -        super(ChoiceField, self).__init__(*args, **kwargs) -        self.choices = choices -        if not self.required: -            self.choices = BLANK_CHOICE_DASH + self.choices +    def to_internal_value(self, data): +        if not isinstance(data, uuid.UUID): +            try: +                return uuid.UUID(data) +            except (ValueError, TypeError): +                self.fail('invalid', value=data) +        return data -    def _get_choices(self): -        return self._choices +    def to_representation(self, value): +        return str(value) -    def _set_choices(self, value): -        # Setting choices also sets the choices on the widget. -        # choices can be any iterable, but we call list() on it because -        # it will be consumed more than once. -        self._choices = self.widget.choices = list(value) -    choices = property(_get_choices, _set_choices) +# Number types... -    def validate(self, value): -        """ -        Validates that the input is in self.choices. -        """ -        super(ChoiceField, self).validate(value) -        if value and not self.valid_value(value): -            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) +class IntegerField(Field): +    default_error_messages = { +        'invalid': _('A valid integer is required.'), +        'max_value': _('Ensure this value is less than or equal to {max_value}.'), +        'min_value': _('Ensure this value is greater than or equal to {min_value}.'), +        'max_string_length': _('String value too large.') +    } +    MAX_STRING_LENGTH = 1000  # Guard against malicious string inputs. -    def valid_value(self, value): -        """ -        Check to see if the provided value is a valid choice. -        """ -        for k, v in self.choices: -            if isinstance(v, (list, tuple)): -                # This is an optgroup, so look inside the group for options -                for k2, v2 in v: -                    if value == smart_text(k2): -                        return True -            else: -                if value == smart_text(k) or value == k: -                    return True -        return False +    def __init__(self, **kwargs): +        self.max_value = kwargs.pop('max_value', None) +        self.min_value = kwargs.pop('min_value', None) +        super(IntegerField, self).__init__(**kwargs) +        if self.max_value is not None: +            message = self.error_messages['max_value'].format(max_value=self.max_value) +            self.validators.append(MaxValueValidator(self.max_value, message=message)) +        if self.min_value is not None: +            message = self.error_messages['min_value'].format(min_value=self.min_value) +            self.validators.append(MinValueValidator(self.min_value, message=message)) +    def to_internal_value(self, data): +        if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: +            self.fail('max_string_length') -class EmailField(CharField): -    type_name = 'EmailField' -    type_label = 'email' -    form_field_class = forms.EmailField +        try: +            data = int(data) +        except (ValueError, TypeError): +            self.fail('invalid') +        return data + +    def to_representation(self, value): +        return int(value) + +class FloatField(Field):      default_error_messages = { -        'invalid': _('Enter a valid email address.'), +        'invalid': _('A valid number is required.'), +        'max_value': _('Ensure this value is less than or equal to {max_value}.'), +        'min_value': _('Ensure this value is greater than or equal to {min_value}.'), +        'max_string_length': _('String value too large.')      } -    default_validators = [validators.validate_email] +    MAX_STRING_LENGTH = 1000  # Guard against malicious string inputs. -    def from_native(self, value): -        ret = super(EmailField, self).from_native(value) -        if ret is None: -            return None -        return ret.strip() +    def __init__(self, **kwargs): +        self.max_value = kwargs.pop('max_value', None) +        self.min_value = kwargs.pop('min_value', None) +        super(FloatField, self).__init__(**kwargs) +        if self.max_value is not None: +            message = self.error_messages['max_value'].format(max_value=self.max_value) +            self.validators.append(MaxValueValidator(self.max_value, message=message)) +        if self.min_value is not None: +            message = self.error_messages['min_value'].format(min_value=self.min_value) +            self.validators.append(MinValueValidator(self.min_value, message=message)) +    def to_internal_value(self, data): +        if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: +            self.fail('max_string_length') -class RegexField(CharField): -    type_name = 'RegexField' -    type_label = 'regex' -    form_field_class = forms.RegexField +        try: +            return float(data) +        except (TypeError, ValueError): +            self.fail('invalid') + +    def to_representation(self, value): +        return float(value) + + +class DecimalField(Field): +    default_error_messages = { +        'invalid': _('A valid number is required.'), +        'max_value': _('Ensure this value is less than or equal to {max_value}.'), +        'min_value': _('Ensure this value is greater than or equal to {min_value}.'), +        'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'), +        'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'), +        'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'), +        'max_string_length': _('String value too large.') +    } +    MAX_STRING_LENGTH = 1000  # Guard against malicious string inputs. + +    coerce_to_string = api_settings.COERCE_DECIMAL_TO_STRING + +    def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, **kwargs): +        self.max_digits = max_digits +        self.decimal_places = decimal_places +        self.coerce_to_string = coerce_to_string if (coerce_to_string is not None) else self.coerce_to_string + +        self.max_value = max_value +        self.min_value = min_value + +        super(DecimalField, self).__init__(**kwargs) + +        if self.max_value is not None: +            message = self.error_messages['max_value'].format(max_value=self.max_value) +            self.validators.append(MaxValueValidator(self.max_value, message=message)) +        if self.min_value is not None: +            message = self.error_messages['min_value'].format(min_value=self.min_value) +            self.validators.append(MinValueValidator(self.min_value, message=message)) + +    def to_internal_value(self, data): +        """ +        Validates that the input is a decimal number. Returns a Decimal +        instance. Returns None for empty values. Ensures that there are no more +        than max_digits in the number, and no more than decimal_places digits +        after the decimal point. +        """ +        data = smart_text(data).strip() +        if len(data) > self.MAX_STRING_LENGTH: +            self.fail('max_string_length') -    def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): -        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) -        self.regex = regex +        try: +            value = decimal.Decimal(data) +        except decimal.DecimalException: +            self.fail('invalid') -    def _get_regex(self): -        return self._regex +        # Check for NaN. It is the only value that isn't equal to itself, +        # so we can use this to identify NaN values. +        if value != value: +            self.fail('invalid') -    def _set_regex(self, regex): -        if isinstance(regex, six.string_types): -            regex = re.compile(regex) -        self._regex = regex -        if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: -            self.validators.remove(self._regex_validator) -        self._regex_validator = validators.RegexValidator(regex=regex) -        self.validators.append(self._regex_validator) +        # Check for infinity and negative infinity. +        if value in (decimal.Decimal('Inf'), decimal.Decimal('-Inf')): +            self.fail('invalid') -    regex = property(_get_regex, _set_regex) +        sign, digittuple, exponent = value.as_tuple() +        decimals = abs(exponent) +        # digittuple doesn't include any leading zeros. +        digits = len(digittuple) +        if decimals > digits: +            # We have leading zeros up to or past the decimal point.  Count +            # everything past the decimal point as a digit.  We do not count +            # 0 before the decimal point as a digit since that would mean +            # we would not allow max_digits = decimal_places. +            digits = decimals +        whole_digits = digits - decimals + +        if self.max_digits is not None and digits > self.max_digits: +            self.fail('max_digits', max_digits=self.max_digits) +        if self.decimal_places is not None and decimals > self.decimal_places: +            self.fail('max_decimal_places', max_decimal_places=self.decimal_places) +        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): +            self.fail('max_whole_digits', max_whole_digits=self.max_digits - self.decimal_places) + +        return value + +    def to_representation(self, value): +        if not isinstance(value, decimal.Decimal): +            value = decimal.Decimal(six.text_type(value).strip()) + +        context = decimal.getcontext().copy() +        context.prec = self.max_digits +        quantized = value.quantize( +            decimal.Decimal('.1') ** self.decimal_places, +            context=context +        ) +        if not self.coerce_to_string: +            return quantized +        return '{0:f}'.format(quantized) -class DateField(WritableField): -    type_name = 'DateField' -    type_label = 'date' -    widget = widgets.DateInput -    form_field_class = forms.DateField +# Date & time fields... +class DateTimeField(Field):      default_error_messages = { -        'invalid': _("Date has wrong format. Use one of these formats instead: %s"), +        'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'), +        'date': _('Expected a datetime but got a date.'),      } -    empty = None -    input_formats = api_settings.DATE_INPUT_FORMATS -    format = api_settings.DATE_FORMAT +    format = api_settings.DATETIME_FORMAT +    input_formats = api_settings.DATETIME_INPUT_FORMATS +    default_timezone = timezone.get_default_timezone() if settings.USE_TZ else None -    def __init__(self, input_formats=None, format=None, *args, **kwargs): +    def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs): +        self.format = format if format is not empty else self.format          self.input_formats = input_formats if input_formats is not None else self.input_formats -        self.format = format if format is not None else self.format -        super(DateField, self).__init__(*args, **kwargs) +        self.default_timezone = default_timezone if default_timezone is not None else self.default_timezone +        super(DateTimeField, self).__init__(*args, **kwargs) -    def from_native(self, value): -        if value in validators.EMPTY_VALUES: -            return None +    def enforce_timezone(self, value): +        """ +        When `self.default_timezone` is `None`, always return naive datetimes. +        When `self.default_timezone` is not `None`, always return aware datetimes. +        """ +        if (self.default_timezone is not None) and not timezone.is_aware(value): +            return timezone.make_aware(value, self.default_timezone) +        elif (self.default_timezone is None) and timezone.is_aware(value): +            return timezone.make_naive(value, timezone.UTC()) +        return value + +    def to_internal_value(self, value): +        if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime): +            self.fail('date')          if isinstance(value, datetime.datetime): -            if timezone and settings.USE_TZ and timezone.is_aware(value): -                # Convert aware datetimes to the default time zone -                # before casting them to dates (#17742). -                default_timezone = timezone.get_default_timezone() -                value = timezone.make_naive(value, default_timezone) -            return value.date() -        if isinstance(value, datetime.date): -            return value +            return self.enforce_timezone(value)          for format in self.input_formats:              if format.lower() == ISO_8601:                  try: -                    parsed = parse_date(value) +                    parsed = parse_datetime(value)                  except (ValueError, TypeError):                      pass                  else:                      if parsed is not None: -                        return parsed +                        return self.enforce_timezone(parsed)              else:                  try:                      parsed = datetime.datetime.strptime(value, format)                  except (ValueError, TypeError):                      pass                  else: -                    return parsed.date() +                    return self.enforce_timezone(parsed) -        msg = self.error_messages['invalid'] % readable_date_formats(self.input_formats) -        raise ValidationError(msg) +        humanized_format = humanize_datetime.datetime_formats(self.input_formats) +        self.fail('invalid', format=humanized_format) -    def to_native(self, value): -        if value is None or self.format is None: +    def to_representation(self, value): +        if self.format is None:              return value -        if isinstance(value, datetime.datetime): -            value = value.date() -          if self.format.lower() == ISO_8601: -            return value.isoformat() +            value = value.isoformat() +            if value.endswith('+00:00'): +                value = value[:-6] + 'Z' +            return value          return value.strftime(self.format) -class DateTimeField(WritableField): -    type_name = 'DateTimeField' -    type_label = 'datetime' -    widget = widgets.DateTimeInput -    form_field_class = forms.DateTimeField - +class DateField(Field):      default_error_messages = { -        'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), +        'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'), +        'datetime': _('Expected a date but got a datetime.'),      } -    empty = None -    input_formats = api_settings.DATETIME_INPUT_FORMATS -    format = api_settings.DATETIME_FORMAT +    format = api_settings.DATE_FORMAT +    input_formats = api_settings.DATE_INPUT_FORMATS -    def __init__(self, input_formats=None, format=None, *args, **kwargs): +    def __init__(self, format=empty, input_formats=None, *args, **kwargs): +        self.format = format if format is not empty else self.format          self.input_formats = input_formats if input_formats is not None else self.input_formats -        self.format = format if format is not None else self.format -        super(DateTimeField, self).__init__(*args, **kwargs) - -    def from_native(self, value): -        if value in validators.EMPTY_VALUES: -            return None +        super(DateField, self).__init__(*args, **kwargs) +    def to_internal_value(self, value):          if isinstance(value, datetime.datetime): -            return value +            self.fail('datetime') +          if isinstance(value, datetime.date): -            value = datetime.datetime(value.year, value.month, value.day) -            if settings.USE_TZ: -                # For backwards compatibility, interpret naive datetimes in -                # local time. This won't work during DST change, but we can't -                # do much about it, so we let the exceptions percolate up the -                # call stack. -                warnings.warn("DateTimeField received a naive datetime (%s)" -                              " while time zone support is active." % value, -                              RuntimeWarning) -                default_timezone = timezone.get_default_timezone() -                value = timezone.make_aware(value, default_timezone)              return value          for format in self.input_formats:              if format.lower() == ISO_8601:                  try: -                    parsed = parse_datetime(value) +                    parsed = parse_date(value)                  except (ValueError, TypeError):                      pass                  else: @@ -664,45 +918,42 @@ class DateTimeField(WritableField):                  except (ValueError, TypeError):                      pass                  else: -                    return parsed +                    return parsed.date() -        msg = self.error_messages['invalid'] % readable_datetime_formats(self.input_formats) -        raise ValidationError(msg) +        humanized_format = humanize_datetime.date_formats(self.input_formats) +        self.fail('invalid', format=humanized_format) -    def to_native(self, value): -        if value is None or self.format is None: +    def to_representation(self, value): +        if self.format is None:              return value +        # Applying a `DateField` to a datetime value is almost always +        # not a sensible thing to do, as it means naively dropping +        # any explicit or implicit timezone info. +        assert not isinstance(value, datetime.datetime), ( +            'Expected a `date`, but got a `datetime`. Refusing to coerce, ' +            'as this may mean losing timezone information. Use a custom ' +            'read-only field and deal with timezone issues explicitly.' +        ) +          if self.format.lower() == ISO_8601: -            ret = value.isoformat() -            if ret.endswith('+00:00'): -                ret = ret[:-6] + 'Z' -            return ret +            return value.isoformat()          return value.strftime(self.format) -class TimeField(WritableField): -    type_name = 'TimeField' -    type_label = 'time' -    widget = widgets.TimeInput -    form_field_class = forms.TimeField - +class TimeField(Field):      default_error_messages = { -        'invalid': _("Time has wrong format. Use one of these formats instead: %s"), +        'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),      } -    empty = None -    input_formats = api_settings.TIME_INPUT_FORMATS      format = api_settings.TIME_FORMAT +    input_formats = api_settings.TIME_INPUT_FORMATS -    def __init__(self, input_formats=None, format=None, *args, **kwargs): +    def __init__(self, format=empty, input_formats=None, *args, **kwargs): +        self.format = format if format is not empty else self.format          self.input_formats = input_formats if input_formats is not None else self.input_formats -        self.format = format if format is not None else self.format          super(TimeField, self).__init__(*args, **kwargs) -    def from_native(self, value): -        if value in validators.EMPTY_VALUES: -            return None - +    def to_internal_value(self, value):          if isinstance(value, datetime.time):              return value @@ -723,246 +974,389 @@ class TimeField(WritableField):                  else:                      return parsed.time() -        msg = self.error_messages['invalid'] % readable_time_formats(self.input_formats) -        raise ValidationError(msg) +        humanized_format = humanize_datetime.time_formats(self.input_formats) +        self.fail('invalid', format=humanized_format) -    def to_native(self, value): -        if value is None or self.format is None: +    def to_representation(self, value): +        if self.format is None:              return value -        if isinstance(value, datetime.datetime): -            value = value.time() +        # Applying a `TimeField` to a datetime value is almost always +        # not a sensible thing to do, as it means naively dropping +        # any explicit or implicit timezone info. +        assert not isinstance(value, datetime.datetime), ( +            'Expected a `time`, but got a `datetime`. Refusing to coerce, ' +            'as this may mean losing timezone information. Use a custom ' +            'read-only field and deal with timezone issues explicitly.' +        )          if self.format.lower() == ISO_8601:              return value.isoformat()          return value.strftime(self.format) -class IntegerField(WritableField): -    type_name = 'IntegerField' -    type_label = 'integer' -    form_field_class = forms.IntegerField +# Choice types... +class ChoiceField(Field):      default_error_messages = { -        'invalid': _('Enter a whole number.'), -        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), -        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), +        'invalid_choice': _('"{input}" is not a valid choice.')      } -    def __init__(self, max_value=None, min_value=None, *args, **kwargs): -        self.max_value, self.min_value = max_value, min_value -        super(IntegerField, self).__init__(*args, **kwargs) - -        if max_value is not None: -            self.validators.append(validators.MaxValueValidator(max_value)) -        if min_value is not None: -            self.validators.append(validators.MinValueValidator(min_value)) - -    def from_native(self, value): -        if value in validators.EMPTY_VALUES: -            return None - -        try: -            value = int(str(value)) -        except (ValueError, TypeError): -            raise ValidationError(self.error_messages['invalid']) -        return value +    def __init__(self, choices, **kwargs): +        # Allow either single or paired choices style: +        # choices = [1, 2, 3] +        # choices = [(1, 'First'), (2, 'Second'), (3, 'Third')] +        pairs = [ +            isinstance(item, (list, tuple)) and len(item) == 2 +            for item in choices +        ] +        if all(pairs): +            self.choices = OrderedDict([(key, display_value) for key, display_value in choices]) +        else: +            self.choices = OrderedDict([(item, item) for item in choices]) +        # Map the string representation of choices to the underlying value. +        # Allows us to deal with eg. integer choices while supporting either +        # integer or string input, but still get the correct datatype out. +        self.choice_strings_to_values = dict([ +            (six.text_type(key), key) for key in self.choices.keys() +        ]) -class FloatField(WritableField): -    type_name = 'FloatField' -    type_label = 'float' -    form_field_class = forms.FloatField +        self.allow_blank = kwargs.pop('allow_blank', False) -    default_error_messages = { -        'invalid': _("'%s' value must be a float."), -    } +        super(ChoiceField, self).__init__(**kwargs) -    def from_native(self, value): -        if value in validators.EMPTY_VALUES: -            return None +    def to_internal_value(self, data): +        if data == '' and self.allow_blank: +            return ''          try: -            return float(value) -        except (TypeError, ValueError): -            msg = self.error_messages['invalid'] % value -            raise ValidationError(msg) +            return self.choice_strings_to_values[six.text_type(data)] +        except KeyError: +            self.fail('invalid_choice', input=data) +    def to_representation(self, value): +        if value in ('', None): +            return value +        return self.choice_strings_to_values[six.text_type(value)] -class DecimalField(WritableField): -    type_name = 'DecimalField' -    type_label = 'decimal' -    form_field_class = forms.DecimalField +class MultipleChoiceField(ChoiceField):      default_error_messages = { -        'invalid': _('Enter a number.'), -        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), -        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), -        'max_digits': _('Ensure that there are no more than %s digits in total.'), -        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), -        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') +        'invalid_choice': _('"{input}" is not a valid choice.'), +        'not_a_list': _('Expected a list of items but got type "{input_type}".')      } +    default_empty_html = [] -    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): -        self.max_value, self.min_value = max_value, min_value -        self.max_digits, self.decimal_places = max_digits, decimal_places -        super(DecimalField, self).__init__(*args, **kwargs) +    def get_value(self, dictionary): +        # We override the default field access in order to support +        # lists in HTML forms. +        if html.is_html_input(dictionary): +            return dictionary.getlist(self.field_name) +        return dictionary.get(self.field_name, empty) -        if max_value is not None: -            self.validators.append(validators.MaxValueValidator(max_value)) -        if min_value is not None: -            self.validators.append(validators.MinValueValidator(min_value)) +    def to_internal_value(self, data): +        if isinstance(data, type('')) or not hasattr(data, '__iter__'): +            self.fail('not_a_list', input_type=type(data).__name__) -    def from_native(self, value): -        """ -        Validates that the input is a decimal number. Returns a Decimal -        instance. Returns None for empty values. Ensures that there are no more -        than max_digits in the number, and no more than decimal_places digits -        after the decimal point. -        """ -        if value in validators.EMPTY_VALUES: -            return None -        value = smart_text(value).strip() -        try: -            value = Decimal(value) -        except DecimalException: -            raise ValidationError(self.error_messages['invalid']) -        return value - -    def validate(self, value): -        super(DecimalField, self).validate(value) -        if value in validators.EMPTY_VALUES: -            return -        # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, -        # since it is never equal to itself. However, NaN is the only value that -        # isn't equal to itself, so we can use this to identify NaN -        if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): -            raise ValidationError(self.error_messages['invalid']) -        sign, digittuple, exponent = value.as_tuple() -        decimals = abs(exponent) -        # digittuple doesn't include any leading zeros. -        digits = len(digittuple) -        if decimals > digits: -            # We have leading zeros up to or past the decimal point.  Count -            # everything past the decimal point as a digit.  We do not count -            # 0 before the decimal point as a digit since that would mean -            # we would not allow max_digits = decimal_places. -            digits = decimals -        whole_digits = digits - decimals +        return set([ +            super(MultipleChoiceField, self).to_internal_value(item) +            for item in data +        ]) -        if self.max_digits is not None and digits > self.max_digits: -            raise ValidationError(self.error_messages['max_digits'] % self.max_digits) -        if self.decimal_places is not None and decimals > self.decimal_places: -            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) -        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): -            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) -        return value +    def to_representation(self, value): +        return set([ +            self.choice_strings_to_values[six.text_type(item)] for item in value +        ]) -class FileField(WritableField): -    use_files = True -    type_name = 'FileField' -    type_label = 'file upload' -    form_field_class = forms.FileField -    widget = widgets.FileInput +# File types... +class FileField(Field):      default_error_messages = { -        'invalid': _("No file was submitted. Check the encoding type on the form."), -        'missing': _("No file was submitted."), -        'empty': _("The submitted file is empty."), -        'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), -        'contradiction': _('Please either submit a file or check the clear checkbox, not both.') +        'required': _('No file was submitted.'), +        'invalid': _('The submitted data was not a file. Check the encoding type on the form.'), +        'no_name': _('No filename could be determined.'), +        'empty': _('The submitted file is empty.'), +        'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),      } +    use_url = api_settings.UPLOADED_FILES_USE_URL      def __init__(self, *args, **kwargs):          self.max_length = kwargs.pop('max_length', None)          self.allow_empty_file = kwargs.pop('allow_empty_file', False) +        self.use_url = kwargs.pop('use_url', self.use_url)          super(FileField, self).__init__(*args, **kwargs) -    def from_native(self, data): -        if data in validators.EMPTY_VALUES: -            return None - -        # UploadedFile objects should have name and size attributes. +    def to_internal_value(self, data):          try: +            # `UploadedFile` objects should have name and size attributes.              file_name = data.name              file_size = data.size          except AttributeError: -            raise ValidationError(self.error_messages['invalid']) +            self.fail('invalid') -        if self.max_length is not None and len(file_name) > self.max_length: -            error_values = {'max': self.max_length, 'length': len(file_name)} -            raise ValidationError(self.error_messages['max_length'] % error_values)          if not file_name: -            raise ValidationError(self.error_messages['invalid']) +            self.fail('no_name')          if not self.allow_empty_file and not file_size: -            raise ValidationError(self.error_messages['empty']) +            self.fail('empty') +        if self.max_length and len(file_name) > self.max_length: +            self.fail('max_length', max_length=self.max_length, length=len(file_name))          return data -    def to_native(self, value): +    def to_representation(self, value): +        if self.use_url: +            if not value: +                return None +            url = value.url +            request = self.context.get('request', None) +            if request is not None: +                return request.build_absolute_uri(url) +            return url          return value.name  class ImageField(FileField): -    use_files = True -    type_name = 'ImageField' -    type_label = 'image upload' -    form_field_class = forms.ImageField +    default_error_messages = { +        'invalid_image': _( +            'Upload a valid image. The file you uploaded was either not an image or a corrupted image.' +        ), +    } + +    def __init__(self, *args, **kwargs): +        self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField) +        super(ImageField, self).__init__(*args, **kwargs) + +    def to_internal_value(self, data): +        # Image validation is a bit grungy, so we'll just outright +        # defer to Django's implementation so we don't need to +        # consider it, or treat PIL as a test dependency. +        file_object = super(ImageField, self).to_internal_value(data) +        django_field = self._DjangoImageField() +        django_field.error_messages = self.error_messages +        django_field.to_python(file_object) +        return file_object + +# Composite field types... + +class _UnvalidatedField(Field): +    def __init__(self, *args, **kwargs): +        super(_UnvalidatedField, self).__init__(*args, **kwargs) +        self.allow_blank = True +        self.allow_null = True + +    def to_internal_value(self, data): +        return data + +    def to_representation(self, value): +        return value + + +class ListField(Field): +    child = _UnvalidatedField() +    initial = []      default_error_messages = { -        'invalid_image': _("Upload a valid image. The file you uploaded was " -                           "either not an image or a corrupted image."), +        'not_a_list': _('Expected a list of items but got type "{input_type}".')      } -    def from_native(self, data): +    def __init__(self, *args, **kwargs): +        self.child = kwargs.pop('child', copy.deepcopy(self.child)) +        assert not inspect.isclass(self.child), '`child` has not been instantiated.' +        super(ListField, self).__init__(*args, **kwargs) +        self.child.bind(field_name='', parent=self) + +    def get_value(self, dictionary): +        # We override the default field access in order to support +        # lists in HTML forms. +        if html.is_html_input(dictionary): +            return html.parse_html_list(dictionary, prefix=self.field_name) +        return dictionary.get(self.field_name, empty) + +    def to_internal_value(self, data):          """ -        Checks that the file-upload field data contains a valid image (GIF, JPG, -        PNG, possibly others -- whatever the Python Imaging Library supports). +        List of dicts of native values <- List of dicts of primitive datatypes.          """ -        f = super(ImageField, self).from_native(data) -        if f is None: -            return None +        if html.is_html_input(data): +            data = html.parse_html_list(data) +        if isinstance(data, type('')) or not hasattr(data, '__iter__'): +            self.fail('not_a_list', input_type=type(data).__name__) +        return [self.child.run_validation(item) for item in data] + +    def to_representation(self, data): +        """ +        List of object instances -> List of dicts of primitive datatypes. +        """ +        return [self.child.to_representation(item) for item in data] -        from compat import Image -        assert Image is not None, 'PIL must be installed for ImageField support' -        # We need to get a file object for PIL. We might have a path or we might -        # have to read the data into memory. -        if hasattr(data, 'temporary_file_path'): -            file = data.temporary_file_path() -        else: -            if hasattr(data, 'read'): -                file = BytesIO(data.read()) -            else: -                file = BytesIO(data['content']) +class DictField(Field): +    child = _UnvalidatedField() +    initial = {} +    default_error_messages = { +        'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".') +    } -        try: -            # load() could spot a truncated JPEG, but it loads the entire -            # image in memory, which is a DoS vector. See #3848 and #18520. -            # verify() must be called immediately after the constructor. -            Image.open(file).verify() -        except ImportError: -            # Under PyPy, it is possible to import PIL. However, the underlying -            # _imaging C module isn't available, so an ImportError will be -            # raised. Catch and re-raise. -            raise -        except Exception:  # Python Imaging Library doesn't recognize it as an image -            raise ValidationError(self.error_messages['invalid_image']) -        if hasattr(f, 'seek') and callable(f.seek): -            f.seek(0) -        return f +    def __init__(self, *args, **kwargs): +        self.child = kwargs.pop('child', copy.deepcopy(self.child)) +        assert not inspect.isclass(self.child), '`child` has not been instantiated.' +        super(DictField, self).__init__(*args, **kwargs) +        self.child.bind(field_name='', parent=self) + +    def get_value(self, dictionary): +        # We override the default field access in order to support +        # dictionaries in HTML forms. +        if html.is_html_input(dictionary): +            return html.parse_html_dict(dictionary, prefix=self.field_name) +        return dictionary.get(self.field_name, empty) + +    def to_internal_value(self, data): +        """ +        Dicts of native values <- Dicts of primitive datatypes. +        """ +        if html.is_html_input(data): +            data = html.parse_html_dict(data) +        if not isinstance(data, dict): +            self.fail('not_a_dict', input_type=type(data).__name__) +        return dict([ +            (six.text_type(key), self.child.run_validation(value)) +            for key, value in data.items() +        ]) + +    def to_representation(self, value): +        """ +        List of object instances -> List of dicts of primitive datatypes. +        """ +        return dict([ +            (six.text_type(key), self.child.to_representation(val)) +            for key, val in value.items() +        ]) -class SerializerMethodField(Field): +# Miscellaneous field types... + +class ReadOnlyField(Field): +    """ +    A read-only field that simply returns the field value. + +    If the field is a method with no parameters, the method will be called +    and it's return value used as the representation. + +    For example, the following would call `get_expiry_date()` on the object: + +    class ExampleSerializer(self): +        expiry_date = ReadOnlyField(source='get_expiry_date') +    """ + +    def __init__(self, **kwargs): +        kwargs['read_only'] = True +        super(ReadOnlyField, self).__init__(**kwargs) + +    def to_representation(self, value): +        return value + + +class HiddenField(Field): +    """ +    A hidden field does not take input from the user, or present any output, +    but it does populate a field in `validated_data`, based on its default +    value. This is particularly useful when we have a `unique_for_date` +    constraint on a pair of fields, as we need some way to include the date in +    the validated data.      """ -    A field that gets its value by calling a method on the serializer it's attached to. +    def __init__(self, **kwargs): +        assert 'default' in kwargs, 'default is a required argument.' +        kwargs['write_only'] = True +        super(HiddenField, self).__init__(**kwargs) + +    def get_value(self, dictionary): +        # We always use the default value for `HiddenField`. +        # User input is never provided or accepted. +        return empty + +    def to_internal_value(self, data): +        return data + + +class SerializerMethodField(Field):      """ +    A read-only field that get its representation from calling a method on the +    parent serializer class. The method called will be of the form +    "get_{field_name}", and should take a single argument, which is the +    object being serialized. -    def __init__(self, method_name): +    For example: + +    class ExampleSerializer(self): +        extra_info = SerializerMethodField() + +        def get_extra_info(self, obj): +            return ...  # Calculate some data to return. +    """ +    def __init__(self, method_name=None, **kwargs):          self.method_name = method_name -        super(SerializerMethodField, self).__init__() +        kwargs['source'] = '*' +        kwargs['read_only'] = True +        super(SerializerMethodField, self).__init__(**kwargs) + +    def bind(self, field_name, parent): +        # In order to enforce a consistent style, we error if a redundant +        # 'method_name' argument has been used. For example: +        # my_field = serializer.CharField(source='my_field') +        default_method_name = 'get_{field_name}'.format(field_name=field_name) +        assert self.method_name != default_method_name, ( +            "It is redundant to specify `%s` on SerializerMethodField '%s' in " +            "serializer '%s', because it is the same as the default method name. " +            "Remove the `method_name` argument." % +            (self.method_name, field_name, parent.__class__.__name__) +        ) + +        # The method name should default to `get_{field_name}`. +        if self.method_name is None: +            self.method_name = default_method_name + +        super(SerializerMethodField, self).bind(field_name, parent) + +    def to_representation(self, value): +        method = getattr(self.parent, self.method_name) +        return method(value) + + +class ModelField(Field): +    """ +    A generic field that can be used against an arbitrary model field. -    def field_to_native(self, obj, field_name): -        value = getattr(self.parent, self.method_name)(obj) -        return self.to_native(value) +    This is used by `ModelSerializer` when dealing with custom model fields, +    that do not have a serializer field to be mapped to. +    """ +    default_error_messages = { +        'max_length': _('Ensure this field has no more than {max_length} characters.'), +    } + +    def __init__(self, model_field, **kwargs): +        self.model_field = model_field +        # The `max_length` option is supported by Django's base `Field` class, +        # so we'd better support it here. +        max_length = kwargs.pop('max_length', None) +        super(ModelField, self).__init__(**kwargs) +        if max_length is not None: +            message = self.error_messages['max_length'].format(max_length=max_length) +            self.validators.append(MaxLengthValidator(max_length, message=message)) + +    def to_internal_value(self, data): +        rel = getattr(self.model_field, 'rel', None) +        if rel is not None: +            return rel.to._meta.get_field(rel.field_name).to_python(data) +        return self.model_field.to_python(data) + +    def get_attribute(self, obj): +        # We pass the object instance onto `to_representation`, +        # not just the field attribute. +        return obj + +    def to_representation(self, obj): +        value = self.model_field._get_val_from_obj(obj) +        if is_protected_type(value): +            return value +        return self.model_field.value_to_string(obj) | 
