serializer_helpers.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. from collections import OrderedDict
  2. from collections.abc import Mapping, MutableMapping
  3. from django.utils.encoding import force_str
  4. from rest_framework.utils import json
  5. class ReturnDict(OrderedDict):
  6. """
  7. Return object from `serializer.data` for the `Serializer` class.
  8. Includes a backlink to the serializer instance for renderers
  9. to use if they need richer field information.
  10. """
  11. def __init__(self, *args, **kwargs):
  12. self.serializer = kwargs.pop('serializer')
  13. super().__init__(*args, **kwargs)
  14. def copy(self):
  15. return ReturnDict(self, serializer=self.serializer)
  16. def __repr__(self):
  17. return dict.__repr__(self)
  18. def __reduce__(self):
  19. # Pickling these objects will drop the .serializer backlink,
  20. # but preserve the raw data.
  21. return (dict, (dict(self),))
  22. class ReturnList(list):
  23. """
  24. Return object from `serializer.data` for the `SerializerList` class.
  25. Includes a backlink to the serializer instance for renderers
  26. to use if they need richer field information.
  27. """
  28. def __init__(self, *args, **kwargs):
  29. self.serializer = kwargs.pop('serializer')
  30. super().__init__(*args, **kwargs)
  31. def __repr__(self):
  32. return list.__repr__(self)
  33. def __reduce__(self):
  34. # Pickling these objects will drop the .serializer backlink,
  35. # but preserve the raw data.
  36. return (list, (list(self),))
  37. class BoundField:
  38. """
  39. A field object that also includes `.value` and `.error` properties.
  40. Returned when iterating over a serializer instance,
  41. providing an API similar to Django forms and form fields.
  42. """
  43. def __init__(self, field, value, errors, prefix=''):
  44. self._field = field
  45. self._prefix = prefix
  46. self.value = value
  47. self.errors = errors
  48. self.name = prefix + self.field_name
  49. def __getattr__(self, attr_name):
  50. return getattr(self._field, attr_name)
  51. @property
  52. def _proxy_class(self):
  53. return self._field.__class__
  54. def __repr__(self):
  55. return '<%s value=%s errors=%s>' % (
  56. self.__class__.__name__, self.value, self.errors
  57. )
  58. def as_form_field(self):
  59. value = '' if (self.value is None or self.value is False) else self.value
  60. return self.__class__(self._field, value, self.errors, self._prefix)
  61. class JSONBoundField(BoundField):
  62. def as_form_field(self):
  63. value = self.value
  64. # When HTML form input is used and the input is not valid
  65. # value will be a JSONString, rather than a JSON primitive.
  66. if not getattr(value, 'is_json_string', False):
  67. try:
  68. value = json.dumps(
  69. self.value,
  70. sort_keys=True,
  71. indent=4,
  72. separators=(',', ': '),
  73. )
  74. except (TypeError, ValueError):
  75. pass
  76. return self.__class__(self._field, value, self.errors, self._prefix)
  77. class NestedBoundField(BoundField):
  78. """
  79. This `BoundField` additionally implements __iter__ and __getitem__
  80. in order to support nested bound fields. This class is the type of
  81. `BoundField` that is used for serializer fields.
  82. """
  83. def __init__(self, field, value, errors, prefix=''):
  84. if value is None or value == '' or not isinstance(value, Mapping):
  85. value = {}
  86. super().__init__(field, value, errors, prefix)
  87. def __iter__(self):
  88. for field in self.fields.values():
  89. yield self[field.field_name]
  90. def __getitem__(self, key):
  91. field = self.fields[key]
  92. value = self.value.get(key) if self.value else None
  93. error = self.errors.get(key) if isinstance(self.errors, dict) else None
  94. if hasattr(field, 'fields'):
  95. return NestedBoundField(field, value, error, prefix=self.name + '.')
  96. elif getattr(field, '_is_jsonfield', False):
  97. return JSONBoundField(field, value, error, prefix=self.name + '.')
  98. return BoundField(field, value, error, prefix=self.name + '.')
  99. def as_form_field(self):
  100. values = {}
  101. for key, value in self.value.items():
  102. if isinstance(value, (list, dict)):
  103. values[key] = value
  104. else:
  105. values[key] = '' if (value is None or value is False) else force_str(value)
  106. return self.__class__(self._field, values, self.errors, self._prefix)
  107. class BindingDict(MutableMapping):
  108. """
  109. This dict-like object is used to store fields on a serializer.
  110. This ensures that whenever fields are added to the serializer we call
  111. `field.bind()` so that the `field_name` and `parent` attributes
  112. can be set correctly.
  113. """
  114. def __init__(self, serializer):
  115. self.serializer = serializer
  116. self.fields = OrderedDict()
  117. def __setitem__(self, key, field):
  118. self.fields[key] = field
  119. field.bind(field_name=key, parent=self.serializer)
  120. def __getitem__(self, key):
  121. return self.fields[key]
  122. def __delitem__(self, key):
  123. del self.fields[key]
  124. def __iter__(self):
  125. return iter(self.fields)
  126. def __len__(self):
  127. return len(self.fields)
  128. def __repr__(self):
  129. return dict.__repr__(self.fields)