diff options
| author | James Rutherford | 2015-03-11 10:38:03 +0000 | 
|---|---|---|
| committer | James Rutherford | 2015-03-11 10:38:03 +0000 | 
| commit | 4a2d27975ab5249269aebafd803be87a2107092b (patch) | |
| tree | 55b524c93b02eef404304f734be98871bbb1324f /rest_framework/serializers.py | |
| parent | 856dc855c952746f566a6a8de263afe951362dfb (diff) | |
| parent | dc56e5a0f41fdd6350e91a5749023d086bd1640f (diff) | |
| download | django-rest-framework-4a2d27975ab5249269aebafd803be87a2107092b.tar.bz2 | |
Merge pull request #1 from tomchristie/master
Merge in from upstream
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 1896 | 
1 files changed, 1160 insertions, 736 deletions
| diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 682a99a4..2eef6eeb 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -6,20 +6,31 @@ form encoded input.  Serialization in REST framework is a two-phase process:  1. Serializers marshal between complex types like model instances, and -python primatives. -2. The process of marshalling between python primatives and request and +python primitives. +2. The process of marshalling between python primitives and request and  response content is handled by parsers and renderers.  """  from __future__ import unicode_literals -import copy -import datetime -import types -from decimal import Decimal -from django.core.paginator import Page  from django.db import models -from django.forms import widgets -from django.utils.datastructures import SortedDict -from rest_framework.compat import get_concrete_model, six +from django.db.models.fields import FieldDoesNotExist, Field as DjangoModelField +from django.db.models import query +from django.utils.translation import ugettext_lazy as _ +from rest_framework.compat import postgres_fields, unicode_to_repr +from rest_framework.utils import model_meta +from rest_framework.utils.field_mapping import ( +    get_url_kwargs, get_field_kwargs, +    get_relation_kwargs, get_nested_relation_kwargs, +    ClassLookupDict +) +from rest_framework.utils.serializer_helpers import ( +    ReturnDict, ReturnList, BoundField, NestedBoundField, BindingDict +) +from rest_framework.validators import ( +    UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator, +    UniqueTogetherValidator +) +import warnings +  # Note: We do the following so that users of the framework can use this style:  # @@ -28,941 +39,1354 @@ from rest_framework.compat import get_concrete_model, six  # This helps keep the separation between model fields, form fields, and  # serializer fields more explicit. -from rest_framework.relations import * -from rest_framework.fields import * +from rest_framework.relations import *  # NOQA +from rest_framework.fields import *  # NOQA -class NestedValidationError(ValidationError): -    """ -    The default ValidationError behavior is to stringify each item in the list -    if the messages are a list of error messages. +# We assume that 'validators' are intended for the child serializer, +# rather than the parent serializer. +LIST_SERIALIZER_KWARGS = ( +    'read_only', 'write_only', 'required', 'default', 'initial', 'source', +    'label', 'help_text', 'style', 'error_messages', +    'instance', 'data', 'partial', 'context' +) + -    In the case of nested serializers, where the parent has many children, -    then the child's `serializer.errors` will be a list of dicts.  In the case -    of a single child, the `serializer.errors` will be a dict. +# BaseSerializer +# -------------- -    We need to override the default behavior to get properly nested error dicts. +class BaseSerializer(Field):      """ +    The BaseSerializer class provides a minimal class which may be used +    for writing custom serializer implementations. -    def __init__(self, message): -        if isinstance(message, dict): -            self.messages = [message] -        else: -            self.messages = message +    Note that we strongly restrict the ordering of operations/properties +    that may be used on the serializer in order to enforce correct usage. +    In particular, if a `data=` argument is passed then: -class DictWithMetadata(dict): -    """ -    A dict-like object, that can have additional properties attached. -    """ -    def __getstate__(self): -        """ -        Used by pickle (e.g., caching). -        Overridden to remove the metadata from the dict, since it shouldn't be -        pickled and may in some instances be unpickleable. -        """ -        return dict(self) +    .is_valid() - Available. +    .initial_data - Available. +    .validated_data - Only available after calling `is_valid()` +    .errors - Only available after calling `is_valid()` +    .data - Only available after calling `is_valid()` +    If a `data=` argument is not passed then: -class SortedDictWithMetadata(SortedDict): +    .is_valid() - Not available. +    .initial_data - Not available. +    .validated_data - Not available. +    .errors - Not available. +    .data - Available.      """ -    A sorted dict-like object, that can have additional properties attached. -    """ -    def __getstate__(self): + +    def __init__(self, instance=None, data=empty, **kwargs): +        self.instance = instance +        if data is not empty: +            self.initial_data = data +        self.partial = kwargs.pop('partial', False) +        self._context = kwargs.pop('context', {}) +        kwargs.pop('many', None) +        super(BaseSerializer, self).__init__(**kwargs) + +    def __new__(cls, *args, **kwargs): +        # We override this method in order to automagically create +        # `ListSerializer` classes instead when `many=True` is set. +        if kwargs.pop('many', False): +            return cls.many_init(*args, **kwargs) +        return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) + +    @classmethod +    def many_init(cls, *args, **kwargs):          """ -        Used by pickle (e.g., caching). -        Overriden to remove the metadata from the dict, since it shouldn't be -        pickle and may in some instances be unpickleable. +        This method implements the creation of a `ListSerializer` parent +        class when `many=True` is used. You can customize it if you need to +        control which keyword arguments are passed to the parent, and +        which are passed to the child. + +        Note that we're over-cautious in passing most arguments to both parent +        and child classes in order to try to cover the general case. If you're +        overriding this method you'll probably want something much simpler, eg: + +        @classmethod +        def many_init(cls, *args, **kwargs): +            kwargs['child'] = cls() +            return CustomListSerializer(*args, **kwargs)          """ -        return SortedDict(self).__dict__ +        child_serializer = cls(*args, **kwargs) +        list_kwargs = {'child': child_serializer} +        list_kwargs.update(dict([ +            (key, value) for key, value in kwargs.items() +            if key in LIST_SERIALIZER_KWARGS +        ])) +        meta = getattr(cls, 'Meta', None) +        list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer) +        return list_serializer_class(*args, **list_kwargs) +    def to_internal_value(self, data): +        raise NotImplementedError('`to_internal_value()` must be implemented.') -def _is_protected_type(obj): -    """ -    True if the object is a native datatype that does not need to -    be serialized further. -    """ -    return isinstance(obj, ( -        types.NoneType, -        int, long, -        datetime.datetime, datetime.date, datetime.time, -        float, Decimal, -        basestring) -    ) +    def to_representation(self, instance): +        raise NotImplementedError('`to_representation()` must be implemented.') +    def update(self, instance, validated_data): +        raise NotImplementedError('`update()` must be implemented.') -def _get_declared_fields(bases, attrs): -    """ -    Create a list of serializer field instances from the passed in 'attrs', -    plus any fields on the base classes (in 'bases'). +    def create(self, validated_data): +        raise NotImplementedError('`create()` must be implemented.') -    Note that all fields from the base classes are used. -    """ -    fields = [(field_name, attrs.pop(field_name)) -              for field_name, obj in list(six.iteritems(attrs)) -              if isinstance(obj, Field)] -    fields.sort(key=lambda x: x[1].creation_counter) +    def save(self, **kwargs): +        assert not hasattr(self, 'save_object'), ( +            'Serializer `%s.%s` has old-style version 2 `.save_object()` ' +            'that is no longer compatible with REST framework 3. ' +            'Use the new-style `.create()` and `.update()` methods instead.' % +            (self.__class__.__module__, self.__class__.__name__) +        ) -    # If this class is subclassing another Serializer, add that Serializer's -    # fields.  Note that we loop over the bases in *reverse*. This is necessary -    # in order to maintain the correct order of fields. -    for base in bases[::-1]: -        if hasattr(base, 'base_fields'): -            fields = list(base.base_fields.items()) + fields +        assert hasattr(self, '_errors'), ( +            'You must call `.is_valid()` before calling `.save()`.' +        ) -    return SortedDict(fields) +        assert not self.errors, ( +            'You cannot call `.save()` on a serializer with invalid data.' +        ) +        validated_data = dict( +            list(self.validated_data.items()) + +            list(kwargs.items()) +        ) -class SerializerMetaclass(type): -    def __new__(cls, name, bases, attrs): -        attrs['base_fields'] = _get_declared_fields(bases, attrs) -        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) +        if self.instance is not None: +            self.instance = self.update(self.instance, validated_data) +            assert self.instance is not None, ( +                '`update()` did not return an object instance.' +            ) +        else: +            self.instance = self.create(validated_data) +            assert self.instance is not None, ( +                '`create()` did not return an object instance.' +            ) +        return self.instance -class SerializerOptions(object): -    """ -    Meta class options for Serializer -    """ -    def __init__(self, meta): -        self.depth = getattr(meta, 'depth', 0) -        self.fields = getattr(meta, 'fields', ()) -        self.exclude = getattr(meta, 'exclude', ()) +    def is_valid(self, raise_exception=False): +        assert not hasattr(self, 'restore_object'), ( +            'Serializer `%s.%s` has old-style version 2 `.restore_object()` ' +            'that is no longer compatible with REST framework 3. ' +            'Use the new-style `.create()` and `.update()` methods instead.' % +            (self.__class__.__module__, self.__class__.__name__) +        ) +        assert hasattr(self, 'initial_data'), ( +            'Cannot call `.is_valid()` as no `data=` keyword argument was ' +            'passed when instantiating the serializer instance.' +        ) -class BaseSerializer(WritableField): -    """ -    This is the Serializer implementation. -    We need to implement it as `BaseSerializer` due to metaclass magicks. -    """ -    class Meta(object): -        pass +        if not hasattr(self, '_validated_data'): +            try: +                self._validated_data = self.run_validation(self.initial_data) +            except ValidationError as exc: +                self._validated_data = {} +                self._errors = exc.detail +            else: +                self._errors = {} -    _options_class = SerializerOptions -    _dict_class = SortedDictWithMetadata +        if self._errors and raise_exception: +            raise ValidationError(self._errors) -    def __init__(self, instance=None, data=None, files=None, -                 context=None, partial=False, many=None, -                 allow_add_remove=False, **kwargs): -        super(BaseSerializer, self).__init__(**kwargs) -        self.opts = self._options_class(self.Meta) -        self.parent = None -        self.root = None -        self.partial = partial -        self.many = many -        self.allow_add_remove = allow_add_remove +        return not bool(self._errors) -        self.context = context or {} +    @property +    def data(self): +        if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'): +            msg = ( +                'When a serializer is passed a `data` keyword argument you ' +                'must call `.is_valid()` before attempting to access the ' +                'serialized `.data` representation.\n' +                'You should either call `.is_valid()` first, ' +                'or access `.initial_data` instead.' +            ) +            raise AssertionError(msg) -        self.init_data = data -        self.init_files = files -        self.object = instance -        self.fields = self.get_fields() +        if not hasattr(self, '_data'): +            if self.instance is not None and not getattr(self, '_errors', None): +                self._data = self.to_representation(self.instance) +            elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None): +                self._data = self.to_representation(self.validated_data) +            else: +                self._data = self.get_initial() +        return self._data -        self._data = None -        self._files = None -        self._errors = None -        self._deleted = None +    @property +    def errors(self): +        if not hasattr(self, '_errors'): +            msg = 'You must call `.is_valid()` before accessing `.errors`.' +            raise AssertionError(msg) +        return self._errors -        if many and instance is not None and not hasattr(instance, '__iter__'): -            raise ValueError('instance should be a queryset or other iterable with many=True') +    @property +    def validated_data(self): +        if not hasattr(self, '_validated_data'): +            msg = 'You must call `.is_valid()` before accessing `.validated_data`.' +            raise AssertionError(msg) +        return self._validated_data -        if allow_add_remove and not many: -            raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') -    ##### -    # Methods to determine which fields to use when (de)serializing objects. +# Serializer & ListSerializer classes +# ----------------------------------- -    def get_default_fields(self): -        """ -        Return the complete set of default fields for the object, as a dict. -        """ -        return {} +class SerializerMetaclass(type): +    """ +    This metaclass sets a dictionary named `_declared_fields` on the class. -    def get_fields(self): -        """ -        Returns the complete set of fields for the object as a dict. +    Any instances of `Field` included as attributes on either the class +    or on any of its superclasses will be include in the +    `_declared_fields` dictionary. +    """ -        This will be the set of any explicitly declared fields, -        plus the set of fields returned by get_default_fields(). -        """ -        ret = SortedDict() +    @classmethod +    def _get_declared_fields(cls, bases, attrs): +        fields = [(field_name, attrs.pop(field_name)) +                  for field_name, obj in list(attrs.items()) +                  if isinstance(obj, Field)] +        fields.sort(key=lambda x: x[1]._creation_counter) -        # Get the explicitly declared fields -        base_fields = copy.deepcopy(self.base_fields) -        for key, field in base_fields.items(): -            ret[key] = field +        # If this class is subclassing another Serializer, add that Serializer's +        # fields.  Note that we loop over the bases in *reverse*. This is necessary +        # in order to maintain the correct order of fields. +        for base in reversed(bases): +            if hasattr(base, '_declared_fields'): +                fields = list(base._declared_fields.items()) + fields -        # Add in the default fields -        default_fields = self.get_default_fields() -        for key, val in default_fields.items(): -            if key not in ret: -                ret[key] = val +        return OrderedDict(fields) -        # If 'fields' is specified, use those fields, in that order. -        if self.opts.fields: -            assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' -            new = SortedDict() -            for key in self.opts.fields: -                new[key] = ret[key] -            ret = new +    def __new__(cls, name, bases, attrs): +        attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs) +        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) -        # Remove anything in 'exclude' -        if self.opts.exclude: -            assert isinstance(self.opts.exclude, (list, tuple)), '`exclude` must be a list or tuple' -            for key in self.opts.exclude: -                ret.pop(key, None) -        for key, field in ret.items(): -            field.initialize(parent=self, field_name=key) +def get_validation_error_detail(exc): +    assert isinstance(exc, (ValidationError, DjangoValidationError)) -        return ret +    if isinstance(exc, DjangoValidationError): +        # Normally you should raise `serializers.ValidationError` +        # inside your codebase, but we handle Django's validation +        # exception class as well for simpler compat. +        # Eg. Calling Model.clean() explicitly inside Serializer.validate() +        return { +            api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages) +        } +    elif isinstance(exc.detail, dict): +        # If errors may be a dict we use the standard {key: list of values}. +        # Here we ensure that all the values are *lists* of errors. +        return dict([ +            (key, value if isinstance(value, list) else [value]) +            for key, value in exc.detail.items() +        ]) +    elif isinstance(exc.detail, list): +        # Errors raised as a list are non-field errors. +        return { +            api_settings.NON_FIELD_ERRORS_KEY: exc.detail +        } +    # Errors raised as a string are non-field errors. +    return { +        api_settings.NON_FIELD_ERRORS_KEY: [exc.detail] +    } -    ##### -    # Methods to convert or revert from objects <--> primitive representations. -    def get_field_key(self, field_name): +@six.add_metaclass(SerializerMetaclass) +class Serializer(BaseSerializer): +    default_error_messages = { +        'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') +    } + +    @property +    def fields(self):          """ -        Return the key that should be used for a given field. +        A dictionary of {field_name: field_instance}.          """ -        return field_name +        # `fields` is evaluated lazily. We do this to ensure that we don't +        # have issues importing modules that use ModelSerializers as fields, +        # even if Django's app-loading stage has not yet run. +        if not hasattr(self, '_fields'): +            self._fields = BindingDict(self) +            for key, value in self.get_fields().items(): +                self._fields[key] = value +        return self._fields -    def restore_fields(self, data, files): +    def get_fields(self):          """ -        Core of deserialization, together with `restore_object`. -        Converts a dictionary of data into a dictionary of deserialized fields. +        Returns a dictionary of {field_name: field_instance}.          """ -        reverted_data = {} +        # Every new serializer is created with a clone of the field instances. +        # This allows users to dynamically modify the fields on a serializer +        # instance without affecting every other serializer class. +        return copy.deepcopy(self._declared_fields) -        if data is not None and not isinstance(data, dict): -            self._errors['non_field_errors'] = ['Invalid data'] -            return None +    def get_validators(self): +        """ +        Returns a list of validator callables. +        """ +        # Used by the lazily-evaluated `validators` property. +        meta = getattr(self, 'Meta', None) +        validators = getattr(meta, 'validators', None) +        return validators[:] if validators else [] + +    def get_initial(self): +        if hasattr(self, 'initial_data'): +            return OrderedDict([ +                (field_name, field.get_value(self.initial_data)) +                for field_name, field in self.fields.items() +                if (field.get_value(self.initial_data) is not empty) and +                not field.read_only +            ]) + +        return OrderedDict([ +            (field.field_name, field.get_initial()) +            for field in self.fields.values() +            if not field.read_only +        ]) + +    def get_value(self, dictionary): +        # We override the default field access in order to support +        # nested 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 run_validation(self, data=empty): +        """ +        We override the default `run_validation`, because the validation +        performed by validators and the `.validate()` method should +        be coerced into an error dictionary with a 'non_fields_error' key. +        """ +        (is_empty_value, data) = self.validate_empty_values(data) +        if is_empty_value: +            return data -        for field_name, field in self.fields.items(): -            field.initialize(parent=self, field_name=field_name) -            try: -                field.field_from_native(data, files, field_name, reverted_data) -            except ValidationError as err: -                self._errors[field_name] = list(err.messages) +        value = self.to_internal_value(data) +        try: +            self.run_validators(value) +            value = self.validate(value) +            assert value is not None, '.validate() should return the validated data' +        except (ValidationError, DjangoValidationError) as exc: +            raise ValidationError(detail=get_validation_error_detail(exc)) -        return reverted_data +        return value -    def perform_validation(self, attrs): +    def to_internal_value(self, data):          """ -        Run `validate_<fieldname>()` and `validate()` methods on the serializer +        Dict of native values <- Dict of primitive datatypes.          """ -        for field_name, field in self.fields.items(): -            if field_name in self._errors: -                continue -            try: -                validate_method = getattr(self, 'validate_%s' % field_name, None) -                if validate_method: -                    source = field.source or field_name -                    attrs = validate_method(attrs, source) -            except ValidationError as err: -                self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) - -        # If there are already errors, we don't run .validate() because -        # field-validation failed and thus `attrs` may not be complete. -        # which in turn can cause inconsistent validation errors. -        if not self._errors: +        if not isinstance(data, dict): +            message = self.error_messages['invalid'].format( +                datatype=type(data).__name__ +            ) +            raise ValidationError({ +                api_settings.NON_FIELD_ERRORS_KEY: [message] +            }) + +        ret = OrderedDict() +        errors = OrderedDict() +        fields = [ +            field for field in self.fields.values() +            if (not field.read_only) or (field.default is not empty) +        ] + +        for field in fields: +            validate_method = getattr(self, 'validate_' + field.field_name, None) +            primitive_value = field.get_value(data)              try: -                attrs = self.validate(attrs) -            except ValidationError as err: -                if hasattr(err, 'message_dict'): -                    for field_name, error_messages in err.message_dict.items(): -                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) -                elif hasattr(err, 'messages'): -                    self._errors['non_field_errors'] = err.messages +                validated_value = field.run_validation(primitive_value) +                if validate_method is not None: +                    validated_value = validate_method(validated_value) +            except ValidationError as exc: +                errors[field.field_name] = exc.detail +            except DjangoValidationError as exc: +                errors[field.field_name] = list(exc.messages) +            except SkipField: +                pass +            else: +                set_value(ret, field.source_attrs, validated_value) -        return attrs +        if errors: +            raise ValidationError(errors) -    def validate(self, attrs): -        """ -        Stub method, to be overridden in Serializer subclasses -        """ -        return attrs +        return ret -    def restore_object(self, attrs, instance=None): +    def to_representation(self, instance):          """ -        Deserialize a dictionary of attributes into an object instance. -        You should override this method to control how deserialized objects -        are instantiated. +        Object instance -> Dict of primitive datatypes.          """ -        if instance is not None: -            instance.update(attrs) -            return instance -        return attrs +        ret = OrderedDict() +        fields = [field for field in self.fields.values() if not field.write_only] -    def to_native(self, obj): -        """ -        Serialize objects -> primitives. -        """ -        ret = self._dict_class() -        ret.fields = {} +        for field in fields: +            try: +                attribute = field.get_attribute(instance) +            except SkipField: +                continue + +            if attribute is None: +                # We skip `to_representation` for `None` values so that +                # fields do not have to explicitly deal with that case. +                ret[field.field_name] = None +            else: +                ret[field.field_name] = field.to_representation(attribute) -        for field_name, field in self.fields.items(): -            field.initialize(parent=self, field_name=field_name) -            key = self.get_field_key(field_name) -            value = field.field_to_native(obj, field_name) -            ret[key] = value -            ret.fields[key] = field          return ret -    def from_native(self, data, files): -        """ -        Deserialize primitives -> objects. -        """ -        self._errors = {} -        if data is not None or files is not None: -            attrs = self.restore_fields(data, files) -            if attrs is not None: -                attrs = self.perform_validation(attrs) -        else: -            self._errors['non_field_errors'] = ['No input provided'] +    def validate(self, attrs): +        return attrs -        if not self._errors: -            return self.restore_object(attrs, instance=getattr(self, 'object', None)) +    def __repr__(self): +        return unicode_to_repr(representation.serializer_repr(self, indent=1)) -    def field_to_native(self, obj, field_name): -        """ -        Override default so that the serializer can be used as a nested field -        across relationships. -        """ -        if self.source == '*': -            return self.to_native(obj) +    # The following are used for accessing `BoundField` instances on the +    # serializer, for the purposes of presenting a form-like API onto the +    # field values and field errors. -        try: -            source = self.source or field_name -            value = obj +    def __iter__(self): +        for field in self.fields.values(): +            yield self[field.field_name] -            for component in source.split('.'): -                value = get_component(value, component) -                if value is None: -                    break -        except ObjectDoesNotExist: -            return None +    def __getitem__(self, key): +        field = self.fields[key] +        value = self.data.get(key) +        error = self.errors.get(key) if hasattr(self, '_errors') else None +        if isinstance(field, Serializer): +            return NestedBoundField(field, value, error) +        return BoundField(field, value, error) -        if is_simple_callable(getattr(value, 'all', None)): -            return [self.to_native(item) for item in value.all()] +    # Include a backlink to the serializer class on return objects. +    # Allows renderers such as HTMLFormRenderer to get the full field info. -        if value is None: -            return None +    @property +    def data(self): +        ret = super(Serializer, self).data +        return ReturnDict(ret, serializer=self) -        if self.many is not None: -            many = self.many -        else: -            many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) +    @property +    def errors(self): +        ret = super(Serializer, self).errors +        return ReturnDict(ret, serializer=self) -        if many: -            return [self.to_native(item) for item in value] -        return self.to_native(value) -    def field_from_native(self, data, files, field_name, into): -        """ -        Override default so that the serializer can be used as a writable -        nested field across relationships. -        """ -        if self.read_only: -            return +# There's some replication of `ListField` here, +# but that's probably better than obfuscating the call hierarchy. -        try: -            value = data[field_name] -        except KeyError: -            if self.default is not None and not self.partial: -                # Note: partial updates shouldn't set defaults -                value = copy.deepcopy(self.default) -            else: -                if self.required: -                    raise ValidationError(self.error_messages['required']) -                return +class ListSerializer(BaseSerializer): +    child = None +    many = True -        # Set the serializer object if it exists -        obj = getattr(self.parent.object, field_name) if self.parent.object else None +    default_error_messages = { +        'not_a_list': _('Expected a list of items but got type "{input_type}".') +    } -        if self.source == '*': -            if value: -                into.update(value) -        else: -            if value in (None, ''): -                into[(self.source or field_name)] = None -            else: -                kwargs = { -                    'instance': obj, -                    'data': value, -                    'context': self.context, -                    'partial': self.partial, -                    'many': self.many -                } -                serializer = self.__class__(**kwargs) - -                if serializer.is_valid(): -                    into[self.source or field_name] = serializer.object -                else: -                    # Propagate errors up to our parent -                    raise NestedValidationError(serializer.errors) +    def __init__(self, *args, **kwargs): +        self.child = kwargs.pop('child', copy.deepcopy(self.child)) +        assert self.child is not None, '`child` is a required argument.' +        assert not inspect.isclass(self.child), '`child` has not been instantiated.' +        super(ListSerializer, self).__init__(*args, **kwargs) +        self.child.bind(field_name='', parent=self) + +    def get_initial(self): +        if hasattr(self, 'initial_data'): +            return self.to_representation(self.initial_data) +        return [] -    def get_identity(self, data): +    def get_value(self, dictionary):          """ -        This hook is required for bulk update. -        It is used to determine the canonical identity of a given object. +        Given the input dictionary, return the field value. +        """ +        # 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) -        Note that the data has not been validated at this point, so we need -        to make sure that we catch any cases of incorrect datatypes being -        passed to this method. +    def run_validation(self, data=empty): +        """ +        We override the default `run_validation`, because the validation +        performed by validators and the `.validate()` method should +        be coerced into an error dictionary with a 'non_fields_error' key.          """ +        (is_empty_value, data) = self.validate_empty_values(data) +        if is_empty_value: +            return data + +        value = self.to_internal_value(data)          try: -            return data.get('id', None) -        except AttributeError: -            return None +            self.run_validators(value) +            value = self.validate(value) +            assert value is not None, '.validate() should return the validated data' +        except (ValidationError, DjangoValidationError) as exc: +            raise ValidationError(detail=get_validation_error_detail(exc)) -    @property -    def errors(self): +        return value + +    def to_internal_value(self, data):          """ -        Run deserialization and return error data, -        setting self.object if no errors occurred. +        List of dicts of native values <- List of dicts of primitive datatypes.          """ -        if self._errors is None: -            data, files = self.init_data, self.init_files +        if html.is_html_input(data): +            data = html.parse_html_list(data) -            if self.many is not None: -                many = self.many -            else: -                many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type)) -                if many: -                    warnings.warn('Implict list/queryset serialization is deprecated. ' -                                  'Use the `many=True` flag when instantiating the serializer.', -                                  DeprecationWarning, stacklevel=3) - -            if many: -                ret = [] -                errors = [] -                update = self.object is not None - -                if update: -                    # If this is a bulk update we need to map all the objects -                    # to a canonical identity so we can determine which -                    # individual object is being updated for each item in the -                    # incoming data -                    objects = self.object -                    identities = [self.get_identity(self.to_native(obj)) for obj in objects] -                    identity_to_objects = dict(zip(identities, objects)) - -                if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)): -                    for item in data: -                        if update: -                            # Determine which object we're updating -                            identity = self.get_identity(item) -                            self.object = identity_to_objects.pop(identity, None) -                            if self.object is None and not self.allow_add_remove: -                                ret.append(None) -                                errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) -                                continue - -                        ret.append(self.from_native(item, None)) -                        errors.append(self._errors) - -                    if update: -                        self._deleted = identity_to_objects.values() - -                    self._errors = any(errors) and errors or [] -                else: -                    self._errors = {'non_field_errors': ['Expected a list of items.']} -            else: -                ret = self.from_native(data, files) +        if not isinstance(data, list): +            message = self.error_messages['not_a_list'].format( +                input_type=type(data).__name__ +            ) +            raise ValidationError({ +                api_settings.NON_FIELD_ERRORS_KEY: [message] +            }) -            if not self._errors: -                self.object = ret +        ret = [] +        errors = [] -        return self._errors +        for item in data: +            try: +                validated = self.child.run_validation(item) +            except ValidationError as exc: +                errors.append(exc.detail) +            else: +                ret.append(validated) +                errors.append({}) -    def is_valid(self): -        return not self.errors +        if any(errors): +            raise ValidationError(errors) -    @property -    def data(self): +        return ret + +    def to_representation(self, data):          """ -        Returns the serialized data on the serializer. +        List of object instances -> List of dicts of primitive datatypes.          """ -        if self._data is None: -            obj = self.object +        # Dealing with nested relationships, data can be a Manager, +        # so, first get a queryset from the Manager if needed +        iterable = data.all() if isinstance(data, (models.Manager, query.QuerySet)) else data +        return [ +            self.child.to_representation(item) for item in iterable +        ] -            if self.many is not None: -                many = self.many -            else: -                many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) -                if many: -                    warnings.warn('Implict list/queryset serialization is deprecated. ' -                                  'Use the `many=True` flag when instantiating the serializer.', -                                  DeprecationWarning, stacklevel=2) - -            if many: -                self._data = [self.to_native(item) for item in obj] -            else: -                self._data = self.to_native(obj) - -        return self._data +    def validate(self, attrs): +        return attrs -    def save_object(self, obj, **kwargs): -        obj.save(**kwargs) +    def update(self, instance, validated_data): +        raise NotImplementedError( +            "Serializers with many=True do not support multiple update by " +            "default, only multiple create. For updates it is unclear how to " +            "deal with insertions and deletions. If you need to support " +            "multiple update, use a `ListSerializer` class and override " +            "`.update()` so you can specify the behavior exactly." +        ) -    def delete_object(self, obj): -        obj.delete() +    def create(self, validated_data): +        return [ +            self.child.create(attrs) for attrs in validated_data +        ]      def save(self, **kwargs):          """ -        Save the deserialized object and return it. +        Save and return a list of object instances.          """ -        if isinstance(self.object, list): -            [self.save_object(item, **kwargs) for item in self.object] +        validated_data = [ +            dict(list(attrs.items()) + list(kwargs.items())) +            for attrs in self.validated_data +        ] + +        if self.instance is not None: +            self.instance = self.update(self.instance, validated_data) +            assert self.instance is not None, ( +                '`update()` did not return an object instance.' +            )          else: -            self.save_object(self.object, **kwargs) +            self.instance = self.create(validated_data) +            assert self.instance is not None, ( +                '`create()` did not return an object instance.' +            ) -        if self.allow_add_remove and self._deleted: -            [self.delete_object(item) for item in self._deleted] +        return self.instance -        return self.object +    def __repr__(self): +        return unicode_to_repr(representation.list_repr(self, indent=1)) -    def metadata(self): -        """ -        Return a dictionary of metadata about the fields on the serializer. -        Useful for things like responding to OPTIONS requests, or generating -        API schemas for auto-documentation. -        """ -        return SortedDict( -            [(field_name, field.metadata()) -            for field_name, field in six.iteritems(self.fields)] -        ) +    # Include a backlink to the serializer class on return objects. +    # Allows renderers such as HTMLFormRenderer to get the full field info. + +    @property +    def data(self): +        ret = super(ListSerializer, self).data +        return ReturnList(ret, serializer=self) +    @property +    def errors(self): +        ret = super(ListSerializer, self).errors +        if isinstance(ret, dict): +            return ReturnDict(ret, serializer=self) +        return ReturnList(ret, serializer=self) -class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)): -    pass +# ModelSerializer & HyperlinkedModelSerializer +# -------------------------------------------- -class ModelSerializerOptions(SerializerOptions): +def raise_errors_on_nested_writes(method_name, serializer, validated_data):      """ -    Meta class options for ModelSerializer +    Give explicit errors when users attempt to pass writable nested data. + +    If we don't do this explicitly they'd get a less helpful error when +    calling `.save()` on the serializer. + +    We don't *automatically* support these sorts of nested writes because +    there are too many ambiguities to define a default behavior. + +    Eg. Suppose we have a `UserSerializer` with a nested profile. How should +    we handle the case of an update, where the `profile` relationship does +    not exist? Any of the following might be valid: + +    * Raise an application error. +    * Silently ignore the nested part of the update. +    * Automatically create a profile instance.      """ -    def __init__(self, meta): -        super(ModelSerializerOptions, self).__init__(meta) -        self.model = getattr(meta, 'model', None) -        self.read_only_fields = getattr(meta, 'read_only_fields', ()) + +    # Ensure we don't have a writable nested field. For example: +    # +    # class UserSerializer(ModelSerializer): +    #     ... +    #     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' +        'fields by default.\nWrite an explicit `.{method_name}()` method for ' +        'serializer `{module}.{class_name}`, or set `read_only=True` on ' +        'nested serializer fields.'.format( +            method_name=method_name, +            module=serializer.__class__.__module__, +            class_name=serializer.__class__.__name__ +        ) +    ) + +    # Ensure we don't have a writable dotted-source field. For example: +    # +    # class UserSerializer(ModelSerializer): +    #     ... +    #     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 ' +        'fields by default.\nWrite an explicit `.{method_name}()` method for ' +        'serializer `{module}.{class_name}`, or set `read_only=True` on ' +        'dotted-source serializer fields.'.format( +            method_name=method_name, +            module=serializer.__class__.__module__, +            class_name=serializer.__class__.__name__ +        ) +    )  class ModelSerializer(Serializer):      """ -    A serializer that deals with model instances and querysets. -    """ -    _options_class = ModelSerializerOptions +    A `ModelSerializer` is just a regular `Serializer`, except that: + +    * A set of default fields are automatically populated. +    * A set of default validators are automatically populated. +    * Default `.create()` and `.update()` implementations are provided. + +    The process of automatically determining a set of serializer fields +    based on the model fields is reasonably complex, but you almost certainly +    don't need to dig into the implementation. -    field_mapping = { +    If the `ModelSerializer` class *doesn't* generate the set of fields that +    you need you should either declare the extra/differing fields explicitly on +    the serializer class, or simply use a `Serializer` class. +    """ +    serializer_field_mapping = {          models.AutoField: IntegerField, +        models.BigIntegerField: IntegerField, +        models.BooleanField: BooleanField, +        models.CharField: CharField, +        models.CommaSeparatedIntegerField: CharField, +        models.DateField: DateField, +        models.DateTimeField: DateTimeField, +        models.DecimalField: DecimalField, +        models.EmailField: EmailField, +        models.Field: ModelField, +        models.FileField: FileField,          models.FloatField: FloatField, +        models.ImageField: ImageField,          models.IntegerField: IntegerField, +        models.NullBooleanField: NullBooleanField,          models.PositiveIntegerField: IntegerField, -        models.SmallIntegerField: IntegerField,          models.PositiveSmallIntegerField: IntegerField, -        models.DateTimeField: DateTimeField, -        models.DateField: DateField, -        models.TimeField: TimeField, -        models.DecimalField: DecimalField, -        models.EmailField: EmailField, -        models.CharField: CharField, -        models.URLField: URLField,          models.SlugField: SlugField, +        models.SmallIntegerField: IntegerField,          models.TextField: CharField, -        models.CommaSeparatedIntegerField: CharField, -        models.BooleanField: BooleanField, -        models.FileField: FileField, -        models.ImageField: ImageField, +        models.TimeField: TimeField, +        models.URLField: URLField,      } +    serializer_related_field = PrimaryKeyRelatedField +    serializer_url_field = HyperlinkedIdentityField +    serializer_choice_field = ChoiceField + +    # Default `create` and `update` behavior... -    def get_default_fields(self): +    def create(self, validated_data):          """ -        Return all the fields that should be serialized for the model. +        We have a bit of extra checking around this in order to provide +        descriptive messages when something goes wrong, but this method is +        essentially just: + +            return ExampleModel.objects.create(**validated_data) + +        If there are many to many fields present on the instance then they +        cannot be set until the model is instantiated, in which case the +        implementation is like so: + +            example_relationship = validated_data.pop('example_relationship') +            instance = ExampleModel.objects.create(**validated_data) +            instance.example_relationship = example_relationship +            return instance + +        The default implementation also does not handle nested relationships. +        If you want to support writable nested relationships you'll need +        to write an explicit `.create()` method.          """ +        raise_errors_on_nested_writes('create', self, validated_data) -        cls = self.opts.model -        assert cls is not None, \ -                "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ -        opts = get_concrete_model(cls)._meta -        ret = SortedDict() -        nested = bool(self.opts.depth) +        ModelClass = self.Meta.model -        # Deal with adding the primary key field -        pk_field = opts.pk -        while pk_field.rel and pk_field.rel.parent_link: -            # If model is a child via multitable inheritance, use parent's pk -            pk_field = pk_field.rel.to._meta.pk +        # Remove many-to-many relationships from validated_data. +        # They are not valid arguments to the default `.create()` method, +        # as they require that the instance has already been saved. +        info = model_meta.get_field_info(ModelClass) +        many_to_many = {} +        for field_name, relation_info in info.relations.items(): +            if relation_info.to_many and (field_name in validated_data): +                many_to_many[field_name] = validated_data.pop(field_name) -        field = self.get_pk_field(pk_field) -        if field: -            ret[pk_field.name] = field +        try: +            instance = ModelClass.objects.create(**validated_data) +        except TypeError as exc: +            msg = ( +                'Got a `TypeError` when calling `%s.objects.create()`. ' +                'This may be because you have a writable field on the ' +                'serializer class that is not a valid argument to ' +                '`%s.objects.create()`. You may need to make the field ' +                'read-only, or override the %s.create() method to handle ' +                'this correctly.\nOriginal exception text was: %s.' % +                ( +                    ModelClass.__name__, +                    ModelClass.__name__, +                    self.__class__.__name__, +                    exc +                ) +            ) +            raise TypeError(msg) -        # Deal with forward relationships -        forward_rels = [field for field in opts.fields if field.serialize] -        forward_rels += [field for field in opts.many_to_many if field.serialize] +        # Save many-to-many relationships after the instance is created. +        if many_to_many: +            for field_name, value in many_to_many.items(): +                setattr(instance, field_name, value) -        for model_field in forward_rels: -            has_through_model = False +        return instance -            if model_field.rel: -                to_many = isinstance(model_field, -                                     models.fields.related.ManyToManyField) -                related_model = model_field.rel.to +    def update(self, instance, validated_data): +        raise_errors_on_nested_writes('update', self, validated_data) -                if to_many and not model_field.rel.through._meta.auto_created: -                    has_through_model = True +        for attr, value in validated_data.items(): +            setattr(instance, attr, value) +        instance.save() -            if model_field.rel and nested: -                if len(inspect.getargspec(self.get_nested_field).args) == 2: -                    warnings.warn( -                        'The `get_nested_field(model_field)` call signature ' -                        'is due to be deprecated. ' -                        'Use `get_nested_field(model_field, related_model, ' -                        'to_many) instead', -                        PendingDeprecationWarning -                    ) -                    field = self.get_nested_field(model_field) -                else: -                    field = self.get_nested_field(model_field, related_model, to_many) -            elif model_field.rel: -                if len(inspect.getargspec(self.get_nested_field).args) == 3: -                    warnings.warn( -                        'The `get_related_field(model_field, to_many)` call ' -                        'signature is due to be deprecated. ' -                        'Use `get_related_field(model_field, related_model, ' -                        'to_many) instead', -                        PendingDeprecationWarning -                    ) -                    field = self.get_related_field(model_field, to_many=to_many) -                else: -                    field = self.get_related_field(model_field, related_model, to_many) -            else: -                field = self.get_field(model_field) +        return instance -            if field: -                if has_through_model: -                    field.read_only = True +    # Determine the fields to apply... -                ret[model_field.name] = field +    def get_fields(self): +        """ +        Return the dict of field names -> field instances that should be +        used for `self.fields` when instantiating the serializer. +        """ +        assert hasattr(self, 'Meta'), ( +            'Class {serializer_class} missing "Meta" attribute'.format( +                serializer_class=self.__class__.__name__ +            ) +        ) +        assert hasattr(self.Meta, 'model'), ( +            'Class {serializer_class} missing "Meta.model" attribute'.format( +                serializer_class=self.__class__.__name__ +            ) +        ) -        # Deal with reverse relationships -        if not self.opts.fields: -            reverse_rels = [] -        else: -            # Reverse relationships are only included if they are explicitly -            # present in the `fields` option on the serializer -            reverse_rels = opts.get_all_related_objects() -            reverse_rels += opts.get_all_related_many_to_many_objects() - -        for relation in reverse_rels: -            accessor_name = relation.get_accessor_name() -            if not self.opts.fields or accessor_name not in self.opts.fields: +        declared_fields = copy.deepcopy(self._declared_fields) +        model = getattr(self.Meta, 'model') +        depth = getattr(self.Meta, 'depth', 0) + +        if depth is not None: +            assert depth >= 0, "'depth' may not be negative." +            assert depth <= 10, "'depth' may not be greater than 10." + +        # Retrieve metadata about fields & relationships on the model class. +        info = model_meta.get_field_info(model) +        field_names = self.get_field_names(declared_fields, info) + +        # Determine any extra field arguments and hidden fields that +        # should be included +        extra_kwargs = self.get_extra_kwargs() +        extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs( +            field_names, declared_fields, extra_kwargs +        ) + +        # Determine the fields that should be included on the serializer. +        fields = OrderedDict() + +        for field_name in field_names: +            # If the field is explicitly declared on the class then use that. +            if field_name in declared_fields: +                fields[field_name] = declared_fields[field_name]                  continue -            related_model = relation.model -            to_many = relation.field.rel.multiple -            has_through_model = False -            is_m2m = isinstance(relation.field, -                                models.fields.related.ManyToManyField) -            if is_m2m and not relation.field.rel.through._meta.auto_created: -                has_through_model = True +            # Determine the serializer field class and keyword arguments. +            field_class, field_kwargs = self.build_field( +                field_name, info, model, depth +            ) -            if nested: -                field = self.get_nested_field(None, related_model, to_many) -            else: -                field = self.get_related_field(None, related_model, to_many) - -            if field: -                if has_through_model: -                    field.read_only = True - -                ret[accessor_name] = field - -        # Add the `read_only` flag to any fields that have bee specified -        # in the `read_only_fields` option -        for field_name in self.opts.read_only_fields: -            assert field_name not in self.base_fields.keys(), \ -                "field '%s' on serializer '%s' specfied in " \ -                "`read_only_fields`, but also added " \ -                "as an explict field.  Remove it from `read_only_fields`." % \ -                (field_name, self.__class__.__name__) -            assert field_name in ret, \ -                "Noexistant field '%s' specified in `read_only_fields` " \ -                "on serializer '%s'." % \ -                (field_name, self.__class__.__name__) -            ret[field_name].read_only = True +            # Include any kwargs defined in `Meta.extra_kwargs` +            extra_field_kwargs = extra_kwargs.get(field_name, {}) +            field_kwargs = self.include_extra_kwargs( +                field_kwargs, extra_field_kwargs +            ) -        return ret +            # Create the serializer field. +            fields[field_name] = field_class(**field_kwargs) -    def get_pk_field(self, model_field): +        # Add in any hidden fields. +        fields.update(hidden_fields) + +        return fields + +    # Methods for determining the set of field names to include... + +    def get_field_names(self, declared_fields, info):          """ -        Returns a default instance of the pk field. +        Returns the list of all field names that should be created when +        instantiating this serializer class. This is based on the default +        set of fields, but also takes into account the `Meta.fields` or +        `Meta.exclude` options if they have been specified.          """ -        return self.get_field(model_field) +        fields = getattr(self.Meta, 'fields', None) +        exclude = getattr(self.Meta, 'exclude', None) -    def get_nested_field(self, model_field, related_model, to_many): -        """ -        Creates a default instance of a nested relational field. +        if fields and not isinstance(fields, (list, tuple)): +            raise TypeError( +                'The `fields` option must be a list or tuple. Got %s.' % +                type(fields).__name__ +            ) -        Note that model_field will be `None` for reverse relationships. -        """ -        class NestedModelSerializer(ModelSerializer): -            class Meta: -                model = related_model -                depth = self.opts.depth - 1 +        if exclude and not isinstance(exclude, (list, tuple)): +            raise TypeError( +                'The `exclude` option must be a list or tuple. Got %s.' % +                type(exclude).__name__ +            ) -        return NestedModelSerializer(many=to_many) +        assert not (fields and exclude), ( +            "Cannot set both 'fields' and 'exclude' options on " +            "serializer {serializer_class}.".format( +                serializer_class=self.__class__.__name__ +            ) +        ) + +        if fields is not None: +            # Ensure that all declared fields have also been included in the +            # `Meta.fields` option. + +            # Do not require any fields that are declared a parent class, +            # in order to allow serializer subclasses to only include +            # a subset of fields. +            required_field_names = set(declared_fields) +            for cls in self.__class__.__bases__: +                required_field_names -= set(getattr(cls, '_declared_fields', [])) + +            for field_name in required_field_names: +                assert field_name in fields, ( +                    "The field '{field_name}' was declared on serializer " +                    "{serializer_class}, but has not been included in the " +                    "'fields' option.".format( +                        field_name=field_name, +                        serializer_class=self.__class__.__name__ +                    ) +                ) +            return fields + +        # Use the default set of field names if `Meta.fields` is not specified. +        fields = self.get_default_field_names(declared_fields, info) + +        if exclude is not None: +            # If `Meta.exclude` is included, then remove those fields. +            for field_name in exclude: +                assert field_name in fields, ( +                    "The field '{field_name}' was include on serializer " +                    "{serializer_class} in the 'exclude' option, but does " +                    "not match any model field.".format( +                        field_name=field_name, +                        serializer_class=self.__class__.__name__ +                    ) +                ) +                fields.remove(field_name) -    def get_related_field(self, model_field, related_model, to_many): +        return fields + +    def get_default_field_names(self, declared_fields, model_info): +        """ +        Return the default list of field names that will be used if the +        `Meta.fields` option is not specified.          """ -        Creates a default instance of a flat relational field. +        return ( +            [model_info.pk.name] + +            list(declared_fields.keys()) + +            list(model_info.fields.keys()) + +            list(model_info.forward_relations.keys()) +        ) + +    # Methods for constructing serializer fields... -        Note that model_field will be `None` for reverse relationships. +    def build_field(self, field_name, info, model_class, nested_depth):          """ -        # TODO: filter queryset using: -        # .using(db).complex_filter(self.rel.limit_choices_to) +        Return a two tuple of (cls, kwargs) to build a serializer field with. +        """ +        if field_name in info.fields_and_pk: +            model_field = info.fields_and_pk[field_name] +            return self.build_standard_field(field_name, model_field) + +        elif field_name in info.relations: +            relation_info = info.relations[field_name] +            if not nested_depth: +                return self.build_relational_field(field_name, relation_info) +            else: +                return self.build_nested_field(field_name, relation_info, nested_depth) -        kwargs = { -            'queryset': related_model._default_manager, -            'many': to_many -        } +        elif hasattr(model_class, field_name): +            return self.build_property_field(field_name, model_class) -        if model_field: -            kwargs['required'] = not(model_field.null or model_field.blank) +        elif field_name == api_settings.URL_FIELD_NAME: +            return self.build_url_field(field_name, model_class) -        return PrimaryKeyRelatedField(**kwargs) +        return self.build_unknown_field(field_name, model_class) -    def get_field(self, model_field): +    def build_standard_field(self, field_name, model_field):          """ -        Creates a default instance of a basic non-relational field. +        Create regular model fields.          """ -        kwargs = {} +        field_mapping = ClassLookupDict(self.serializer_field_mapping) + +        field_class = field_mapping[model_field] +        field_kwargs = get_field_kwargs(field_name, model_field) + +        if 'choices' in field_kwargs: +            # Fields with choices get coerced into `ChoiceField` +            # instead of using their regular typed field. +            field_class = self.serializer_choice_field + +        if not issubclass(field_class, ModelField): +            # `model_field` is only valid for the fallback case of +            # `ModelField`, which is used when no other typed field +            # matched to the model field. +            field_kwargs.pop('model_field', None) + +        if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField): +            # `allow_blank` is only valid for textual fields. +            field_kwargs.pop('allow_blank', None) + +        if postgres_fields and isinstance(model_field, postgres_fields.ArrayField): +            # Populate the `child` argument on `ListField` instances generated +            # for the PostgrSQL specfic `ArrayField`. +            child_model_field = model_field.base_field +            child_field_class, child_field_kwargs = self.build_standard_field( +                'child', child_model_field +            ) +            field_kwargs['child'] = child_field_class(**child_field_kwargs) -        if model_field.null or model_field.blank: -            kwargs['required'] = False +        return field_class, field_kwargs -        if isinstance(model_field, models.AutoField) or not model_field.editable: -            kwargs['read_only'] = True +    def build_relational_field(self, field_name, relation_info): +        """ +        Create fields for forward and reverse relationships. +        """ +        field_class = self.serializer_related_field +        field_kwargs = get_relation_kwargs(field_name, relation_info) -        if model_field.has_default(): -            kwargs['default'] = model_field.get_default() +        # `view_name` is only valid for hyperlinked relationships. +        if not issubclass(field_class, HyperlinkedRelatedField): +            field_kwargs.pop('view_name', None) -        if issubclass(model_field.__class__, models.TextField): -            kwargs['widget'] = widgets.Textarea +        return field_class, field_kwargs -        if model_field.verbose_name is not None: -            kwargs['label'] = model_field.verbose_name +    def build_nested_field(self, field_name, relation_info, nested_depth): +        """ +        Create nested fields for forward and reverse relationships. +        """ +        class NestedSerializer(ModelSerializer): +            class Meta: +                model = relation_info.related_model +                depth = nested_depth -        if model_field.help_text is not None: -            kwargs['help_text'] = model_field.help_text +        field_class = NestedSerializer +        field_kwargs = get_nested_relation_kwargs(relation_info) -        # TODO: TypedChoiceField? -        if model_field.flatchoices:  # This ModelField contains choices -            kwargs['choices'] = model_field.flatchoices -            return ChoiceField(**kwargs) +        return field_class, field_kwargs -        # put this below the ChoiceField because min_value isn't a valid initializer -        if issubclass(model_field.__class__, models.PositiveIntegerField) or\ -                issubclass(model_field.__class__, models.PositiveSmallIntegerField): -            kwargs['min_value'] = 0 +    def build_property_field(self, field_name, model_class): +        """ +        Create a read only field for model methods and properties. +        """ +        field_class = ReadOnlyField +        field_kwargs = {} -        attribute_dict = { -            models.CharField: ['max_length'], -            models.CommaSeparatedIntegerField: ['max_length'], -            models.DecimalField: ['max_digits', 'decimal_places'], -            models.EmailField: ['max_length'], -            models.FileField: ['max_length'], -            models.ImageField: ['max_length'], -            models.SlugField: ['max_length'], -            models.URLField: ['max_length'], -        } +        return field_class, field_kwargs -        if model_field.__class__ in attribute_dict: -            attributes = attribute_dict[model_field.__class__] -            for attribute in attributes: -                kwargs.update({attribute: getattr(model_field, attribute)}) +    def build_url_field(self, field_name, model_class): +        """ +        Create a field representing the object's own URL. +        """ +        field_class = self.serializer_url_field +        field_kwargs = get_url_kwargs(model_class) -        try: -            return self.field_mapping[model_field.__class__](**kwargs) -        except KeyError: -            return ModelField(model_field=model_field, **kwargs) +        return field_class, field_kwargs -    def get_validation_exclusions(self): +    def build_unknown_field(self, field_name, model_class):          """ -        Return a list of field names to exclude from model validation. +        Raise an error on any unknown fields.          """ -        cls = self.opts.model -        opts = get_concrete_model(cls)._meta -        exclusions = [field.name for field in opts.fields + opts.many_to_many] -        for field_name, field in self.fields.items(): -            field_name = field.source or field_name -            if field_name in exclusions and not field.read_only: -                exclusions.remove(field_name) -        return exclusions +        raise ImproperlyConfigured( +            'Field name `%s` is not valid for model `%s`.' % +            (field_name, model_class.__name__) +        ) -    def full_clean(self, instance): +    def include_extra_kwargs(self, kwargs, extra_kwargs):          """ -        Perform Django's full_clean, and populate the `errors` dictionary -        if any validation errors occur. +        Include any 'extra_kwargs' that have been included for this field, +        possibly removing any incompatible existing keyword arguments. +        """ +        if extra_kwargs.get('read_only', False): +            for attr in [ +                'required', 'default', 'allow_blank', 'allow_null', +                'min_length', 'max_length', 'min_value', 'max_value', +                'validators', 'queryset' +            ]: +                kwargs.pop(attr, None) + +        if extra_kwargs.get('default') and kwargs.get('required') is False: +            kwargs.pop('required') -        Note that we don't perform this inside the `.restore_object()` method, -        so that subclasses can override `.restore_object()`, and still get -        the full_clean validation checking. +        kwargs.update(extra_kwargs) + +        return kwargs + +    # Methods for determining additional keyword arguments to apply... + +    def get_extra_kwargs(self):          """ -        try: -            instance.full_clean(exclude=self.get_validation_exclusions()) -        except ValidationError as err: -            self._errors = err.message_dict -            return None -        return instance +        Return a dictionary mapping field names to a dictionary of +        additional keyword arguments. +        """ +        extra_kwargs = getattr(self.Meta, 'extra_kwargs', {}) + +        read_only_fields = getattr(self.Meta, 'read_only_fields', None) +        if read_only_fields is not None: +            for field_name in read_only_fields: +                kwargs = extra_kwargs.get(field_name, {}) +                kwargs['read_only'] = True +                extra_kwargs[field_name] = kwargs + +        # These are all pending deprecation. +        write_only_fields = getattr(self.Meta, 'write_only_fields', None) +        if write_only_fields is not None: +            warnings.warn( +                "The `Meta.write_only_fields` option is deprecated. " +                "Use `Meta.extra_kwargs={<field_name>: {'write_only': True}}` instead.", +                DeprecationWarning, +                stacklevel=3 +            ) +            for field_name in write_only_fields: +                kwargs = extra_kwargs.get(field_name, {}) +                kwargs['write_only'] = True +                extra_kwargs[field_name] = kwargs + +        view_name = getattr(self.Meta, 'view_name', None) +        if view_name is not None: +            warnings.warn( +                "The `Meta.view_name` option is deprecated. " +                "Use `Meta.extra_kwargs={'url': {'view_name': ...}}` instead.", +                DeprecationWarning, +                stacklevel=3 +            ) +            kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {}) +            kwargs['view_name'] = view_name +            extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs + +        lookup_field = getattr(self.Meta, 'lookup_field', None) +        if lookup_field is not None: +            warnings.warn( +                "The `Meta.lookup_field` option is deprecated. " +                "Use `Meta.extra_kwargs={'url': {'lookup_field': ...}}` instead.", +                DeprecationWarning, +                stacklevel=3 +            ) +            kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {}) +            kwargs['lookup_field'] = lookup_field +            extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs -    def restore_object(self, attrs, instance=None): +        return extra_kwargs + +    def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):          """ -        Restore the model instance. +        Return any additional field options that need to be included as a +        result of uniqueness constraints on the model. This is returned as +        a two-tuple of: + +        ('dict of updated extra kwargs', 'mapping of hidden fields')          """ -        m2m_data = {} -        related_data = {} -        meta = self.opts.model._meta +        model = getattr(self.Meta, 'model') +        model_fields = self._get_model_fields( +            field_names, declared_fields, extra_kwargs +        ) -        # Reverse fk or one-to-one relations -        for (obj, model) in meta.get_all_related_objects_with_model(): -            field_name = obj.field.related_query_name() -            if field_name in attrs: -                related_data[field_name] = attrs.pop(field_name) +        # Determine if we need any additional `HiddenField` or extra keyword +        # arguments to deal with `unique_for` dates that are required to +        # be in the input data in order to validate it. +        unique_constraint_names = set() + +        for model_field in model_fields.values(): +            # Include each of the `unique_for_*` field names. +            unique_constraint_names |= set([ +                model_field.unique_for_date, +                model_field.unique_for_month, +                model_field.unique_for_year +            ]) + +        unique_constraint_names -= set([None]) + +        # Include each of the `unique_together` field names, +        # so long as all the field names are included on the serializer. +        for parent_class in [model] + list(model._meta.parents.keys()): +            for unique_together_list in parent_class._meta.unique_together: +                if set(field_names).issuperset(set(unique_together_list)): +                    unique_constraint_names |= set(unique_together_list) + +        # Now we have all the field names that have uniqueness constraints +        # applied, we can add the extra 'required=...' or 'default=...' +        # arguments that are appropriate to these fields, or add a `HiddenField` for it. +        hidden_fields = {} +        uniqueness_extra_kwargs = {} + +        for unique_constraint_name in unique_constraint_names: +            # Get the model field that is referred too. +            unique_constraint_field = model._meta.get_field(unique_constraint_name) + +            if getattr(unique_constraint_field, 'auto_now_add', None): +                default = CreateOnlyDefault(timezone.now) +            elif getattr(unique_constraint_field, 'auto_now', None): +                default = timezone.now +            elif unique_constraint_field.has_default(): +                default = unique_constraint_field.default +            else: +                default = empty -        # Reverse m2m relations -        for (obj, model) in meta.get_all_related_m2m_objects_with_model(): -            field_name = obj.field.related_query_name() -            if field_name in attrs: -                m2m_data[field_name] = attrs.pop(field_name) +            if unique_constraint_name in model_fields: +                # The corresponding field is present in the serializer +                if default is empty: +                    uniqueness_extra_kwargs[unique_constraint_name] = {'required': True} +                else: +                    uniqueness_extra_kwargs[unique_constraint_name] = {'default': default} +            elif default is not empty: +                # The corresponding field is not present in the, +                # serializer. We have a default to use for it, so +                # add in a hidden field that populates it. +                hidden_fields[unique_constraint_name] = HiddenField(default=default) + +        # Update `extra_kwargs` with any new options. +        for key, value in uniqueness_extra_kwargs.items(): +            if key in extra_kwargs: +                extra_kwargs[key].update(value) +            else: +                extra_kwargs[key] = value -        # Forward m2m relations -        for field in meta.many_to_many: -            if field.name in attrs: -                m2m_data[field.name] = attrs.pop(field.name) +        return extra_kwargs, hidden_fields -        # Update an existing instance... -        if instance is not None: -            for key, val in attrs.items(): -                setattr(instance, key, val) +    def _get_model_fields(self, field_names, declared_fields, extra_kwargs): +        """ +        Returns all the model fields that are being mapped to by fields +        on the serializer class. +        Returned as a dict of 'model field name' -> 'model field'. +        Used internally by `get_uniqueness_field_options`. +        """ +        model = getattr(self.Meta, 'model') +        model_fields = {} + +        for field_name in field_names: +            if field_name in declared_fields: +                # If the field is declared on the serializer +                field = declared_fields[field_name] +                source = field.source or field_name +            else: +                try: +                    source = extra_kwargs[field_name]['source'] +                except KeyError: +                    source = field_name + +            if '.' in source or source == '*': +                # Model fields will always have a simple source mapping, +                # they can't be nested attribute lookups. +                continue -        # ...or create a new instance -        else: -            instance = self.opts.model(**attrs) +            try: +                field = model._meta.get_field(source) +                if isinstance(field, DjangoModelField): +                    model_fields[source] = field +            except FieldDoesNotExist: +                pass -        # Any relations that cannot be set until we've -        # saved the model get hidden away on these -        # private attributes, so we can deal with them -        # at the point of save. -        instance._related_data = related_data -        instance._m2m_data = m2m_data +        return model_fields -        return instance +    # Determine the validators to apply... -    def from_native(self, data, files): +    def get_validators(self):          """ -        Override the default method to also include model field validation. +        Determine the set of validators to use when instantiating serializer.          """ -        instance = super(ModelSerializer, self).from_native(data, files) -        if not self._errors: -            return self.full_clean(instance) +        # If the validators have been declared explicitly then use that. +        validators = getattr(getattr(self, 'Meta', None), 'validators', None) +        if validators is not None: +            return validators[:] + +        # Otherwise use the default set of validators. +        return ( +            self.get_unique_together_validators() + +            self.get_unique_for_date_validators() +        ) -    def save_object(self, obj, **kwargs): +    def get_unique_together_validators(self):          """ -        Save the deserialized object and return it. +        Determine a default set of validators for any unique_together contraints.          """ -        obj.save(**kwargs) +        model_class_inheritance_tree = ( +            [self.Meta.model] + +            list(self.Meta.model._meta.parents.keys()) +        ) -        if getattr(obj, '_m2m_data', None): -            for accessor_name, object_list in obj._m2m_data.items(): -                setattr(obj, accessor_name, object_list) -            del(obj._m2m_data) +        # The field names we're passing though here only include fields +        # which may map onto a model field. Any dotted field name lookups +        # cannot map to a field, and must be a traversal, so we're not +        # including those. +        field_names = set([ +            field.source for field in self.fields.values() +            if (field.source != '*') and ('.' not in field.source) +        ]) + +        # Note that we make sure to check `unique_together` both on the +        # base model class, but also on any parent classes. +        validators = [] +        for parent_class in model_class_inheritance_tree: +            for unique_together in parent_class._meta.unique_together: +                if field_names.issuperset(set(unique_together)): +                    validator = UniqueTogetherValidator( +                        queryset=parent_class._default_manager, +                        fields=unique_together +                    ) +                    validators.append(validator) +        return validators -        if getattr(obj, '_related_data', None): -            for accessor_name, related in obj._related_data.items(): -                setattr(obj, accessor_name, related) -            del(obj._related_data) +    def get_unique_for_date_validators(self): +        """ +        Determine a default set of validators for the following contraints: +        * unique_for_date +        * unique_for_month +        * unique_for_year +        """ +        info = model_meta.get_field_info(self.Meta.model) +        default_manager = self.Meta.model._default_manager +        field_names = [field.source for field in self.fields.values()] -class HyperlinkedModelSerializerOptions(ModelSerializerOptions): -    """ -    Options for HyperlinkedModelSerializer -    """ -    def __init__(self, meta): -        super(HyperlinkedModelSerializerOptions, self).__init__(meta) -        self.view_name = getattr(meta, 'view_name', None) -        self.lookup_field = getattr(meta, 'lookup_field', None) +        validators = [] +        for field_name, field in info.fields_and_pk.items(): +            if field.unique_for_date and field_name in field_names: +                validator = UniqueForDateValidator( +                    queryset=default_manager, +                    field=field_name, +                    date_field=field.unique_for_date +                ) +                validators.append(validator) -class HyperlinkedModelSerializer(ModelSerializer): -    """ -    A subclass of ModelSerializer that uses hyperlinked relationships, -    instead of primary key relationships. -    """ -    _options_class = HyperlinkedModelSerializerOptions -    _default_view_name = '%(model_name)s-detail' -    _hyperlink_field_class = HyperlinkedRelatedField +            if field.unique_for_month and field_name in field_names: +                validator = UniqueForMonthValidator( +                    queryset=default_manager, +                    field=field_name, +                    date_field=field.unique_for_month +                ) +                validators.append(validator) -    def get_default_fields(self): -        fields = super(HyperlinkedModelSerializer, self).get_default_fields() +            if field.unique_for_year and field_name in field_names: +                validator = UniqueForYearValidator( +                    queryset=default_manager, +                    field=field_name, +                    date_field=field.unique_for_year +                ) +                validators.append(validator) -        if self.opts.view_name is None: -            self.opts.view_name = self._get_default_view_name(self.opts.model) +        return validators -        if 'url' not in fields: -            url_field = HyperlinkedIdentityField( -                view_name=self.opts.view_name, -                lookup_field=self.opts.lookup_field -            ) -            ret = self._dict_class() -            ret['url'] = url_field -            ret.update(fields) -            fields = ret -        return fields +if hasattr(models, 'UUIDField'): +    ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField -    def get_pk_field(self, model_field): -        if self.opts.fields and model_field.name in self.opts.fields: -            return self.get_field(model_field) +if postgres_fields: +    class CharMappingField(DictField): +        child = CharField() -    def get_related_field(self, model_field, related_model, to_many): -        """ -        Creates a default instance of a flat relational field. -        """ -        # TODO: filter queryset using: -        # .using(db).complex_filter(self.rel.limit_choices_to) -        kwargs = { -            'queryset': related_model._default_manager, -            'view_name': self._get_default_view_name(related_model), -            'many': to_many -        } +    ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = CharMappingField +    ModelSerializer.serializer_field_mapping[postgres_fields.ArrayField] = ListField -        if model_field: -            kwargs['required'] = not(model_field.null or model_field.blank) -        if self.opts.lookup_field: -            kwargs['lookup_field'] = self.opts.lookup_field +class HyperlinkedModelSerializer(ModelSerializer): +    """ +    A type of `ModelSerializer` that uses hyperlinked relationships instead +    of primary key relationships. Specifically: -        return self._hyperlink_field_class(**kwargs) +    * A 'url' field is included instead of the 'id' field. +    * Relationships to other instances are hyperlinks, instead of primary keys. +    """ +    serializer_related_field = HyperlinkedRelatedField -    def get_identity(self, data): +    def get_default_field_names(self, declared_fields, model_info):          """ -        This hook is required for bulk update. -        We need to override the default, to use the url as the identity. +        Return the default list of field names that will be used if the +        `Meta.fields` option is not specified.          """ -        try: -            return data.get('url', None) -        except AttributeError: -            return None +        return ( +            [api_settings.URL_FIELD_NAME] + +            list(declared_fields.keys()) + +            list(model_info.fields.keys()) + +            list(model_info.forward_relations.keys()) +        ) -    def _get_default_view_name(self, model): +    def build_nested_field(self, field_name, relation_info, nested_depth):          """ -        Return the view name to use if 'view_name' is not specified in 'Meta' +        Create nested fields for forward and reverse relationships.          """ -        model_meta = model._meta -        format_kwargs = { -            'app_label': model_meta.app_label, -            'model_name': model_meta.object_name.lower() -        } -        return self._default_view_name % format_kwargs +        class NestedSerializer(HyperlinkedModelSerializer): +            class Meta: +                model = relation_info.related_model +                depth = nested_depth - 1 + +        field_class = NestedSerializer +        field_kwargs = get_nested_relation_kwargs(relation_info) + +        return field_class, field_kwargs | 
