django-rest-framework/djangorestframework/serializer.py
Tom Christie 412b5fc2d5 Support for nesting resources etc...
--HG--
rename : djangorestframework/tests/resources.py => djangorestframework/tests/serializer.py
2011-06-14 18:22:13 +01:00

348 lines
10 KiB
Python

"""
Customizable serialization.
"""
from django.db import models
from django.db.models.query import QuerySet
from django.db.models.fields.related import RelatedField
from django.utils.encoding import smart_unicode
import decimal
import inspect
import types
# We register serializer classes, so that we can refer to them by their
# class names, if there are cyclical serialization heirachys.
_serializers = {}
def _field_to_tuple(field):
"""
Convert an item in the `fields` attribute into a 2-tuple.
"""
if isinstance(field, (tuple, list)):
return (field[0], field[1])
return (field, None)
def _fields_to_list(fields):
"""
Return a list of field names.
"""
return [_field_to_tuple(field)[0] for field in fields or ()]
def _fields_to_dict(fields):
"""
Return a `dict` of field name -> None, or tuple of fields, or Serializer class
"""
return dict([_field_to_tuple(field) for field in fields or ()])
class _SkipField(Exception):
"""
Signals that a serialized field should be ignored.
We use this mechanism as the default behavior for ensuring
that we don't infinitely recurse when dealing with nested data.
"""
pass
class _BuildInnerMeta(type):
"""
Some magic so that an inner Meta class gets inheriting behavior.
"""
def __new__(cls, name, bases, attrs):
# Get a list of all the inner Metas, from the bases upwards.
inner_metas = [getattr(base, 'Meta', object) for base in bases]
inner_metas.append(attrs.get('Meta', object))
# Build up the attributes on the inner Meta.
meta_attrs = {}
[meta_attrs.update(inner_meta.__dict__) for inner_meta in inner_metas]
# Drop private attributes.
meta_attrs = dict([ (key, val) for (key, val) in meta_attrs.items()
if not key.startswith('_') ])
# Lovely, now we can create our inner Meta class.
attrs['Meta'] = type('Meta', (object,), meta_attrs)
# Build the class and register it.
ret = super(_BuildInnerMeta, cls).__new__(cls, name, bases, attrs)
_serializers[name] = ret
return ret
class Serializer(object):
"""
Converts python objects into plain old native types suitable for
serialization. In particular it handles models and querysets.
The output format is specified by setting a number of attributes on the
inner `Meta` class.
You may also override any of the serialization methods, to provide
for more flexible behavior.
Valid output types include anything that may be directly rendered into
json, xml etc...
"""
__metaclass__ = _BuildInnerMeta
class Meta:
"""
Information on how to serialize the data.
"""
fields = ()
"""
Specify the fields to be serialized on a model or dict.
Overrides `include` and `exclude`.
"""
include = ()
"""
Fields to add to the default set to be serialized on a model/dict.
"""
exclude = ()
"""
Fields to remove from the default set to be serialized on a model/dict.
"""
rename = {}
"""
A dict of key->name to use for the field keys.
"""
related_serializer = None
"""
The default serializer class to use for any related models.
"""
depth = None
"""
The maximum depth to serialize to, or `None`.
"""
def __init__(self, depth=None, stack=[], **kwargs):
"""
Allow `Meta` items to be set on init.
"""
self.depth = depth
self.stack = stack
if self.depth is None:
self.depth = self.Meta.depth
for (key, val) in kwargs.items():
setattr(self.Meta, key, val)
def get_fields(self, obj):
"""
Return the set of field names/keys to use for a model instance/dict.
"""
fields = self.Meta.fields
# If Meta.fields is not set, we use the default fields and modify
# them with Meta.include and Meta.exclude
if not fields:
default = self.get_default_fields(obj)
include = self.Meta.include or ()
exclude = self.Meta.exclude or ()
fields = set(default + list(include)) - set(exclude)
else:
fields = _fields_to_list(self.Meta.fields)
return fields
def get_default_fields(self, obj):
"""
Return the default list of field names/keys for a model instance/dict.
These are used if `Meta.fields` is not given.
"""
if isinstance(obj, models.Model):
opts = obj._meta
return [field.name for field in opts.fields + opts.many_to_many]
else:
return obj.keys()
def get_related_serializer(self, key):
info = _fields_to_dict(self.Meta.fields).get(key, None)
# If an element in `fields` is a 2-tuple of (str, tuple)
# then the second element of the tuple is the fields to
# set on the related serializer
if isinstance(info, (list, tuple)):
class OnTheFlySerializer(Serializer):
class Meta:
fields = info
return OnTheFlySerializer
# If an element in `fields` is a 2-tuple of (str, Serializer)
# then the second element of the tuple is the Serializer
# class to use for that field.
elif isinstance(info, type) and issubclass(info, Serializer):
return info
# If an element in `fields` is a 2-tuple of (str, str)
# then the second element of the tuple is the name of the Serializer
# class to use for that field.
#
# Black magic to deal with cyclical Serializer dependancies.
# Similar to what Django does for cyclically related models.
elif isinstance(info, str) and info in _serializers:
return _serializers[info]
# Otherwise use `related_serializer` or fall back to `Serializer`
return getattr(self.Meta, 'related_serializer') or Serializer
def serialize_key(self, key):
"""
Keys serialize to their string value,
unless they exist in the `Meta.rename` dict.
"""
return getattr(self.Meta.rename, key, key)
def serialize_val(self, key, obj):
"""
Convert a model field or dict value into a serializable representation.
"""
related_serializer = self.get_related_serializer(key)
if self.depth is None:
depth = None
elif self.depth <= 0:
return self.serialize_max_depth(obj)
else:
depth = self.depth - 1
if any([obj is elem for elem in self.stack]):
return self.serialize_recursion(obj)
else:
stack = self.stack[:]
stack.append(obj)
return related_serializer(depth=depth, stack=stack).serialize(obj)
def serialize_max_depth(self, obj):
"""
Determine how objects should be serialized once `depth` is exceeded.
The default behavior is to ignore the field.
"""
raise _SkipField
def serialize_recursion(self, obj):
"""
Determine how objects should be serialized if recursion occurs.
The default behavior is to ignore the field.
"""
raise _SkipField
def serialize_model(self, instance):
"""
Given a model instance or dict, serialize it to a dict, using
the given behavior given on Meta.
"""
data = {}
fields = self.get_fields(instance)
# serialize each required field
for fname in fields:
if hasattr(self, fname):
# check for a method 'fname' on self first
meth = getattr(self, fname)
if inspect.ismethod(meth) and len(inspect.getargspec(meth)[0]) == 2:
obj = meth(instance)
elif hasattr(instance, fname):
# now check for an attribute 'fname' on the instance
obj = getattr(instance, fname)
elif fname in instance:
# finally check for a key 'fname' on the instance
obj = instance[fname]
else:
continue
try:
key = self.serialize_key(fname)
val = self.serialize_val(fname, obj)
data[key] = val
except _SkipField:
pass
return data
def serialize_iter(self, obj):
"""
Convert iterables into a serializable representation.
"""
return [self.serialize(item) for item in obj]
def serialize_func(self, obj):
"""
Convert no-arg methods and functions into a serializable representation.
"""
return self.serialize(obj())
def serialize_manager(self, obj):
"""
Convert a model manager into a serializable representation.
"""
return self.serialize_iter(obj.all())
def serialize_decimal(self, obj):
"""
Convert a Decimal instance into a serializable representation.
"""
return str(obj)
def serialize_fallback(self, obj):
"""
Convert any unhandled object into a serializable representation.
"""
return smart_unicode(obj, strings_only=True)
def serialize(self, obj):
"""
Convert any object into a serializable representation.
"""
if isinstance(obj, (dict, models.Model)):
# Model instances & dictionaries
return self.serialize_model(obj)
elif isinstance(obj, (tuple, list, set, QuerySet, types.GeneratorType)):
# basic iterables
return self.serialize_iter(obj)
elif isinstance(obj, models.Manager):
# Manager objects
return self.serialize_manager(obj)
elif isinstance(obj, decimal.Decimal):
# Decimals (force to string representation)
return self.serialize_decimal(obj)
elif inspect.isfunction(obj) and not inspect.getargspec(obj)[0]:
# function with no args
return self.serialize_func(obj)
elif inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1:
# bound method
return self.serialize_func(obj)
# fall back to smart unicode
return self.serialize_fallback(obj)