django-rest-framework/rest_framework/serializers.py

506 lines
18 KiB
Python
Raw Normal View History

2013-04-25 15:47:34 +04:00
"""
Serializers and ModelSerializers are similar to Forms and ModelForms.
Unlike forms, they are not constrained to dealing with HTML output, and
form encoded input.
Serialization in REST framework is a two-phase process:
1. Serializers marshal between complex types like model instances, and
python primitives.
2. The process of marshalling between python primitives and request and
2013-04-25 15:47:34 +04:00
response content is handled by parsers and renderers.
"""
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models
from django.utils import six
2014-09-10 19:57:22 +04:00
from django.utils.datastructures import SortedDict
from collections import namedtuple
2014-09-05 19:29:46 +04:00
from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation
2014-09-18 14:20:56 +04:00
from rest_framework.utils.field_mapping import (
get_url_kwargs, get_field_kwargs,
get_relation_kwargs, get_nested_relation_kwargs,
2014-09-24 17:09:49 +04:00
ClassLookupDict
2014-09-18 14:20:56 +04:00
)
2014-08-29 19:46:26 +04:00
import copy
2014-09-19 19:43:13 +04:00
import inspect
2012-11-05 14:56:30 +04:00
# Note: We do the following so that users of the framework can use this style:
#
# example_field = serializers.CharField(...)
#
2013-05-28 18:09:23 +04:00
# This helps keep the separation between model fields, form fields, and
2012-11-05 14:56:30 +04:00
# serializer fields more explicit.
2014-05-17 02:05:33 +04:00
from rest_framework.relations import * # NOQA
from rest_framework.fields import * # NOQA
2014-08-29 19:46:26 +04:00
FieldResult = namedtuple('FieldResult', ['field', 'value', 'error'])
2014-08-29 19:46:26 +04:00
class BaseSerializer(Field):
2014-09-05 19:29:46 +04:00
"""
The BaseSerializer class provides a minimal class which may be used
for writing custom serializer implementations.
"""
2014-08-29 19:46:26 +04:00
def __init__(self, instance=None, data=None, **kwargs):
self.instance = instance
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):
kwargs['child'] = cls()
return ListSerializer(*args, **kwargs)
return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
def to_internal_value(self, data):
raise NotImplementedError('`to_internal_value()` must be implemented.')
def to_representation(self, instance):
raise NotImplementedError('`to_representation()` must be implemented.')
2014-09-26 13:46:52 +04:00
def update(self, instance, validated_data):
2014-09-05 19:29:46 +04:00
raise NotImplementedError('`update()` must be implemented.')
2014-09-26 13:46:52 +04:00
def create(self, validated_data):
2014-09-05 19:29:46 +04:00
raise NotImplementedError('`create()` must be implemented.')
2014-08-29 19:46:26 +04:00
def save(self, extras=None):
2014-09-26 13:46:52 +04:00
validated_data = self.validated_data
2014-08-29 19:46:26 +04:00
if extras is not None:
2014-09-26 13:46:52 +04:00
validated_data = dict(
list(validated_data.items()) +
list(extras.items())
)
2014-08-29 19:46:26 +04:00
if self.instance is not None:
2014-09-26 13:46:52 +04:00
self.update(self.instance, validated_data)
else:
2014-09-26 13:46:52 +04:00
self.instance = self.create(validated_data)
2014-09-26 14:56:29 +04:00
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
2014-08-29 19:46:26 +04:00
return self.instance
2014-09-05 19:29:46 +04:00
def is_valid(self, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
self._validated_data = self.to_internal_value(self._initial_data)
2014-09-05 19:29:46 +04:00
except ValidationError as exc:
self._validated_data = {}
2014-09-08 17:24:05 +04:00
self._errors = exc.message_dict
2014-09-05 19:29:46 +04:00
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self._errors)
return not bool(self._errors)
2014-08-29 19:46:26 +04:00
@property
def data(self):
if not hasattr(self, '_data'):
if self.instance is not None:
self._data = self.to_representation(self.instance)
2014-08-29 19:46:26 +04:00
elif self._initial_data is not None:
2014-09-10 19:57:22 +04:00
self._data = dict([
(field_name, field.get_value(self._initial_data))
2014-08-29 19:46:26 +04:00
for field_name, field in self.fields.items()
2014-09-10 19:57:22 +04:00
])
2014-08-29 19:46:26 +04:00
else:
self._data = self.get_initial()
return self._data
2014-08-29 19:46:26 +04:00
@property
def errors(self):
if not hasattr(self, '_errors'):
msg = 'You must call `.is_valid()` before accessing `.errors`.'
raise AssertionError(msg)
return self._errors
2014-08-29 19:46:26 +04:00
@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
2014-08-29 19:46:26 +04:00
class SerializerMetaclass(type):
"""
2014-08-29 19:46:26 +04:00
This metaclass sets a dictionary named `base_fields` on the class.
2014-09-10 16:52:16 +04:00
Any instances of `Field` included as attributes on either the class
or on any of its superclasses will be include in the
`base_fields` dictionary.
"""
2014-08-29 19:46:26 +04:00
@classmethod
2014-09-18 14:20:56 +04:00
def _get_declared_fields(cls, bases, attrs):
2014-08-29 19:46:26 +04:00
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)
2014-08-29 19:46:26 +04:00
# 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]:
2014-09-18 14:20:56 +04:00
if hasattr(base, '_declared_fields'):
fields = list(base._declared_fields.items()) + fields
2014-09-10 19:57:22 +04:00
return SortedDict(fields)
def __new__(cls, name, bases, attrs):
2014-09-18 14:20:56 +04:00
attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
2014-09-25 14:04:18 +04:00
class BindingDict(object):
"""
This dict-like object is used to store fields on a serializer.
This ensures that whenever fields are added to the serializer we call
`field.bind()` so that the `field_name` and `parent` attributes
can be set correctly.
"""
2014-09-25 14:04:18 +04:00
def __init__(self, serializer):
self.serializer = serializer
self.fields = SortedDict()
def __setitem__(self, key, field):
self.fields[key] = field
field.bind(field_name=key, parent=self.serializer)
2014-09-25 14:04:18 +04:00
def __getitem__(self, key):
return self.fields[key]
def __delitem__(self, key):
del self.fields[key]
def items(self):
return self.fields.items()
def values(self):
return self.fields.values()
2014-08-29 19:46:26 +04:00
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
def __init__(self, *args, **kwargs):
super(Serializer, self).__init__(*args, **kwargs)
2014-08-29 19:46:26 +04:00
# 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.
2014-09-25 14:04:18 +04:00
self.fields = BindingDict(self)
for key, value in self._get_base_fields().items():
self.fields[key] = value
2014-09-18 14:20:56 +04:00
def _get_base_fields(self):
return copy.deepcopy(self._declared_fields)
2014-08-29 19:46:26 +04:00
def get_initial(self):
2014-09-10 19:57:22 +04:00
return dict([
(field.field_name, field.get_initial())
2014-08-29 19:46:26 +04:00
for field in self.fields.values()
2014-09-10 19:57:22 +04:00
])
2014-08-29 19:46:26 +04:00
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)
2012-10-24 14:39:17 +04:00
def to_internal_value(self, data):
"""
2014-08-29 19:46:26 +04:00
Dict of native values <- Dict of primitive datatypes.
"""
2014-09-05 19:29:46 +04:00
if not isinstance(data, dict):
2014-09-12 13:21:35 +04:00
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: ['Invalid data']
})
2014-09-05 19:29:46 +04:00
2014-08-29 19:46:26 +04:00
ret = {}
errors = {}
fields = [field for field in self.fields.values() if not field.read_only]
2014-08-29 19:46:26 +04:00
for field in fields:
2014-09-05 19:29:46 +04:00
validate_method = getattr(self, 'validate_' + field.field_name, None)
2014-08-29 19:46:26 +04:00
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
2014-09-05 19:29:46 +04:00
if validate_method is not None:
validated_value = validate_method(validated_value)
2014-08-29 19:46:26 +04:00
except ValidationError as exc:
2014-09-08 17:24:05 +04:00
errors[field.field_name] = exc.messages
2014-08-29 19:46:26 +04:00
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
2014-08-29 19:46:26 +04:00
if errors:
raise ValidationError(errors)
2014-09-08 17:24:05 +04:00
try:
return self.validate(ret)
2014-09-10 19:57:22 +04:00
except ValidationError as exc:
2014-09-12 13:21:35 +04:00
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: exc.messages
})
def to_representation(self, instance):
"""
2014-08-29 19:46:26 +04:00
Object instance -> Dict of primitive datatypes.
"""
2014-09-10 19:57:22 +04:00
ret = SortedDict()
2014-08-29 19:46:26 +04:00
fields = [field for field in self.fields.values() if not field.write_only]
2014-08-29 19:46:26 +04:00
for field in fields:
native_value = field.get_attribute(instance)
ret[field.field_name] = field.to_representation(native_value)
2014-08-29 19:46:26 +04:00
return ret
2014-09-02 20:41:23 +04:00
def validate(self, attrs):
return attrs
2014-08-29 19:46:26 +04:00
def __iter__(self):
errors = self.errors if hasattr(self, '_errors') else {}
for field in self.fields.values():
value = self.data.get(field.field_name) if self.data else None
error = errors.get(field.field_name)
yield FieldResult(field, value, error)
2014-09-09 20:46:28 +04:00
def __repr__(self):
return representation.serializer_repr(self, indent=1)
2014-09-26 16:08:20 +04:00
# There's some replication of `ListField` here,
# but that's probably better than obfuscating the call hierarchy.
2014-08-29 19:46:26 +04:00
class ListSerializer(BaseSerializer):
child = None
initial = []
2014-08-29 19:46:26 +04:00
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.'
2014-09-19 19:43:13 +04:00
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
2014-08-29 19:46:26 +04:00
super(ListSerializer, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
2014-08-29 19:46:26 +04:00
def get_value(self, dictionary):
# We override the default field access in order to support
# lists in HTML forms.
2014-09-26 16:08:20 +04:00
if html.is_html_input(dictionary):
2014-08-29 19:46:26 +04:00
return html.parse_html_list(dictionary, prefix=self.field_name)
return dictionary.get(self.field_name, empty)
2013-03-19 18:26:48 +04:00
def to_internal_value(self, data):
"""
2014-08-29 19:46:26 +04:00
List of dicts of native values <- List of dicts of primitive datatypes.
"""
2014-08-29 19:46:26 +04:00
if html.is_html_input(data):
data = html.parse_html_list(data)
return [self.child.run_validation(item) for item in data]
def to_representation(self, data):
2013-01-31 00:38:11 +04:00
"""
2014-08-29 19:46:26 +04:00
List of object instances -> List of dicts of primitive datatypes.
2013-01-31 00:38:11 +04:00
"""
return [self.child.to_representation(item) for item in data]
2014-08-29 19:46:26 +04:00
def create(self, attrs_list):
return [self.child.create(attrs) for attrs in attrs_list]
2014-09-09 20:46:28 +04:00
def __repr__(self):
return representation.list_repr(self, indent=1)
2012-10-04 16:28:14 +04:00
class ModelSerializer(Serializer):
2014-09-24 17:09:49 +04:00
_field_mapping = ClassLookupDict({
models.AutoField: IntegerField,
2014-09-09 20:46:28 +04:00
models.BigIntegerField: IntegerField,
models.BooleanField: BooleanField,
models.CharField: CharField,
models.CommaSeparatedIntegerField: CharField,
models.DateField: DateField,
models.DateTimeField: DateTimeField,
models.DecimalField: DecimalField,
models.EmailField: EmailField,
2014-09-11 23:22:32 +04:00
models.Field: ModelField,
2014-09-09 20:46:28 +04:00
models.FileField: FileField,
models.FloatField: FloatField,
2014-09-10 11:53:33 +04:00
models.ImageField: ImageField,
models.IntegerField: IntegerField,
2014-09-23 17:30:17 +04:00
models.NullBooleanField: NullBooleanField,
models.PositiveIntegerField: IntegerField,
models.PositiveSmallIntegerField: IntegerField,
2014-09-09 20:46:28 +04:00
models.SlugField: SlugField,
models.SmallIntegerField: IntegerField,
models.TextField: CharField,
2014-09-02 20:41:23 +04:00
models.TimeField: TimeField,
models.URLField: URLField,
2014-09-24 17:09:49 +04:00
})
2014-09-18 14:20:56 +04:00
_related_class = PrimaryKeyRelatedField
2014-08-29 19:46:26 +04:00
2014-09-05 19:29:46 +04:00
def create(self, attrs):
2014-09-18 14:20:56 +04:00
ModelClass = self.Meta.model
# Remove many-to-many relationships from attrs.
# 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 = {}
2014-09-18 18:47:27 +04:00
for field_name, relation_info in info.relations.items():
if relation_info.to_many and (field_name in attrs):
many_to_many[field_name] = attrs.pop(field_name)
instance = ModelClass.objects.create(**attrs)
2014-09-18 18:47:27 +04:00
# Save many-to-many relationships after the instance is created.
if many_to_many:
2014-09-18 18:47:27 +04:00
for field_name, value in many_to_many.items():
setattr(instance, field_name, value)
return instance
2014-09-02 20:41:23 +04:00
2014-09-05 19:29:46 +04:00
def update(self, obj, attrs):
for attr, value in attrs.items():
2014-09-02 20:41:23 +04:00
setattr(obj, attr, value)
obj.save()
2014-09-18 14:20:56 +04:00
def _get_base_fields(self):
declared_fields = copy.deepcopy(self._declared_fields)
2014-08-29 19:46:26 +04:00
2014-09-10 19:57:22 +04:00
ret = SortedDict()
2014-09-18 14:20:56 +04:00
model = getattr(self.Meta, 'model')
fields = getattr(self.Meta, 'fields', None)
depth = getattr(self.Meta, 'depth', 0)
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
2014-09-18 14:20:56 +04:00
# Retrieve metadata about fields & relationships on the model class.
info = model_meta.get_field_info(model)
# Use the default set of fields if none is supplied explicitly.
if fields is None:
fields = self._get_default_field_names(declared_fields, info)
for field_name in fields:
if field_name in declared_fields:
# Field is explicitly declared on the class, use that.
ret[field_name] = declared_fields[field_name]
continue
elif field_name == api_settings.URL_FIELD_NAME:
# Create the URL field.
field_cls = HyperlinkedIdentityField
kwargs = get_url_kwargs(model)
elif field_name in info.fields_and_pk:
# Create regular model fields.
model_field = info.fields_and_pk[field_name]
2014-09-24 17:09:49 +04:00
field_cls = self._field_mapping[model_field]
2014-09-18 14:20:56 +04:00
kwargs = get_field_kwargs(field_name, model_field)
if 'choices' in kwargs:
# Fields with choices get coerced into `ChoiceField`
# instead of using their regular typed field.
field_cls = ChoiceField
if not issubclass(field_cls, 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.
kwargs.pop('model_field', None)
2014-09-23 17:15:00 +04:00
if not issubclass(field_cls, CharField):
# `allow_blank` is only valid for textual fields.
kwargs.pop('allow_blank', None)
2014-09-18 14:20:56 +04:00
elif field_name in info.relations:
# Create forward and reverse relationships.
relation_info = info.relations[field_name]
if depth:
field_cls = self._get_nested_class(depth, relation_info)
kwargs = get_nested_relation_kwargs(relation_info)
else:
field_cls = self._related_class
kwargs = get_relation_kwargs(field_name, relation_info)
# `view_name` is only valid for hyperlinked relationships.
if not issubclass(field_cls, HyperlinkedRelatedField):
kwargs.pop('view_name', None)
elif hasattr(model, field_name):
# Create a read only field for model methods and properties.
field_cls = ReadOnlyField
kwargs = {}
else:
raise ImproperlyConfigured(
'Field name `%s` is not valid for model `%s`.' %
(field_name, model.__class__.__name__)
)
# Check that any fields declared on the class are
# also explicity included in `Meta.fields`.
missing_fields = set(declared_fields.keys()) - set(fields)
if missing_fields:
missing_field = list(missing_fields)[0]
raise ImproperlyConfigured(
'Field `%s` has been declared on serializer `%s`, but '
'is missing from `Meta.fields`.' %
(missing_field, self.__class__.__name__)
)
# Populate any kwargs defined in `Meta.extra_kwargs`
kwargs.update(extra_kwargs.get(field_name, {}))
# Create the serializer field.
2014-09-18 14:20:56 +04:00
ret[field_name] = field_cls(**kwargs)
return ret
2014-09-18 14:20:56 +04:00
def _get_default_field_names(self, declared_fields, model_info):
return (
[model_info.pk.name] +
list(declared_fields.keys()) +
list(model_info.fields.keys()) +
list(model_info.forward_relations.keys())
)
2013-04-30 11:24:33 +04:00
2014-09-18 14:20:56 +04:00
def _get_nested_class(self, nested_depth, relation_info):
class NestedSerializer(ModelSerializer):
2012-11-13 15:47:32 +04:00
class Meta:
2014-09-18 14:20:56 +04:00
model = relation_info.related
depth = nested_depth
return NestedSerializer
class HyperlinkedModelSerializer(ModelSerializer):
2014-09-18 14:20:56 +04:00
_related_class = HyperlinkedRelatedField
def _get_default_field_names(self, declared_fields, model_info):
return (
[api_settings.URL_FIELD_NAME] +
list(declared_fields.keys()) +
list(model_info.fields.keys()) +
list(model_info.forward_relations.keys())
)
def _get_nested_class(self, nested_depth, relation_info):
class NestedSerializer(HyperlinkedModelSerializer):
class Meta:
model = relation_info.related
depth = nested_depth
return NestedSerializer