django-rest-framework/rest_framework/utils/serializer_helpers.py

184 lines
5.8 KiB
Python
Raw Normal View History

import contextlib
import sys
2015-09-22 17:35:38 +03:00
from collections import OrderedDict
from collections.abc import Mapping, MutableMapping
2019-07-30 03:02:43 +03:00
from django.utils.encoding import force_str
2015-08-07 00:51:35 +03:00
2017-07-07 19:47:08 +03:00
from rest_framework.utils import json
2014-11-07 13:13:46 +03:00
class ReturnDict(OrderedDict):
"""
Return object from `serializer.data` for the `Serializer` class.
2014-11-07 13:13:46 +03:00
Includes a backlink to the serializer instance for renderers
to use if they need richer field information.
"""
2015-08-07 00:51:35 +03:00
2014-11-07 13:13:46 +03:00
def __init__(self, *args, **kwargs):
self.serializer = kwargs.pop('serializer')
super().__init__(*args, **kwargs)
2014-11-07 13:13:46 +03:00
def copy(self):
return ReturnDict(self, serializer=self.serializer)
def __repr__(self):
return dict.__repr__(self)
def __reduce__(self):
# Pickling these objects will drop the .serializer backlink,
# but preserve the raw data.
return (dict, (dict(self),))
if sys.version_info >= (3, 9):
# These are basically copied from OrderedDict, with `serializer` added.
def __or__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(self, serializer=self.serializer)
new.update(other)
return new
def __ror__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(other, serializer=self.serializer)
new.update(self)
return new
2014-11-07 13:13:46 +03:00
class ReturnList(list):
"""
Return object from `serializer.data` for the `SerializerList` class.
2014-11-07 13:13:46 +03:00
Includes a backlink to the serializer instance for renderers
to use if they need richer field information.
"""
2015-08-07 00:51:35 +03:00
2014-11-07 13:13:46 +03:00
def __init__(self, *args, **kwargs):
self.serializer = kwargs.pop('serializer')
super().__init__(*args, **kwargs)
2014-11-07 13:13:46 +03:00
def __repr__(self):
return list.__repr__(self)
def __reduce__(self):
# Pickling these objects will drop the .serializer backlink,
# but preserve the raw data.
return (list, (list(self),))
2014-11-07 13:13:46 +03:00
class BoundField:
2014-11-07 13:13:46 +03:00
"""
A field object that also includes `.value` and `.error` properties.
Returned when iterating over a serializer instance,
providing an API similar to Django forms and form fields.
"""
2015-08-07 00:51:35 +03:00
2014-11-07 13:13:46 +03:00
def __init__(self, field, value, errors, prefix=''):
self._field = field
self._prefix = prefix
2014-11-07 13:13:46 +03:00
self.value = value
self.errors = errors
self.name = prefix + self.field_name
def __getattr__(self, attr_name):
return getattr(self._field, attr_name)
@property
def _proxy_class(self):
return self._field.__class__
def __repr__(self):
return '<%s value=%s errors=%s>' % (
2014-11-07 13:13:46 +03:00
self.__class__.__name__, self.value, self.errors
)
2014-11-07 13:13:46 +03:00
def as_form_field(self):
value = '' if (self.value is None or self.value is False) else self.value
return self.__class__(self._field, value, self.errors, self._prefix)
2014-11-07 13:13:46 +03:00
class JSONBoundField(BoundField):
def as_form_field(self):
value = self.value
# When HTML form input is used and the input is not valid
# value will be a JSONString, rather than a JSON primitive.
if not getattr(value, 'is_json_string', False):
with contextlib.suppress(TypeError, ValueError):
value = json.dumps(
self.value,
sort_keys=True,
indent=4,
separators=(',', ': '),
)
return self.__class__(self._field, value, self.errors, self._prefix)
2014-11-07 13:13:46 +03:00
class NestedBoundField(BoundField):
"""
This `BoundField` additionally implements __iter__ and __getitem__
in order to support nested bound fields. This class is the type of
`BoundField` that is used for serializer fields.
"""
2015-08-07 00:51:35 +03:00
def __init__(self, field, value, errors, prefix=''):
if value is None or value == '' or not isinstance(value, Mapping):
value = {}
super().__init__(field, value, errors, prefix)
2014-11-07 13:13:46 +03:00
def __iter__(self):
for field in self.fields.values():
yield self[field.field_name]
def __getitem__(self, key):
field = self.fields[key]
value = self.value.get(key) if self.value else None
error = self.errors.get(key) if isinstance(self.errors, dict) else None
2014-11-07 13:13:46 +03:00
if hasattr(field, 'fields'):
return NestedBoundField(field, value, error, prefix=self.name + '.')
elif getattr(field, '_is_jsonfield', False):
return JSONBoundField(field, value, error, prefix=self.name + '.')
2014-11-07 13:13:46 +03:00
return BoundField(field, value, error, prefix=self.name + '.')
def as_form_field(self):
values = {}
for key, value in self.value.items():
if isinstance(value, (list, dict)):
values[key] = value
else:
2019-07-30 03:02:43 +03:00
values[key] = '' if (value is None or value is False) else force_str(value)
return self.__class__(self._field, values, self.errors, self._prefix)
2014-11-07 13:13:46 +03:00
class BindingDict(MutableMapping):
2014-11-07 13:13:46 +03:00
"""
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.
"""
2015-08-07 00:51:35 +03:00
2014-11-07 13:13:46 +03:00
def __init__(self, serializer):
self.serializer = serializer
self.fields = OrderedDict()
def __setitem__(self, key, field):
self.fields[key] = field
field.bind(field_name=key, parent=self.serializer)
def __getitem__(self, key):
return self.fields[key]
def __delitem__(self, key):
del self.fields[key]
def __iter__(self):
return iter(self.fields)
2014-11-07 13:13:46 +03:00
def __len__(self):
return len(self.fields)
def __repr__(self):
return dict.__repr__(self.fields)