2013-02-05 00:55:35 +04:00
|
|
|
from __future__ import unicode_literals
|
2012-09-20 16:06:27 +04:00
|
|
|
import copy
|
|
|
|
import datetime
|
|
|
|
import types
|
2012-09-20 20:44:34 +04:00
|
|
|
from decimal import Decimal
|
2013-01-27 00:54:41 +04:00
|
|
|
from django.core.paginator import Page
|
2012-10-02 19:16:49 +04:00
|
|
|
from django.db import models
|
2012-10-26 15:45:52 +04:00
|
|
|
from django.forms import widgets
|
2012-09-20 20:44:34 +04:00
|
|
|
from django.utils.datastructures import SortedDict
|
2013-03-09 02:43:46 +04:00
|
|
|
from rest_framework.compat import get_concrete_model, six
|
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(...)
|
|
|
|
#
|
|
|
|
# This helps keep the seperation between model fields, form fields, and
|
|
|
|
# serializer fields more explicit.
|
|
|
|
|
2012-12-31 12:53:40 +04:00
|
|
|
from rest_framework.relations import *
|
2012-09-20 16:06:27 +04:00
|
|
|
from rest_framework.fields import *
|
|
|
|
|
|
|
|
|
2013-03-12 17:33:02 +04:00
|
|
|
class NestedValidationError(ValidationError):
|
2013-03-12 22:35:20 +04:00
|
|
|
"""
|
|
|
|
The default ValidationError behavior is to stringify each item in the list
|
|
|
|
if the messages are a list of error messages.
|
|
|
|
|
|
|
|
In the case of nested serializers, where the parent has many children,
|
2013-03-13 07:59:25 +04:00
|
|
|
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.
|
2013-03-12 22:35:20 +04:00
|
|
|
|
|
|
|
We need to override the default behavior to get properly nested error dicts.
|
|
|
|
"""
|
|
|
|
|
2013-03-12 17:33:02 +04:00
|
|
|
def __init__(self, message):
|
2013-03-13 07:59:25 +04:00
|
|
|
if isinstance(message, dict):
|
|
|
|
self.messages = [message]
|
|
|
|
else:
|
|
|
|
self.messages = message
|
2013-03-12 17:33:02 +04:00
|
|
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
class DictWithMetadata(dict):
|
|
|
|
"""
|
|
|
|
A dict-like object, that can have additional properties attached.
|
|
|
|
"""
|
2012-12-06 04:43:47 +04:00
|
|
|
def __getstate__(self):
|
2012-12-06 23:45:50 +04:00
|
|
|
"""
|
|
|
|
Used by pickle (e.g., caching).
|
2013-02-23 02:59:55 +04:00
|
|
|
Overriden to remove the metadata from the dict, since it shouldn't be
|
|
|
|
pickled and may in some instances be unpickleable.
|
2012-12-06 04:43:47 +04:00
|
|
|
"""
|
2013-02-23 02:59:55 +04:00
|
|
|
return dict(self)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
2013-02-23 02:59:55 +04:00
|
|
|
class SortedDictWithMetadata(SortedDict):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
A sorted dict-like object, that can have additional properties attached.
|
|
|
|
"""
|
2013-02-23 02:59:55 +04:00
|
|
|
def __getstate__(self):
|
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
return SortedDict(self).__dict__
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
|
|
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,
|
2012-10-27 13:32:49 +04:00
|
|
|
int, long,
|
|
|
|
datetime.datetime, datetime.date, datetime.time,
|
|
|
|
float, Decimal,
|
|
|
|
basestring)
|
2012-09-20 16:06:27 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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').
|
|
|
|
|
|
|
|
Note that all fields from the base classes are used.
|
|
|
|
"""
|
|
|
|
fields = [(field_name, attrs.pop(field_name))
|
2012-11-22 11:30:32 +04:00
|
|
|
for field_name, obj in list(six.iteritems(attrs))
|
2012-09-20 16:06:27 +04:00
|
|
|
if isinstance(obj, Field)]
|
|
|
|
fields.sort(key=lambda x: x[1].creation_counter)
|
|
|
|
|
|
|
|
# If this class is subclassing another Serializer, add that Serializer's
|
|
|
|
# fields. Note that we loop over the bases in *reverse*. This is necessary
|
2012-11-22 22:50:29 +04:00
|
|
|
# in order to maintain the correct order of fields.
|
2012-09-20 16:06:27 +04:00
|
|
|
for base in bases[::-1]:
|
|
|
|
if hasattr(base, 'base_fields'):
|
2012-11-22 11:30:32 +04:00
|
|
|
fields = list(base.base_fields.items()) + fields
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
return SortedDict(fields)
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
class SerializerOptions(object):
|
|
|
|
"""
|
2012-10-01 18:49:19 +04:00
|
|
|
Meta class options for Serializer
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
def __init__(self, meta):
|
2012-10-29 00:43:43 +04:00
|
|
|
self.depth = getattr(meta, 'depth', 0)
|
2012-09-20 16:06:27 +04:00
|
|
|
self.fields = getattr(meta, 'fields', ())
|
|
|
|
self.exclude = getattr(meta, 'exclude', ())
|
|
|
|
|
|
|
|
|
2013-03-12 17:33:02 +04:00
|
|
|
class BaseSerializer(WritableField):
|
2013-01-31 00:38:11 +04:00
|
|
|
"""
|
|
|
|
This is the Serializer implementation.
|
|
|
|
We need to implement it as `BaseSerializer` due to metaclass magicks.
|
|
|
|
"""
|
2012-09-20 16:06:27 +04:00
|
|
|
class Meta(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
_options_class = SerializerOptions
|
2013-01-31 00:38:11 +04:00
|
|
|
_dict_class = SortedDictWithMetadata
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-12-13 00:44:55 +04:00
|
|
|
def __init__(self, instance=None, data=None, files=None,
|
2013-03-23 01:57:37 +04:00
|
|
|
context=None, partial=False, many=None,
|
2013-04-06 19:43:21 +04:00
|
|
|
allow_add_remove=False, **kwargs):
|
2013-03-18 05:27:15 +04:00
|
|
|
super(BaseSerializer, self).__init__(**kwargs)
|
2012-09-20 16:06:27 +04:00
|
|
|
self.opts = self._options_class(self.Meta)
|
|
|
|
self.parent = None
|
|
|
|
self.root = None
|
2012-11-20 23:01:21 +04:00
|
|
|
self.partial = partial
|
2013-01-31 21:06:23 +04:00
|
|
|
self.many = many
|
2013-04-06 19:43:21 +04:00
|
|
|
self.allow_add_remove = allow_add_remove
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
self.context = context or {}
|
|
|
|
|
|
|
|
self.init_data = data
|
2012-11-14 02:26:17 +04:00
|
|
|
self.init_files = files
|
2012-09-25 16:20:12 +04:00
|
|
|
self.object = instance
|
2012-11-22 22:50:29 +04:00
|
|
|
self.fields = self.get_fields()
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
self._data = None
|
2012-11-14 02:26:17 +04:00
|
|
|
self._files = None
|
2012-09-20 16:06:27 +04:00
|
|
|
self._errors = None
|
2013-03-19 18:26:48 +04:00
|
|
|
self._deleted = None
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2013-03-22 21:01:06 +04:00
|
|
|
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')
|
|
|
|
|
2013-04-06 19:43:21 +04:00
|
|
|
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')
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
#####
|
|
|
|
# Methods to determine which fields to use when (de)serializing objects.
|
|
|
|
|
2012-11-20 10:09:40 +04:00
|
|
|
def get_default_fields(self):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
Return the complete set of default fields for the object, as a dict.
|
|
|
|
"""
|
|
|
|
return {}
|
|
|
|
|
2012-11-20 10:09:40 +04:00
|
|
|
def get_fields(self):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
Returns the complete set of fields for the object as a dict.
|
|
|
|
|
|
|
|
This will be the set of any explicitly declared fields,
|
2012-11-20 10:09:40 +04:00
|
|
|
plus the set of fields returned by get_default_fields().
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
ret = SortedDict()
|
|
|
|
|
|
|
|
# Get the explicitly declared fields
|
2012-11-22 22:50:29 +04:00
|
|
|
base_fields = copy.deepcopy(self.base_fields)
|
|
|
|
for key, field in base_fields.items():
|
2012-09-20 16:06:27 +04:00
|
|
|
ret[key] = field
|
|
|
|
|
|
|
|
# Add in the default fields
|
2012-11-22 23:27:55 +04:00
|
|
|
default_fields = self.get_default_fields()
|
2012-11-23 00:22:30 +04:00
|
|
|
for key, val in default_fields.items():
|
2012-09-20 16:06:27 +04:00
|
|
|
if key not in ret:
|
|
|
|
ret[key] = val
|
|
|
|
|
|
|
|
# If 'fields' is specified, use those fields, in that order.
|
|
|
|
if self.opts.fields:
|
2013-02-23 02:02:42 +04:00
|
|
|
assert isinstance(self.opts.fields, (list, tuple)), '`include` must be a list or tuple'
|
2012-09-20 16:06:27 +04:00
|
|
|
new = SortedDict()
|
|
|
|
for key in self.opts.fields:
|
|
|
|
new[key] = ret[key]
|
|
|
|
ret = new
|
|
|
|
|
|
|
|
# Remove anything in 'exclude'
|
|
|
|
if self.opts.exclude:
|
2013-02-23 02:02:42 +04:00
|
|
|
assert isinstance(self.opts.fields, (list, tuple)), '`exclude` must be a list or tuple'
|
2012-09-20 16:06:27 +04:00
|
|
|
for key in self.opts.exclude:
|
|
|
|
ret.pop(key, None)
|
|
|
|
|
2012-12-18 01:59:51 +04:00
|
|
|
for key, field in ret.items():
|
|
|
|
field.initialize(parent=self, field_name=key)
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
return ret
|
|
|
|
|
|
|
|
#####
|
|
|
|
# Field methods - used when the serializer class is itself used as a field.
|
|
|
|
|
2012-11-05 16:51:04 +04:00
|
|
|
def initialize(self, parent, field_name):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
Same behaviour as usual Field, except that we need to keep track
|
2012-10-29 00:50:14 +04:00
|
|
|
of state so that we can deal with handling maximum depth.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-11-05 16:51:04 +04:00
|
|
|
super(BaseSerializer, self).initialize(parent, field_name)
|
2012-10-29 00:43:43 +04:00
|
|
|
if parent.opts.depth:
|
|
|
|
self.opts.depth = parent.opts.depth - 1
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
#####
|
2012-11-14 22:36:29 +04:00
|
|
|
# Methods to convert or revert from objects <--> primitive representations.
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
def get_field_key(self, field_name):
|
|
|
|
"""
|
|
|
|
Return the key that should be used for a given field.
|
|
|
|
"""
|
|
|
|
return field_name
|
|
|
|
|
2012-11-14 02:26:17 +04:00
|
|
|
def restore_fields(self, data, files):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
Core of deserialization, together with `restore_object`.
|
|
|
|
Converts a dictionary of data into a dictionary of deserialized fields.
|
|
|
|
"""
|
|
|
|
reverted_data = {}
|
2013-01-04 18:11:05 +04:00
|
|
|
|
|
|
|
if data is not None and not isinstance(data, dict):
|
2013-02-01 18:03:28 +04:00
|
|
|
self._errors['non_field_errors'] = ['Invalid data']
|
2013-01-04 18:11:05 +04:00
|
|
|
return None
|
|
|
|
|
2012-11-22 22:50:29 +04:00
|
|
|
for field_name, field in self.fields.items():
|
2012-12-14 23:59:21 +04:00
|
|
|
field.initialize(parent=self, field_name=field_name)
|
2012-09-20 16:06:27 +04:00
|
|
|
try:
|
2012-11-15 00:13:23 +04:00
|
|
|
field.field_from_native(data, files, field_name, reverted_data)
|
2012-09-20 16:06:27 +04:00
|
|
|
except ValidationError as err:
|
2013-01-18 23:47:57 +04:00
|
|
|
self._errors[field_name] = list(err.messages)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
return reverted_data
|
|
|
|
|
2012-10-24 15:12:27 +04:00
|
|
|
def perform_validation(self, attrs):
|
2012-10-24 12:28:10 +04:00
|
|
|
"""
|
2012-10-24 15:12:27 +04:00
|
|
|
Run `validate_<fieldname>()` and `validate()` methods on the serializer
|
2012-10-24 12:28:10 +04:00
|
|
|
"""
|
2012-11-22 22:50:29 +04:00
|
|
|
for field_name, field in self.fields.items():
|
2013-01-23 10:53:54 +04:00
|
|
|
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)
|
2012-10-24 12:28:10 +04:00
|
|
|
|
2012-12-12 01:07:25 +04:00
|
|
|
# If there are already errors, we don't run .validate() because
|
|
|
|
# field-validation failed and thus `attrs` may not be complete.
|
2012-11-28 02:21:12 +04:00
|
|
|
# which in turn can cause inconsistent validation errors.
|
|
|
|
if not self._errors:
|
|
|
|
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
|
2012-10-24 15:12:27 +04:00
|
|
|
|
2012-10-24 14:43:30 +04:00
|
|
|
return attrs
|
2012-10-24 12:28:10 +04:00
|
|
|
|
2012-10-24 15:12:27 +04:00
|
|
|
def validate(self, attrs):
|
2012-10-24 14:39:17 +04:00
|
|
|
"""
|
2012-10-24 15:12:27 +04:00
|
|
|
Stub method, to be overridden in Serializer subclasses
|
2012-10-24 14:39:17 +04:00
|
|
|
"""
|
|
|
|
return attrs
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
def restore_object(self, attrs, instance=None):
|
|
|
|
"""
|
|
|
|
Deserialize a dictionary of attributes into an object instance.
|
|
|
|
You should override this method to control how deserialized objects
|
|
|
|
are instantiated.
|
|
|
|
"""
|
2013-04-06 19:43:21 +04:00
|
|
|
removed_relations = []
|
|
|
|
|
|
|
|
# Deleted related objects
|
|
|
|
if self._deleted:
|
|
|
|
removed_relations = list(self._deleted)
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
if instance is not None:
|
|
|
|
instance.update(attrs)
|
2013-04-06 19:43:21 +04:00
|
|
|
instance._removed_relations = removed_relations
|
2012-09-20 16:06:27 +04:00
|
|
|
return instance
|
|
|
|
return attrs
|
|
|
|
|
|
|
|
def to_native(self, obj):
|
|
|
|
"""
|
2012-11-14 22:36:29 +04:00
|
|
|
Serialize objects -> primitives.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2013-01-31 21:06:23 +04:00
|
|
|
ret = self._dict_class()
|
|
|
|
ret.fields = {}
|
|
|
|
|
|
|
|
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
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-11-14 02:26:17 +04:00
|
|
|
def from_native(self, data, files):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-11-14 22:36:29 +04:00
|
|
|
Deserialize primitives -> objects.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
self._errors = {}
|
2012-11-14 02:26:17 +04:00
|
|
|
if data is not None or files is not None:
|
|
|
|
attrs = self.restore_fields(data, files)
|
2012-10-24 15:12:27 +04:00
|
|
|
attrs = self.perform_validation(attrs)
|
2012-10-10 19:41:08 +04:00
|
|
|
else:
|
2012-10-24 14:43:30 +04:00
|
|
|
self._errors['non_field_errors'] = ['No input provided']
|
2012-10-10 19:41:08 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
if not self._errors:
|
2012-09-25 16:20:12 +04:00
|
|
|
return self.restore_object(attrs, instance=getattr(self, 'object', None))
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-22 18:12:25 +04:00
|
|
|
def field_to_native(self, obj, field_name):
|
|
|
|
"""
|
2013-03-15 23:17:54 +04:00
|
|
|
Override default so that the serializer can be used as a nested field
|
|
|
|
across relationships.
|
2012-10-22 18:12:25 +04:00
|
|
|
"""
|
2013-01-27 00:54:03 +04:00
|
|
|
if self.source == '*':
|
|
|
|
return self.to_native(obj)
|
|
|
|
|
2013-01-05 00:11:03 +04:00
|
|
|
try:
|
2013-03-12 17:33:02 +04:00
|
|
|
source = self.source or field_name
|
|
|
|
value = obj
|
|
|
|
|
|
|
|
for component in source.split('.'):
|
|
|
|
value = get_component(value, component)
|
|
|
|
if value is None:
|
|
|
|
break
|
2013-01-05 00:11:03 +04:00
|
|
|
except ObjectDoesNotExist:
|
|
|
|
return None
|
2012-11-20 13:41:36 +04:00
|
|
|
|
2013-03-12 17:33:02 +04:00
|
|
|
if is_simple_callable(getattr(value, 'all', None)):
|
|
|
|
return [self.to_native(item) for item in value.all()]
|
2012-10-22 18:12:25 +04:00
|
|
|
|
2013-03-12 17:33:02 +04:00
|
|
|
if value is None:
|
2012-12-29 17:19:05 +04:00
|
|
|
return None
|
|
|
|
|
2013-01-31 21:06:23 +04:00
|
|
|
if self.many is not None:
|
|
|
|
many = self.many
|
|
|
|
else:
|
2013-03-12 17:33:02 +04:00
|
|
|
many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type))
|
2013-01-31 21:06:23 +04:00
|
|
|
|
|
|
|
if many:
|
2013-03-12 17:33:02 +04:00
|
|
|
return [self.to_native(item) for item in value]
|
|
|
|
return self.to_native(value)
|
|
|
|
|
|
|
|
def field_from_native(self, data, files, field_name, into):
|
2013-03-15 23:17:54 +04:00
|
|
|
"""
|
|
|
|
Override default so that the serializer can be used as a writable
|
|
|
|
nested field across relationships.
|
|
|
|
"""
|
2013-03-12 17:33:02 +04:00
|
|
|
if self.read_only:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
value = data[field_name]
|
|
|
|
except KeyError:
|
2013-03-18 07:05:34 +04:00
|
|
|
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'])
|
2013-03-18 05:50:08 +04:00
|
|
|
return
|
2013-03-12 17:33:02 +04:00
|
|
|
|
2013-03-13 07:59:25 +04:00
|
|
|
# Set the serializer object if it exists
|
|
|
|
obj = getattr(self.parent.object, field_name) if self.parent.object else None
|
2013-04-06 19:43:21 +04:00
|
|
|
obj = obj.all() if is_simple_callable(getattr(obj, 'all', None)) else obj
|
2013-03-12 17:33:02 +04:00
|
|
|
|
|
|
|
if value in (None, ''):
|
2013-03-15 23:17:54 +04:00
|
|
|
into[(self.source or field_name)] = None
|
2013-03-12 17:33:02 +04:00
|
|
|
else:
|
|
|
|
kwargs = {
|
2013-03-13 07:59:25 +04:00
|
|
|
'instance': obj,
|
2013-03-12 17:33:02 +04:00
|
|
|
'data': value,
|
|
|
|
'context': self.context,
|
|
|
|
'partial': self.partial,
|
2013-04-06 19:43:21 +04:00
|
|
|
'many': self.many,
|
|
|
|
'allow_add_remove': self.allow_add_remove
|
2013-03-12 17:33:02 +04:00
|
|
|
}
|
|
|
|
serializer = self.__class__(**kwargs)
|
|
|
|
|
|
|
|
if serializer.is_valid():
|
2013-03-15 23:22:31 +04:00
|
|
|
into[self.source or field_name] = serializer.object
|
2013-03-12 17:33:02 +04:00
|
|
|
else:
|
|
|
|
# Propagate errors up to our parent
|
|
|
|
raise NestedValidationError(serializer.errors)
|
2012-10-22 18:12:25 +04:00
|
|
|
|
2013-03-19 18:26:48 +04:00
|
|
|
def get_identity(self, data):
|
|
|
|
"""
|
|
|
|
This hook is required for bulk update.
|
|
|
|
It is used to determine the canonical identity of a given object.
|
2013-03-22 21:01:06 +04:00
|
|
|
|
|
|
|
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.
|
2013-03-19 18:26:48 +04:00
|
|
|
"""
|
2013-03-22 21:01:06 +04:00
|
|
|
try:
|
|
|
|
return data.get('id', None)
|
|
|
|
except AttributeError:
|
|
|
|
return None
|
2013-03-19 18:26:48 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
@property
|
|
|
|
def errors(self):
|
|
|
|
"""
|
|
|
|
Run deserialization and return error data,
|
2012-10-30 03:30:52 +04:00
|
|
|
setting self.object if no errors occurred.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
if self._errors is None:
|
2013-01-31 21:06:23 +04:00
|
|
|
data, files = self.init_data, self.init_files
|
|
|
|
|
|
|
|
if self.many is not None:
|
|
|
|
many = self.many
|
|
|
|
else:
|
2013-03-09 02:43:46 +04:00
|
|
|
many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type))
|
2013-02-12 17:53:45 +04:00
|
|
|
if many:
|
|
|
|
warnings.warn('Implict list/queryset serialization is due to be deprecated. '
|
|
|
|
'Use the `many=True` flag when instantiating the serializer.',
|
2013-02-13 00:07:35 +04:00
|
|
|
PendingDeprecationWarning, stacklevel=3)
|
2013-01-31 21:06:23 +04:00
|
|
|
|
2013-03-09 02:19:09 +04:00
|
|
|
if many:
|
|
|
|
ret = []
|
|
|
|
errors = []
|
2013-03-19 18:26:48 +04:00
|
|
|
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))
|
|
|
|
|
2013-03-22 21:01:06 +04:00
|
|
|
if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)):
|
2013-03-22 16:33:09 +04:00
|
|
|
for item in data:
|
|
|
|
if update:
|
|
|
|
# Determine which object we're updating
|
2013-03-22 21:01:06 +04:00
|
|
|
identity = self.get_identity(item)
|
|
|
|
self.object = identity_to_objects.pop(identity, None)
|
2013-03-22 16:33:09 +04:00
|
|
|
|
|
|
|
ret.append(self.from_native(item, None))
|
|
|
|
errors.append(self._errors)
|
2013-03-19 18:26:48 +04:00
|
|
|
|
2013-03-22 16:33:09 +04:00
|
|
|
if update:
|
|
|
|
self._deleted = identity_to_objects.values()
|
2013-03-19 18:26:48 +04:00
|
|
|
|
2013-03-22 16:33:09 +04:00
|
|
|
self._errors = any(errors) and errors or []
|
2013-03-22 21:01:06 +04:00
|
|
|
else:
|
|
|
|
self._errors = {'non_field_errors': ['Expected a list of items']}
|
2013-03-09 02:19:09 +04:00
|
|
|
else:
|
|
|
|
ret = self.from_native(data, files)
|
2013-01-31 21:06:23 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
if not self._errors:
|
2013-01-31 21:06:23 +04:00
|
|
|
self.object = ret
|
2013-03-09 02:43:46 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
return self._errors
|
|
|
|
|
|
|
|
def is_valid(self):
|
|
|
|
return not self.errors
|
|
|
|
|
|
|
|
@property
|
|
|
|
def data(self):
|
2013-01-31 00:38:11 +04:00
|
|
|
"""
|
|
|
|
Returns the serialized data on the serializer.
|
|
|
|
"""
|
2012-09-20 16:06:27 +04:00
|
|
|
if self._data is None:
|
2013-01-31 21:06:23 +04:00
|
|
|
obj = self.object
|
|
|
|
|
|
|
|
if self.many is not None:
|
|
|
|
many = self.many
|
|
|
|
else:
|
2013-02-07 16:57:40 +04:00
|
|
|
many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict))
|
2013-02-12 17:53:45 +04:00
|
|
|
if many:
|
|
|
|
warnings.warn('Implict list/queryset serialization is due to be deprecated. '
|
|
|
|
'Use the `many=True` flag when instantiating the serializer.',
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
2013-01-31 21:06:23 +04:00
|
|
|
|
|
|
|
if many:
|
|
|
|
self._data = [self.to_native(item) for item in obj]
|
|
|
|
else:
|
|
|
|
self._data = self.to_native(obj)
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
return self._data
|
|
|
|
|
2013-03-14 00:40:39 +04:00
|
|
|
def save_object(self, obj, **kwargs):
|
|
|
|
obj.save(**kwargs)
|
2013-03-09 14:21:53 +04:00
|
|
|
|
2013-04-06 19:43:21 +04:00
|
|
|
if self.allow_add_remove and hasattr(obj, '_removed_relations'):
|
|
|
|
[self.delete_object(item) for item in obj._removed_relations]
|
|
|
|
|
2013-03-19 18:26:48 +04:00
|
|
|
def delete_object(self, obj):
|
|
|
|
obj.delete()
|
|
|
|
|
2013-03-14 00:40:39 +04:00
|
|
|
def save(self, **kwargs):
|
2012-10-03 14:08:02 +04:00
|
|
|
"""
|
|
|
|
Save the deserialized object and return it.
|
|
|
|
"""
|
2013-03-09 14:21:53 +04:00
|
|
|
if isinstance(self.object, list):
|
2013-03-14 00:40:39 +04:00
|
|
|
[self.save_object(item, **kwargs) for item in self.object]
|
2013-03-09 14:21:53 +04:00
|
|
|
else:
|
2013-03-14 00:40:39 +04:00
|
|
|
self.save_object(self.object, **kwargs)
|
2013-03-19 18:26:48 +04:00
|
|
|
|
2013-04-06 19:43:21 +04:00
|
|
|
if self.allow_add_remove and self._deleted:
|
2013-03-19 18:26:48 +04:00
|
|
|
[self.delete_object(item) for item in self._deleted]
|
|
|
|
|
2012-10-03 14:08:02 +04:00
|
|
|
return self.object
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-11-22 11:30:32 +04:00
|
|
|
class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)):
|
|
|
|
pass
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
|
|
class ModelSerializerOptions(SerializerOptions):
|
|
|
|
"""
|
|
|
|
Meta class options for ModelSerializer
|
|
|
|
"""
|
|
|
|
def __init__(self, meta):
|
|
|
|
super(ModelSerializerOptions, self).__init__(meta)
|
|
|
|
self.model = getattr(meta, 'model', None)
|
2012-11-09 21:01:20 +04:00
|
|
|
self.read_only_fields = getattr(meta, 'read_only_fields', ())
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
2012-10-04 16:28:14 +04:00
|
|
|
class ModelSerializer(Serializer):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
A serializer that deals with model instances and querysets.
|
|
|
|
"""
|
|
|
|
_options_class = ModelSerializerOptions
|
|
|
|
|
2013-02-28 17:41:42 +04:00
|
|
|
field_mapping = {
|
|
|
|
models.AutoField: IntegerField,
|
|
|
|
models.FloatField: FloatField,
|
|
|
|
models.IntegerField: IntegerField,
|
|
|
|
models.PositiveIntegerField: IntegerField,
|
|
|
|
models.SmallIntegerField: IntegerField,
|
|
|
|
models.PositiveSmallIntegerField: IntegerField,
|
|
|
|
models.DateTimeField: DateTimeField,
|
|
|
|
models.DateField: DateField,
|
|
|
|
models.TimeField: TimeField,
|
|
|
|
models.EmailField: EmailField,
|
|
|
|
models.CharField: CharField,
|
|
|
|
models.URLField: URLField,
|
|
|
|
models.SlugField: SlugField,
|
|
|
|
models.TextField: CharField,
|
|
|
|
models.CommaSeparatedIntegerField: CharField,
|
|
|
|
models.BooleanField: BooleanField,
|
|
|
|
models.FileField: FileField,
|
|
|
|
models.ImageField: ImageField,
|
|
|
|
}
|
|
|
|
|
2012-11-20 10:09:40 +04:00
|
|
|
def get_default_fields(self):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
Return all the fields that should be serialized for the model.
|
|
|
|
"""
|
2012-10-09 20:51:32 +04:00
|
|
|
|
2012-09-28 18:54:00 +04:00
|
|
|
cls = self.opts.model
|
2013-02-26 12:38:57 +04:00
|
|
|
assert cls is not None, \
|
|
|
|
"Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__
|
2012-09-28 17:53:22 +04:00
|
|
|
opts = get_concrete_model(cls)._meta
|
2012-09-20 16:06:27 +04:00
|
|
|
pk_field = opts.pk
|
2013-03-11 11:23:44 +04:00
|
|
|
|
|
|
|
# If model is a child via multitable inheritance, use parent's pk
|
2013-03-12 00:01:14 +04:00
|
|
|
while pk_field.rel and pk_field.rel.parent_link:
|
2013-03-11 11:23:44 +04:00
|
|
|
pk_field = pk_field.rel.to._meta.pk
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
fields = [pk_field]
|
|
|
|
fields += [field for field in opts.fields if field.serialize]
|
|
|
|
fields += [field for field in opts.many_to_many if field.serialize]
|
|
|
|
|
|
|
|
ret = SortedDict()
|
2012-11-20 10:09:40 +04:00
|
|
|
nested = bool(self.opts.depth)
|
2012-10-02 18:37:13 +04:00
|
|
|
is_pk = True # First field in the list is the pk
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
for model_field in fields:
|
2012-10-02 18:37:13 +04:00
|
|
|
if is_pk:
|
|
|
|
field = self.get_pk_field(model_field)
|
|
|
|
is_pk = False
|
|
|
|
elif model_field.rel and nested:
|
2012-09-20 16:06:27 +04:00
|
|
|
field = self.get_nested_field(model_field)
|
|
|
|
elif model_field.rel:
|
2012-10-09 20:49:04 +04:00
|
|
|
to_many = isinstance(model_field,
|
|
|
|
models.fields.related.ManyToManyField)
|
|
|
|
field = self.get_related_field(model_field, to_many=to_many)
|
2012-09-20 16:06:27 +04:00
|
|
|
else:
|
|
|
|
field = self.get_field(model_field)
|
2012-10-02 18:37:13 +04:00
|
|
|
|
2012-10-03 12:26:15 +04:00
|
|
|
if field:
|
2012-10-02 18:37:13 +04:00
|
|
|
ret[model_field.name] = field
|
|
|
|
|
2012-11-09 21:01:20 +04:00
|
|
|
for field_name in self.opts.read_only_fields:
|
|
|
|
assert field_name in ret, \
|
|
|
|
"read_only_fields on '%s' included invalid item '%s'" % \
|
|
|
|
(self.__class__.__name__, field_name)
|
|
|
|
ret[field_name].read_only = True
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
return ret
|
|
|
|
|
2012-10-02 18:37:13 +04:00
|
|
|
def get_pk_field(self, model_field):
|
|
|
|
"""
|
|
|
|
Returns a default instance of the pk field.
|
|
|
|
"""
|
2013-01-12 13:43:07 +04:00
|
|
|
return self.get_field(model_field)
|
2012-10-02 18:37:13 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
def get_nested_field(self, model_field):
|
|
|
|
"""
|
|
|
|
Creates a default instance of a nested relational field.
|
|
|
|
"""
|
2012-11-13 15:47:32 +04:00
|
|
|
class NestedModelSerializer(ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = model_field.rel.to
|
|
|
|
return NestedModelSerializer()
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-09 20:49:04 +04:00
|
|
|
def get_related_field(self, model_field, to_many=False):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
Creates a default instance of a flat relational field.
|
|
|
|
"""
|
2012-10-04 18:00:23 +04:00
|
|
|
# TODO: filter queryset using:
|
|
|
|
# .using(db).complex_filter(self.rel.limit_choices_to)
|
2012-12-08 01:32:39 +04:00
|
|
|
kwargs = {
|
2013-01-30 17:41:56 +04:00
|
|
|
'required': not(model_field.null or model_field.blank),
|
2013-02-07 13:14:58 +04:00
|
|
|
'queryset': model_field.rel.to._default_manager,
|
|
|
|
'many': to_many
|
2012-12-08 01:32:39 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return PrimaryKeyRelatedField(**kwargs)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
def get_field(self, model_field):
|
|
|
|
"""
|
2012-10-02 18:37:13 +04:00
|
|
|
Creates a default instance of a basic non-relational field.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-24 23:58:10 +04:00
|
|
|
kwargs = {}
|
2013-02-07 13:24:34 +04:00
|
|
|
has_default = model_field.has_default()
|
2012-10-30 15:03:03 +04:00
|
|
|
|
2013-02-07 13:24:34 +04:00
|
|
|
if model_field.null or model_field.blank or has_default:
|
2012-10-30 03:09:40 +04:00
|
|
|
kwargs['required'] = False
|
|
|
|
|
2013-01-12 13:43:07 +04:00
|
|
|
if isinstance(model_field, models.AutoField) or not model_field.editable:
|
2013-01-12 13:32:53 +04:00
|
|
|
kwargs['read_only'] = True
|
|
|
|
|
2013-02-07 13:24:34 +04:00
|
|
|
if has_default:
|
2012-10-26 16:20:30 +04:00
|
|
|
kwargs['default'] = model_field.get_default()
|
2012-10-26 15:45:52 +04:00
|
|
|
|
2013-01-29 01:08:40 +04:00
|
|
|
if issubclass(model_field.__class__, models.TextField):
|
2012-10-26 15:45:52 +04:00
|
|
|
kwargs['widget'] = widgets.Textarea
|
|
|
|
|
|
|
|
# TODO: TypedChoiceField?
|
|
|
|
if model_field.flatchoices: # This ModelField contains choices
|
|
|
|
kwargs['choices'] = model_field.flatchoices
|
|
|
|
return ChoiceField(**kwargs)
|
2012-10-24 23:58:10 +04:00
|
|
|
|
2012-10-02 19:16:49 +04:00
|
|
|
try:
|
2013-02-28 17:41:42 +04:00
|
|
|
return self.field_mapping[model_field.__class__](**kwargs)
|
2012-10-02 19:16:49 +04:00
|
|
|
except KeyError:
|
2012-10-24 23:58:10 +04:00
|
|
|
return ModelField(model_field=model_field, **kwargs)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-12-16 00:40:41 +04:00
|
|
|
def get_validation_exclusions(self):
|
|
|
|
"""
|
|
|
|
Return a list of field names to exclude from model validation.
|
|
|
|
"""
|
|
|
|
cls = self.opts.model
|
|
|
|
opts = get_concrete_model(cls)._meta
|
|
|
|
exclusions = [field.name for field in opts.fields + opts.many_to_many]
|
2013-03-25 21:28:23 +04:00
|
|
|
|
2012-12-16 00:40:41 +04:00
|
|
|
for field_name, field in self.fields.items():
|
2013-02-23 02:13:06 +04:00
|
|
|
field_name = field.source or field_name
|
2013-03-25 21:28:23 +04:00
|
|
|
if field_name in exclusions \
|
|
|
|
and not field.read_only \
|
|
|
|
and not isinstance(field, Serializer):
|
2012-12-16 00:40:41 +04:00
|
|
|
exclusions.remove(field_name)
|
|
|
|
return exclusions
|
|
|
|
|
2013-01-28 16:56:42 +04:00
|
|
|
def full_clean(self, instance):
|
|
|
|
"""
|
|
|
|
Perform Django's full_clean, and populate the `errors` dictionary
|
|
|
|
if any validation errors occur.
|
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
instance.full_clean(exclude=self.get_validation_exclusions())
|
2013-02-01 18:03:28 +04:00
|
|
|
except ValidationError as err:
|
2013-01-28 16:56:42 +04:00
|
|
|
self._errors = err.message_dict
|
|
|
|
return None
|
|
|
|
return instance
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
def restore_object(self, attrs, instance=None):
|
|
|
|
"""
|
|
|
|
Restore the model instance.
|
|
|
|
"""
|
2013-03-15 23:17:54 +04:00
|
|
|
m2m_data = {}
|
|
|
|
related_data = {}
|
2013-03-25 21:28:23 +04:00
|
|
|
nested_forward_relations = {}
|
2013-04-06 19:43:21 +04:00
|
|
|
removed_relations = []
|
2013-03-16 11:32:50 +04:00
|
|
|
meta = self.opts.model._meta
|
2012-10-08 15:52:56 +04:00
|
|
|
|
2013-03-16 11:32:50 +04:00
|
|
|
# Reverse fk or one-to-one relations
|
|
|
|
for (obj, model) in meta.get_all_related_objects_with_model():
|
2012-12-31 17:33:24 +04:00
|
|
|
field_name = obj.field.related_query_name()
|
|
|
|
if field_name in attrs:
|
2013-03-15 23:17:54 +04:00
|
|
|
related_data[field_name] = attrs.pop(field_name)
|
2012-12-31 17:33:24 +04:00
|
|
|
|
|
|
|
# Reverse m2m relations
|
2013-03-16 11:32:50 +04:00
|
|
|
for (obj, model) in meta.get_all_related_m2m_objects_with_model():
|
2012-12-31 17:33:24 +04:00
|
|
|
field_name = obj.field.related_query_name()
|
|
|
|
if field_name in attrs:
|
2013-03-15 23:17:54 +04:00
|
|
|
m2m_data[field_name] = attrs.pop(field_name)
|
2012-12-31 17:33:24 +04:00
|
|
|
|
|
|
|
# Forward m2m relations
|
2013-03-16 11:32:50 +04:00
|
|
|
for field in meta.many_to_many:
|
2012-12-31 17:33:24 +04:00
|
|
|
if field.name in attrs:
|
2013-03-15 23:17:54 +04:00
|
|
|
m2m_data[field.name] = attrs.pop(field.name)
|
2012-12-31 17:33:24 +04:00
|
|
|
|
2013-03-25 21:28:23 +04:00
|
|
|
# Nested forward relations - These need to be marked so we can save
|
|
|
|
# them before saving the parent model instance.
|
|
|
|
for field_name in attrs.keys():
|
|
|
|
if isinstance(self.fields.get(field_name, None), Serializer):
|
|
|
|
nested_forward_relations[field_name] = attrs[field_name]
|
|
|
|
|
2013-04-06 19:43:21 +04:00
|
|
|
# Deleted related objects
|
|
|
|
if self._deleted:
|
|
|
|
removed_relations = list(self._deleted)
|
|
|
|
|
2013-03-16 11:32:50 +04:00
|
|
|
# Update an existing instance...
|
2012-11-21 17:58:33 +04:00
|
|
|
if instance is not None:
|
2012-09-28 18:54:00 +04:00
|
|
|
for key, val in attrs.items():
|
|
|
|
setattr(instance, key, val)
|
|
|
|
|
2013-03-16 11:32:50 +04:00
|
|
|
# ...or create a new instance
|
2012-12-18 22:21:58 +04:00
|
|
|
else:
|
|
|
|
instance = self.opts.model(**attrs)
|
2012-12-12 01:07:25 +04:00
|
|
|
|
2013-03-16 11:32:50 +04:00
|
|
|
# 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.
|
2013-03-15 23:17:54 +04:00
|
|
|
instance._related_data = related_data
|
|
|
|
instance._m2m_data = m2m_data
|
2013-03-25 21:28:23 +04:00
|
|
|
instance._nested_forward_relations = nested_forward_relations
|
2013-04-06 19:43:21 +04:00
|
|
|
instance._removed_relations = removed_relations
|
2013-03-15 23:17:54 +04:00
|
|
|
|
2012-12-12 01:07:25 +04:00
|
|
|
return instance
|
2012-10-03 14:08:02 +04:00
|
|
|
|
2013-01-28 16:56:42 +04:00
|
|
|
def from_native(self, data, files):
|
|
|
|
"""
|
|
|
|
Override the default method to also include model field validation.
|
|
|
|
"""
|
|
|
|
instance = super(ModelSerializer, self).from_native(data, files)
|
|
|
|
if instance:
|
|
|
|
return self.full_clean(instance)
|
|
|
|
|
2013-03-15 23:57:57 +04:00
|
|
|
def save_object(self, obj, **kwargs):
|
2013-01-18 23:47:57 +04:00
|
|
|
"""
|
|
|
|
Save the deserialized object and return it.
|
|
|
|
"""
|
2013-03-25 21:28:23 +04:00
|
|
|
if getattr(obj, '_nested_forward_relations', None):
|
|
|
|
# Nested relationships need to be saved before we can save the
|
|
|
|
# parent instance.
|
|
|
|
for field_name, sub_object in obj._nested_forward_relations.items():
|
|
|
|
self.save_object(sub_object)
|
|
|
|
setattr(obj, field_name, sub_object)
|
|
|
|
|
2013-03-15 23:17:54 +04:00
|
|
|
obj.save(**kwargs)
|
2012-10-08 15:52:56 +04:00
|
|
|
|
2013-04-06 19:43:21 +04:00
|
|
|
if self.allow_add_remove and hasattr(obj, '_removed_relations'):
|
|
|
|
[self.delete_object(item) for item in obj._removed_relations]
|
|
|
|
|
2013-03-15 23:17:54 +04:00
|
|
|
if getattr(obj, '_m2m_data', None):
|
|
|
|
for accessor_name, object_list in obj._m2m_data.items():
|
2013-03-16 11:35:27 +04:00
|
|
|
setattr(obj, accessor_name, object_list)
|
|
|
|
del(obj._m2m_data)
|
2013-03-15 23:17:54 +04:00
|
|
|
|
|
|
|
if getattr(obj, '_related_data', None):
|
|
|
|
for accessor_name, related in obj._related_data.items():
|
2013-03-25 21:28:23 +04:00
|
|
|
field = self.fields.get(accessor_name, None)
|
|
|
|
if isinstance(field, Serializer):
|
2013-04-06 19:43:21 +04:00
|
|
|
if field.many:
|
|
|
|
# Nested reverse fk relationship
|
|
|
|
for related_item in related:
|
|
|
|
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
|
|
|
|
setattr(related_item, fk_field, obj)
|
|
|
|
self.save_object(related_item)
|
|
|
|
else:
|
|
|
|
# Nested reverse one-one relationship
|
|
|
|
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
|
|
|
|
setattr(related, fk_field, obj)
|
|
|
|
self.save_object(related)
|
2013-03-23 02:39:45 +04:00
|
|
|
else:
|
2013-03-25 21:28:23 +04:00
|
|
|
# Reverse FK or reverse one-one
|
2013-03-23 02:39:45 +04:00
|
|
|
setattr(obj, accessor_name, related)
|
2013-03-16 11:35:27 +04:00
|
|
|
del(obj._related_data)
|
2013-01-18 23:47:57 +04:00
|
|
|
|
2012-10-04 14:26:41 +04:00
|
|
|
|
|
|
|
class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
|
|
|
|
"""
|
|
|
|
Options for HyperlinkedModelSerializer
|
|
|
|
"""
|
|
|
|
def __init__(self, meta):
|
|
|
|
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
|
|
|
|
self.view_name = getattr(meta, 'view_name', None)
|
|
|
|
|
|
|
|
|
|
|
|
class HyperlinkedModelSerializer(ModelSerializer):
|
|
|
|
"""
|
2013-01-31 21:06:23 +04:00
|
|
|
A subclass of ModelSerializer that uses hyperlinked relationships,
|
|
|
|
instead of primary key relationships.
|
2012-10-04 14:26:41 +04:00
|
|
|
"""
|
|
|
|
_options_class = HyperlinkedModelSerializerOptions
|
|
|
|
_default_view_name = '%(model_name)s-detail'
|
|
|
|
|
|
|
|
url = HyperlinkedIdentityField()
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs)
|
|
|
|
if self.opts.view_name is None:
|
2012-10-04 19:58:18 +04:00
|
|
|
self.opts.view_name = self._get_default_view_name(self.opts.model)
|
2012-10-04 14:26:41 +04:00
|
|
|
|
2012-10-04 19:58:18 +04:00
|
|
|
def _get_default_view_name(self, model):
|
2012-10-04 14:26:41 +04:00
|
|
|
"""
|
|
|
|
Return the view name to use if 'view_name' is not specified in 'Meta'
|
|
|
|
"""
|
2012-10-04 19:58:18 +04:00
|
|
|
model_meta = model._meta
|
2012-10-04 14:26:41 +04:00
|
|
|
format_kwargs = {
|
|
|
|
'app_label': model_meta.app_label,
|
|
|
|
'model_name': model_meta.object_name.lower()
|
|
|
|
}
|
|
|
|
return self._default_view_name % format_kwargs
|
|
|
|
|
|
|
|
def get_pk_field(self, model_field):
|
|
|
|
return None
|
2012-10-04 19:58:18 +04:00
|
|
|
|
2012-10-09 20:49:04 +04:00
|
|
|
def get_related_field(self, model_field, to_many):
|
2012-10-04 19:58:18 +04:00
|
|
|
"""
|
|
|
|
Creates a default instance of a flat relational field.
|
|
|
|
"""
|
|
|
|
# TODO: filter queryset using:
|
|
|
|
# .using(db).complex_filter(self.rel.limit_choices_to)
|
|
|
|
rel = model_field.rel.to
|
|
|
|
kwargs = {
|
2013-01-30 17:41:56 +04:00
|
|
|
'required': not(model_field.null or model_field.blank),
|
2012-12-08 01:32:39 +04:00
|
|
|
'queryset': rel._default_manager,
|
2013-02-07 13:14:58 +04:00
|
|
|
'view_name': self._get_default_view_name(rel),
|
|
|
|
'many': to_many
|
2012-10-04 19:58:18 +04:00
|
|
|
}
|
|
|
|
return HyperlinkedRelatedField(**kwargs)
|
2013-03-22 21:01:06 +04:00
|
|
|
|
|
|
|
def get_identity(self, data):
|
|
|
|
"""
|
|
|
|
This hook is required for bulk update.
|
|
|
|
We need to override the default, to use the url as the identity.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return data.get('url', None)
|
|
|
|
except AttributeError:
|
|
|
|
return None
|