make serializers more extensible

The general idea of this commit is to be able to pass metadata about
the serialization to the serializers, so the serializers can change
their behavior.

In order to do this, we must be able to (1) pass additional
parameters to the serializer at serialization time, (2) the
serializer must be able to do something with those parameters, and
(3) the serializer must be able to pass the parameters down to
child serializers.

(1) to_native now accepts *args, **kwargs
(2) we add several extension points to to_native so subclasses can
do things with the *args, **kwargs. to_native now calls _skip_field
to determine whether a given field should be skipped. A subclass
can subclass this method to skip certain fields. to_native
now calls _get_field_value to actually get a subfield's value.
_get_field_value can be subclassed to modify the *args, *kwargs
passed to a child serializer, or to modify the child field's value
based on the *args,**kwargs.
(3) to_native calls child fields with the *args, **kwargs. However
it does so in a try/except block so if it is an old field without
support for *args, **kwargs, it will not fail, it simply won't
support the *args,**kwargs functionality. field_to_native now
passes all *args, **kwargs received into to_native.

all tests pass
This commit is contained in:
David Greisen 2014-04-07 09:44:29 -04:00
parent 115fe04842
commit b4f0fab043

View File

@ -334,7 +334,22 @@ class BaseSerializer(WritableField):
return instance return instance
return attrs return attrs
def to_native(self, obj): def _skip_field(self, obj, field_name, field, *args, **kwargs):
""" Whether to skip serializing the field; return True to skip"""
return field.read_only and obj is None
def _get_field_value(self, obj, field_name, field, *args, **kwargs):
"""
call subfield's field_to_native. First try with args and kwargs,
fallback to without for serializers/fields without support.
"""
try:
value = field.field_to_native(obj, field_name, *args, **kwargs)
except TypeError:
value = field.field_to_native(obj, field_name)
return value
def to_native(self, obj, *args, **kwargs):
""" """
Serialize objects -> primitives. Serialize objects -> primitives.
""" """
@ -342,11 +357,11 @@ class BaseSerializer(WritableField):
ret.fields = self._dict_class() ret.fields = self._dict_class()
for field_name, field in self.fields.items(): for field_name, field in self.fields.items():
if field.read_only and obj is None: if self._skip_field(obj, field_name, field, *args, **kwargs):
continue continue
field.initialize(parent=self, field_name=field_name) field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name) key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name) value = self._get_field_value(obj, field_name, field, *args, **kwargs)
method = getattr(self, 'transform_%s' % field_name, None) method = getattr(self, 'transform_%s' % field_name, None)
if callable(method): if callable(method):
value = method(obj, value) value = method(obj, value)
@ -381,7 +396,7 @@ class BaseSerializer(WritableField):
field.label = pretty_name(key) field.label = pretty_name(key)
return field return field
def field_to_native(self, obj, field_name): def field_to_native(self, obj, field_name, *args, **kwargs):
""" """
Override default so that the serializer can be used as a nested field Override default so that the serializer can be used as a nested field
across relationships. across relationships.
@ -390,7 +405,7 @@ class BaseSerializer(WritableField):
return None return None
if self.source == '*': if self.source == '*':
return self.to_native(obj) return self.to_native(obj, *args, **kwargs)
# Get the raw field value # Get the raw field value
try: try:
@ -405,7 +420,7 @@ class BaseSerializer(WritableField):
return None return None
if is_simple_callable(getattr(value, 'all', None)): if is_simple_callable(getattr(value, 'all', None)):
return [self.to_native(item) for item in value.all()] return [self.to_native(item, *args, **kwargs) for item in value.all()]
if value is None: if value is None:
return None return None
@ -416,8 +431,8 @@ class BaseSerializer(WritableField):
many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type))
if many: if many:
return [self.to_native(item) for item in value] return [self.to_native(item, *args, **kwargs) for item in value]
return self.to_native(value) return self.to_native(value, *args, **kwargs)
def field_from_native(self, data, files, field_name, into): def field_from_native(self, data, files, field_name, into):
""" """