mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-02 11:30:12 +03:00
Handle dotted field.source in writable serializers.
This commit is contained in:
parent
f84d4951bf
commit
0ab70ba170
|
@ -60,6 +60,18 @@ def get_component(obj, attr_name):
|
|||
return val
|
||||
|
||||
|
||||
def set_component(obj, attr_name, value):
|
||||
"""
|
||||
Given an object, and an attribute name, set that attribute on the object.
|
||||
Mirrors get_component, except set_component doesn't handle callable
|
||||
components.
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
obj[attr_name] = value
|
||||
else:
|
||||
setattr(obj, attr_name, value)
|
||||
|
||||
|
||||
def readable_datetime_formats(formats):
|
||||
format = ', '.join(formats).replace(ISO_8601,
|
||||
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]')
|
||||
|
@ -138,6 +150,24 @@ class Field(object):
|
|||
if help_text is not None:
|
||||
self.help_text = strip_multiple_choice_msg(smart_text(help_text))
|
||||
|
||||
def _get_source_value(self, obj, field_name):
|
||||
"""
|
||||
Given an object and a field name, traverses the components in
|
||||
self.source/field_name and returns the source value from the object.
|
||||
|
||||
The source/field_name may contain dot-separated components.
|
||||
Each component should refer to an attribute, a dict key, or a
|
||||
callable with no arguments.
|
||||
"""
|
||||
source = self.source or field_name
|
||||
value = obj
|
||||
|
||||
for component in source.split('.'):
|
||||
value = get_component(value, component)
|
||||
if value is None:
|
||||
break
|
||||
return value
|
||||
|
||||
def initialize(self, parent, field_name):
|
||||
"""
|
||||
Called to set up a field prior to field_to_native or field_from_native.
|
||||
|
@ -170,13 +200,7 @@ class Field(object):
|
|||
if self.source == '*':
|
||||
return self.to_native(obj)
|
||||
|
||||
source = self.source or field_name
|
||||
value = obj
|
||||
|
||||
for component in source.split('.'):
|
||||
value = get_component(value, component)
|
||||
if value is None:
|
||||
break
|
||||
value = self._get_source_value(obj, field_name)
|
||||
|
||||
return self.to_native(value)
|
||||
|
||||
|
@ -297,6 +321,27 @@ class WritableField(Field):
|
|||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
def _set_source_value(self, obj, field_name, value):
|
||||
"""
|
||||
Looks up a field on the given object and sets its value.
|
||||
Uses self.source if set, otherwise the given field name.
|
||||
|
||||
This obeys the same rules as _get_source_value, except that the
|
||||
final component of self.source/field_name can't be a callable.
|
||||
"""
|
||||
source = self.source or field_name
|
||||
parts = source.split('.')
|
||||
last_source_part = parts.pop()
|
||||
|
||||
item = obj
|
||||
for component in parts:
|
||||
item = get_component(item, component)
|
||||
if item is None:
|
||||
raise ValueError(
|
||||
"can't set %r: component %r is None" % (source, component)
|
||||
)
|
||||
set_component(item, last_source_part, value)
|
||||
|
||||
def field_from_native(self, data, files, field_name, into):
|
||||
"""
|
||||
Given a dictionary and a field name, updates the dictionary `into`,
|
||||
|
|
|
@ -130,13 +130,7 @@ class RelatedField(WritableField):
|
|||
if self.source == '*':
|
||||
return self.to_native(obj)
|
||||
|
||||
source = self.source or field_name
|
||||
value = obj
|
||||
|
||||
for component in source.split('.'):
|
||||
value = get_component(value, component)
|
||||
if value is None:
|
||||
break
|
||||
value = self._get_source_value(obj, field_name)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
|
|
|
@ -289,7 +289,8 @@ class BaseSerializer(WritableField):
|
|||
are instantiated.
|
||||
"""
|
||||
if instance is not None:
|
||||
instance.update(attrs)
|
||||
for k, v in attrs.items():
|
||||
self._set_source_value(instance, k, v)
|
||||
return instance
|
||||
return attrs
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user