mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-06-22 14:33:31 +03:00
Support for nesting resources etc...
--HG-- rename : djangorestframework/tests/resources.py => djangorestframework/tests/serializer.py
This commit is contained in:
parent
323d52e7c4
commit
412b5fc2d5
|
@ -582,7 +582,12 @@ class ListModelMixin(object):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
queryset = self.queryset if self.queryset else self.resource.model.objects.all()
|
queryset = self.queryset if self.queryset else self.resource.model.objects.all()
|
||||||
ordering = getattr(self.resource, 'ordering', None)
|
|
||||||
|
if hasattr(self, 'resource'):
|
||||||
|
ordering = getattr(self.resource.Meta, 'ordering', None)
|
||||||
|
else:
|
||||||
|
ordering = None
|
||||||
|
|
||||||
if ordering:
|
if ordering:
|
||||||
args = as_tuple(ordering)
|
args = as_tuple(ordering)
|
||||||
queryset = queryset.order_by(*args)
|
queryset = queryset.order_by(*args)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.db.models.fields.related import RelatedField
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
|
|
||||||
from djangorestframework.response import ErrorResponse
|
from djangorestframework.response import ErrorResponse
|
||||||
|
from djangorestframework.serializer import Serializer
|
||||||
from djangorestframework.utils import as_tuple
|
from djangorestframework.utils import as_tuple
|
||||||
|
|
||||||
import decimal
|
import decimal
|
||||||
|
@ -13,130 +14,19 @@ import inspect
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
# TODO: _IgnoreFieldException
|
|
||||||
|
|
||||||
# Map model classes to resource classes
|
|
||||||
#_model_to_resource = {}
|
|
||||||
|
|
||||||
|
|
||||||
def _model_to_dict(instance, resource=None):
|
class BaseResource(Serializer):
|
||||||
"""
|
|
||||||
Given a model instance, return a ``dict`` representing the model.
|
|
||||||
|
|
||||||
The implementation is similar to Django's ``django.forms.model_to_dict``, except:
|
|
||||||
|
|
||||||
* It doesn't coerce related objects into primary keys.
|
|
||||||
* It doesn't drop ``editable=False`` fields.
|
|
||||||
* It also supports attribute or method fields on the instance or resource.
|
|
||||||
"""
|
|
||||||
opts = instance._meta
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
#print [rel.name for rel in opts.get_all_related_objects()]
|
|
||||||
#related = [rel.get_accessor_name() for rel in opts.get_all_related_objects()]
|
|
||||||
#print [getattr(instance, rel) for rel in related]
|
|
||||||
#if resource.fields:
|
|
||||||
# fields = resource.fields
|
|
||||||
#else:
|
|
||||||
# fields = set(opts.fields + opts.many_to_many)
|
|
||||||
|
|
||||||
fields = resource and resource.fields or ()
|
|
||||||
include = resource and resource.include or ()
|
|
||||||
exclude = resource and resource.exclude or ()
|
|
||||||
|
|
||||||
extra_fields = fields and list(fields) or list(include)
|
|
||||||
|
|
||||||
# Model fields
|
|
||||||
for f in opts.fields + opts.many_to_many:
|
|
||||||
if fields and not f.name in fields:
|
|
||||||
continue
|
|
||||||
if exclude and f.name in exclude:
|
|
||||||
continue
|
|
||||||
if isinstance(f, models.ForeignKey):
|
|
||||||
data[f.name] = getattr(instance, f.name)
|
|
||||||
else:
|
|
||||||
data[f.name] = f.value_from_object(instance)
|
|
||||||
|
|
||||||
if extra_fields and f.name in extra_fields:
|
|
||||||
extra_fields.remove(f.name)
|
|
||||||
|
|
||||||
# Method fields
|
|
||||||
for fname in extra_fields:
|
|
||||||
|
|
||||||
if isinstance(fname, (tuple, list)):
|
|
||||||
fname, fields = fname
|
|
||||||
else:
|
|
||||||
fname, fields = fname, False
|
|
||||||
|
|
||||||
try:
|
|
||||||
if hasattr(resource, fname):
|
|
||||||
# check the resource first, to allow it to override fields
|
|
||||||
obj = getattr(resource, fname)
|
|
||||||
# if it's a method like foo(self, instance), then call it
|
|
||||||
if inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) == 2:
|
|
||||||
obj = obj(instance)
|
|
||||||
elif hasattr(instance, fname):
|
|
||||||
# now check the object instance
|
|
||||||
obj = getattr(instance, fname)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# TODO: It would be nicer if this didn't recurse here.
|
|
||||||
# Let's keep _model_to_dict flat, and _object_to_data recursive.
|
|
||||||
if fields:
|
|
||||||
Resource = type('Resource', (object,), {'fields': fields,
|
|
||||||
'include': (),
|
|
||||||
'exclude': ()})
|
|
||||||
data[fname] = _object_to_data(obj, Resource())
|
|
||||||
else:
|
|
||||||
data[fname] = _object_to_data(obj)
|
|
||||||
|
|
||||||
except NoReverseMatch:
|
|
||||||
# Ug, bit of a hack for now
|
|
||||||
pass
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _object_to_data(obj, resource=None):
|
|
||||||
"""
|
|
||||||
Convert an object into a serializable representation.
|
|
||||||
"""
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
# dictionaries
|
|
||||||
# TODO: apply same _model_to_dict logic fields/exclude here
|
|
||||||
return dict([ (key, _object_to_data(val)) for key, val in obj.iteritems() ])
|
|
||||||
if isinstance(obj, (tuple, list, set, QuerySet)):
|
|
||||||
# basic iterables
|
|
||||||
return [_object_to_data(item, resource) for item in obj]
|
|
||||||
if isinstance(obj, models.Manager):
|
|
||||||
# Manager objects
|
|
||||||
return [_object_to_data(item, resource) for item in obj.all()]
|
|
||||||
if isinstance(obj, models.Model):
|
|
||||||
# Model instances
|
|
||||||
return _object_to_data(_model_to_dict(obj, resource))
|
|
||||||
if isinstance(obj, decimal.Decimal):
|
|
||||||
# Decimals (force to string representation)
|
|
||||||
return str(obj)
|
|
||||||
if inspect.isfunction(obj) and not inspect.getargspec(obj)[0]:
|
|
||||||
# function with no args
|
|
||||||
return _object_to_data(obj(), resource)
|
|
||||||
if inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1:
|
|
||||||
# bound method
|
|
||||||
return _object_to_data(obj(), resource)
|
|
||||||
|
|
||||||
return smart_unicode(obj, strings_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseResource(object):
|
|
||||||
"""
|
"""
|
||||||
Base class for all Resource classes, which simply defines the interface they provide.
|
Base class for all Resource classes, which simply defines the interface they provide.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
fields = None
|
fields = None
|
||||||
include = None
|
include = None
|
||||||
exclude = None
|
exclude = None
|
||||||
|
|
||||||
def __init__(self, view):
|
def __init__(self, view, depth=None, stack=[], **kwargs):
|
||||||
|
super(BaseResource, self).__init__(depth, stack, **kwargs)
|
||||||
self.view = view
|
self.view = view
|
||||||
|
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
|
@ -150,7 +40,7 @@ class BaseResource(object):
|
||||||
"""
|
"""
|
||||||
Given the response content, filter it into a serializable object.
|
Given the response content, filter it into a serializable object.
|
||||||
"""
|
"""
|
||||||
return _object_to_data(obj, self)
|
return self.serialize(obj)
|
||||||
|
|
||||||
|
|
||||||
class Resource(BaseResource):
|
class Resource(BaseResource):
|
||||||
|
@ -159,6 +49,7 @@ class Resource(BaseResource):
|
||||||
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
|
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
# The model attribute refers to the Django Model which this Resource maps to.
|
# The model attribute refers to the Django Model which this Resource maps to.
|
||||||
# (The Model's class, rather than an instance of the Model)
|
# (The Model's class, rather than an instance of the Model)
|
||||||
model = None
|
model = None
|
||||||
|
@ -183,6 +74,7 @@ class FormResource(Resource):
|
||||||
view, which may be used by some renderers.
|
view, which may be used by some renderers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
"""
|
"""
|
||||||
The :class:`Form` class that should be used for request validation.
|
The :class:`Form` class that should be used for request validation.
|
||||||
This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
|
This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
|
||||||
|
@ -297,7 +189,7 @@ class FormResource(Resource):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# A form on the view overrides a form on the resource.
|
# A form on the view overrides a form on the resource.
|
||||||
form = getattr(self.view, 'form', self.form)
|
form = getattr(self.view, 'form', None) or self.Meta.form
|
||||||
|
|
||||||
# Use the requested method or determine the request method
|
# Use the requested method or determine the request method
|
||||||
if method is None and hasattr(self.view, 'request') and hasattr(self.view, 'method'):
|
if method is None and hasattr(self.view, 'request') and hasattr(self.view, 'method'):
|
||||||
|
@ -343,6 +235,7 @@ class ModelResource(FormResource):
|
||||||
# Auto-register new ModelResource classes into _model_to_resource
|
# Auto-register new ModelResource classes into _model_to_resource
|
||||||
#__metaclass__ = _RegisterModelResource
|
#__metaclass__ = _RegisterModelResource
|
||||||
|
|
||||||
|
class Meta:
|
||||||
"""
|
"""
|
||||||
The form class that should be used for request validation.
|
The form class that should be used for request validation.
|
||||||
If set to :const:`None` then the default model form validation will be used.
|
If set to :const:`None` then the default model form validation will be used.
|
||||||
|
@ -390,8 +283,8 @@ class ModelResource(FormResource):
|
||||||
"""
|
"""
|
||||||
super(ModelResource, self).__init__(view)
|
super(ModelResource, self).__init__(view)
|
||||||
|
|
||||||
if getattr(view, 'model', None):
|
self.model = getattr(view, 'model', None) or self.Meta.model
|
||||||
self.model = view.model
|
|
||||||
|
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
|
@ -506,7 +399,7 @@ class ModelResource(FormResource):
|
||||||
isinstance(getattr(self.model, attr, None), property)
|
isinstance(getattr(self.model, attr, None), property)
|
||||||
and not attr.startswith('_'))
|
and not attr.startswith('_'))
|
||||||
|
|
||||||
if self.fields:
|
if self.Meta.fields:
|
||||||
return property_fields & set(as_tuple(self.fields))
|
return property_fields & set(as_tuple(self.Meta.fields))
|
||||||
|
|
||||||
return property_fields.union(set(as_tuple(self.include))) - set(as_tuple(self.exclude))
|
return property_fields.union(set(as_tuple(self.Meta.include))) - set(as_tuple(self.Meta.exclude))
|
||||||
|
|
347
djangorestframework/serializer.py
Normal file
347
djangorestframework/serializer.py
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
"""
|
||||||
|
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)
|
|
@ -1,60 +0,0 @@
|
||||||
"""Tests for the resource module"""
|
|
||||||
from django.test import TestCase
|
|
||||||
from djangorestframework.resources import _object_to_data
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import decimal
|
|
||||||
|
|
||||||
class TestObjectToData(TestCase):
|
|
||||||
"""Tests for the _object_to_data function"""
|
|
||||||
|
|
||||||
def test_decimal(self):
|
|
||||||
"""Decimals need to be converted to a string representation."""
|
|
||||||
self.assertEquals(_object_to_data(decimal.Decimal('1.5')), '1.5')
|
|
||||||
|
|
||||||
def test_function(self):
|
|
||||||
"""Functions with no arguments should be called."""
|
|
||||||
def foo():
|
|
||||||
return 1
|
|
||||||
self.assertEquals(_object_to_data(foo), 1)
|
|
||||||
|
|
||||||
def test_method(self):
|
|
||||||
"""Methods with only a ``self`` argument should be called."""
|
|
||||||
class Foo(object):
|
|
||||||
def foo(self):
|
|
||||||
return 1
|
|
||||||
self.assertEquals(_object_to_data(Foo().foo), 1)
|
|
||||||
|
|
||||||
def test_datetime(self):
|
|
||||||
"""datetime objects are left as-is."""
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
self.assertEquals(_object_to_data(now), now)
|
|
||||||
|
|
||||||
def test_tuples(self):
|
|
||||||
""" Test tuple serialisation """
|
|
||||||
class M1(models.Model):
|
|
||||||
field1 = models.CharField()
|
|
||||||
field2 = models.CharField()
|
|
||||||
|
|
||||||
class M2(models.Model):
|
|
||||||
field = models.OneToOneField(M1)
|
|
||||||
|
|
||||||
class M3(models.Model):
|
|
||||||
field = models.ForeignKey(M1)
|
|
||||||
|
|
||||||
m1 = M1(field1='foo', field2='bar')
|
|
||||||
m2 = M2(field=m1)
|
|
||||||
m3 = M3(field=m1)
|
|
||||||
|
|
||||||
Resource = type('Resource', (object,), {'fields':(), 'include':(), 'exclude':()})
|
|
||||||
|
|
||||||
r = Resource()
|
|
||||||
r.fields = (('field', ('field1')),)
|
|
||||||
|
|
||||||
self.assertEqual(_object_to_data(m2, r), dict(field=dict(field1=u'foo')))
|
|
||||||
|
|
||||||
r.fields = (('field', ('field2')),)
|
|
||||||
self.assertEqual(_object_to_data(m3, r), dict(field=dict(field2=u'bar')))
|
|
||||||
|
|
127
djangorestframework/tests/serializer.py
Normal file
127
djangorestframework/tests/serializer.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
"""Tests for the resource module"""
|
||||||
|
from django.test import TestCase
|
||||||
|
from djangorestframework.serializer import Serializer
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
|
||||||
|
class TestObjectToData(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the Serializer class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.serializer = Serializer()
|
||||||
|
self.serialize = self.serializer.serialize
|
||||||
|
|
||||||
|
def test_decimal(self):
|
||||||
|
"""Decimals need to be converted to a string representation."""
|
||||||
|
self.assertEquals(self.serialize(decimal.Decimal('1.5')), '1.5')
|
||||||
|
|
||||||
|
def test_function(self):
|
||||||
|
"""Functions with no arguments should be called."""
|
||||||
|
def foo():
|
||||||
|
return 1
|
||||||
|
self.assertEquals(self.serialize(foo), 1)
|
||||||
|
|
||||||
|
def test_method(self):
|
||||||
|
"""Methods with only a ``self`` argument should be called."""
|
||||||
|
class Foo(object):
|
||||||
|
def foo(self):
|
||||||
|
return 1
|
||||||
|
self.assertEquals(self.serialize(Foo().foo), 1)
|
||||||
|
|
||||||
|
def test_datetime(self):
|
||||||
|
"""
|
||||||
|
datetime objects are left as-is.
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
self.assertEquals(self.serialize(now), now)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFieldNesting(TestCase):
|
||||||
|
"""
|
||||||
|
Test nesting the fields in the Serializer class
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.serializer = Serializer()
|
||||||
|
self.serialize = self.serializer.serialize
|
||||||
|
|
||||||
|
class M1(models.Model):
|
||||||
|
field1 = models.CharField()
|
||||||
|
field2 = models.CharField()
|
||||||
|
|
||||||
|
class M2(models.Model):
|
||||||
|
field = models.OneToOneField(M1)
|
||||||
|
|
||||||
|
class M3(models.Model):
|
||||||
|
field = models.ForeignKey(M1)
|
||||||
|
|
||||||
|
self.m1 = M1(field1='foo', field2='bar')
|
||||||
|
self.m2 = M2(field=self.m1)
|
||||||
|
self.m3 = M3(field=self.m1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tuple_nesting(self):
|
||||||
|
"""
|
||||||
|
Test tuple nesting on `fields` attr
|
||||||
|
"""
|
||||||
|
class SerializerM2(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = (('field', ('field1',)),)
|
||||||
|
|
||||||
|
class SerializerM3(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = (('field', ('field2',)),)
|
||||||
|
|
||||||
|
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
||||||
|
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
||||||
|
|
||||||
|
|
||||||
|
def test_serializer_class_nesting(self):
|
||||||
|
"""
|
||||||
|
Test related model serialization
|
||||||
|
"""
|
||||||
|
class NestedM2(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = ('field1', )
|
||||||
|
|
||||||
|
class NestedM3(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = ('field2', )
|
||||||
|
|
||||||
|
class SerializerM2(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = [('field', NestedM2)]
|
||||||
|
|
||||||
|
class SerializerM3(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = [('field', NestedM3)]
|
||||||
|
|
||||||
|
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
||||||
|
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
||||||
|
|
||||||
|
def test_serializer_classname_nesting(self):
|
||||||
|
"""
|
||||||
|
Test related model serialization
|
||||||
|
"""
|
||||||
|
class SerializerM2(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = [('field', 'NestedM2')]
|
||||||
|
|
||||||
|
class SerializerM3(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = [('field', 'NestedM3')]
|
||||||
|
|
||||||
|
class NestedM2(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = ('field1', )
|
||||||
|
|
||||||
|
class NestedM3(Serializer):
|
||||||
|
class Meta:
|
||||||
|
fields = ('field2', )
|
||||||
|
|
||||||
|
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
||||||
|
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
|
@ -75,6 +75,7 @@ class TestNonFieldErrors(TestCase):
|
||||||
return self.cleaned_data #pragma: no cover
|
return self.cleaned_data #pragma: no cover
|
||||||
|
|
||||||
class MockResource(FormResource):
|
class MockResource(FormResource):
|
||||||
|
class Meta:
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
|
@ -99,9 +100,11 @@ class TestFormValidation(TestCase):
|
||||||
qwerty = forms.CharField(required=True)
|
qwerty = forms.CharField(required=True)
|
||||||
|
|
||||||
class MockFormResource(FormResource):
|
class MockFormResource(FormResource):
|
||||||
|
class Meta:
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockModelResource(ModelResource):
|
class MockModelResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockFormView(View):
|
class MockFormView(View):
|
||||||
|
@ -275,6 +278,7 @@ class TestModelFormValidator(TestCase):
|
||||||
return 'read only'
|
return 'read only'
|
||||||
|
|
||||||
class MockResource(ModelResource):
|
class MockResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
model = MockModel
|
model = MockModel
|
||||||
|
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
|
|
|
@ -80,6 +80,7 @@ Using Django REST framework can be as simple as adding a few lines to your urlco
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
|
|
||||||
class MyResource(ModelResource):
|
class MyResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
model = MyModel
|
model = MyModel
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
|
@ -134,6 +135,7 @@ Library Reference
|
||||||
library/renderers
|
library/renderers
|
||||||
library/resource
|
library/resource
|
||||||
library/response
|
library/response
|
||||||
|
library/serializer
|
||||||
library/status
|
library/status
|
||||||
library/views
|
library/views
|
||||||
|
|
||||||
|
|
10
docs/library/serializer.rst
Normal file
10
docs/library/serializer.rst
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
:mod:`serializer`
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. module:: serializer
|
||||||
|
|
||||||
|
.. autoclass:: serializer::Serializer.Meta
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: serializer::Serializer
|
||||||
|
:members:
|
|
@ -11,6 +11,7 @@ class BlogPostResource(ModelResource):
|
||||||
"""
|
"""
|
||||||
A Blog Post has a *title* and *content*, and can be associated with zero or more comments.
|
A Blog Post has a *title* and *content*, and can be associated with zero or more comments.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
model = BlogPost
|
model = BlogPost
|
||||||
fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
|
fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
@ -23,6 +24,7 @@ class CommentResource(ModelResource):
|
||||||
"""
|
"""
|
||||||
A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
|
A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
model = Comment
|
model = Comment
|
||||||
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from djangorestframework.resources import ModelResource
|
||||||
from modelresourceexample.models import MyModel
|
from modelresourceexample.models import MyModel
|
||||||
|
|
||||||
class MyModelResource(ModelResource):
|
class MyModelResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
model = MyModel
|
model = MyModel
|
||||||
fields = ('foo', 'bar', 'baz', 'url')
|
fields = ('foo', 'bar', 'baz', 'url')
|
||||||
ordering = ('created',)
|
ordering = ('created',)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user