Handle dotted field.source in writable serializers.

This commit is contained in:
Craig de Stigter 2013-08-21 16:44:20 +12:00
parent f84d4951bf
commit 0ab70ba170
3 changed files with 55 additions and 15 deletions

View File

@ -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`,

View File

@ -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

View File

@ -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