2015-09-22 17:35:38 +03:00
|
|
|
from collections import OrderedDict
|
2021-03-09 14:34:18 +03:00
|
|
|
from collections.abc import Mapping, MutableMapping
|
2015-06-18 16:38:29 +03:00
|
|
|
|
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):
|
|
|
|
"""
|
2016-05-17 03:39:13 +03:00
|
|
|
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')
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(*args, **kwargs)
|
2014-11-07 13:13:46 +03:00
|
|
|
|
2014-11-07 14:16:26 +03:00
|
|
|
def copy(self):
|
|
|
|
return ReturnDict(self, serializer=self.serializer)
|
|
|
|
|
2015-01-19 17:21:09 +03:00
|
|
|
def __repr__(self):
|
|
|
|
return dict.__repr__(self)
|
|
|
|
|
2015-01-21 17:18:13 +03:00
|
|
|
def __reduce__(self):
|
|
|
|
# Pickling these objects will drop the .serializer backlink,
|
|
|
|
# but preserve the raw data.
|
|
|
|
return (dict, (dict(self),))
|
|
|
|
|
2014-11-07 13:13:46 +03:00
|
|
|
|
|
|
|
class ReturnList(list):
|
|
|
|
"""
|
2016-05-17 03:39:13 +03:00
|
|
|
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')
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(*args, **kwargs)
|
2014-11-07 13:13:46 +03:00
|
|
|
|
2015-01-19 17:21:09 +03:00
|
|
|
def __repr__(self):
|
|
|
|
return list.__repr__(self)
|
|
|
|
|
2015-01-21 17:18:13 +03:00
|
|
|
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
|
|
|
|
2019-04-30 18:53:44 +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
|
2015-07-14 17:47:13 +03:00
|
|
|
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):
|
2019-04-30 18:53:44 +03:00
|
|
|
return '<%s value=%s errors=%s>' % (
|
2014-11-07 13:13:46 +03:00
|
|
|
self.__class__.__name__, self.value, self.errors
|
2019-04-30 18:53:44 +03:00
|
|
|
)
|
2014-11-07 13:13:46 +03:00
|
|
|
|
2015-07-14 17:47:13 +03:00
|
|
|
def as_form_field(self):
|
2016-08-10 14:02:33 +03:00
|
|
|
value = '' if (self.value is None or self.value is False) else self.value
|
2015-07-16 14:04:38 +03:00
|
|
|
return self.__class__(self._field, value, self.errors, self._prefix)
|
2015-07-14 17:47:13 +03:00
|
|
|
|
2014-11-07 13:13:46 +03:00
|
|
|
|
2017-04-02 03:31:27 +03:00
|
|
|
class JSONBoundField(BoundField):
|
|
|
|
def as_form_field(self):
|
2017-04-26 20:48:49 +03:00
|
|
|
value = self.value
|
2017-10-25 11:54:38 +03:00
|
|
|
# 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):
|
|
|
|
try:
|
2021-03-15 13:44:03 +03:00
|
|
|
value = json.dumps(
|
|
|
|
self.value,
|
|
|
|
sort_keys=True,
|
|
|
|
indent=4,
|
|
|
|
separators=(',', ': '),
|
|
|
|
)
|
2017-10-25 11:54:38 +03:00
|
|
|
except (TypeError, ValueError):
|
|
|
|
pass
|
2017-04-02 03:31:27 +03:00
|
|
|
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
|
|
|
|
2015-08-11 21:27:41 +03:00
|
|
|
def __init__(self, field, value, errors, prefix=''):
|
2021-03-09 14:34:18 +03:00
|
|
|
if value is None or value == '' or not isinstance(value, Mapping):
|
2015-08-11 21:27:41 +03:00
|
|
|
value = {}
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(field, value, errors, prefix)
|
2015-08-11 21:27:41 +03:00
|
|
|
|
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
|
2017-04-19 03:18:52 +03:00
|
|
|
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 + '.')
|
2021-03-15 13:44:03 +03:00
|
|
|
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 + '.')
|
|
|
|
|
2015-07-16 14:04:38 +03:00
|
|
|
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)
|
2015-07-16 14:04:38 +03:00
|
|
|
return self.__class__(self._field, values, self.errors, self._prefix)
|
|
|
|
|
2014-11-07 13:13:46 +03:00
|
|
|
|
2019-02-25 11:17:04 +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]
|
|
|
|
|
2014-12-02 18:15:21 +03:00
|
|
|
def __iter__(self):
|
|
|
|
return iter(self.fields)
|
2014-11-07 13:13:46 +03:00
|
|
|
|
2014-12-02 18:15:21 +03:00
|
|
|
def __len__(self):
|
|
|
|
return len(self.fields)
|
2015-01-21 16:03:37 +03:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return dict.__repr__(self.fields)
|