mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-24 16:24:18 +03:00
Allow related models to be fully serialized
This commit is contained in:
parent
412b5fc2d5
commit
7dcb851c7f
|
@ -479,6 +479,7 @@ class ReadModelMixin(object):
|
||||||
"""
|
"""
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args:
|
if args:
|
||||||
# If we have any none kwargs then assume the last represents the primrary key
|
# If we have any none kwargs then assume the last represents the primrary key
|
||||||
|
@ -498,6 +499,7 @@ class CreateModelMixin(object):
|
||||||
"""
|
"""
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
|
||||||
# translated 'related_field' kwargs into 'related_field_id'
|
# translated 'related_field' kwargs into 'related_field_id'
|
||||||
for related_name in [field.name for field in model._meta.fields if isinstance(field, RelatedField)]:
|
for related_name in [field.name for field in model._meta.fields if isinstance(field, RelatedField)]:
|
||||||
if kwargs.has_key(related_name):
|
if kwargs.has_key(related_name):
|
||||||
|
@ -522,6 +524,7 @@ class UpdateModelMixin(object):
|
||||||
"""
|
"""
|
||||||
def put(self, request, *args, **kwargs):
|
def put(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
|
||||||
# TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url
|
# TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url
|
||||||
try:
|
try:
|
||||||
if args:
|
if args:
|
||||||
|
@ -547,6 +550,7 @@ class DeleteModelMixin(object):
|
||||||
"""
|
"""
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args:
|
if args:
|
||||||
# If we have any none kwargs then assume the last represents the primrary key
|
# If we have any none kwargs then assume the last represents the primrary key
|
||||||
|
@ -581,10 +585,12 @@ class ListModelMixin(object):
|
||||||
queryset = None
|
queryset = None
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
queryset = self.queryset if self.queryset else self.resource.model.objects.all()
|
model = self.resource.model
|
||||||
|
|
||||||
|
queryset = self.queryset if self.queryset else model.objects.all()
|
||||||
|
|
||||||
if hasattr(self, 'resource'):
|
if hasattr(self, 'resource'):
|
||||||
ordering = getattr(self.resource.Meta, 'ordering', None)
|
ordering = getattr(self.resource, 'ordering', None)
|
||||||
else:
|
else:
|
||||||
ordering = None
|
ordering = None
|
||||||
|
|
||||||
|
|
|
@ -181,7 +181,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
|
|
||||||
# Get the form instance if we have one bound to the input
|
# Get the form instance if we have one bound to the input
|
||||||
form_instance = None
|
form_instance = None
|
||||||
if method == view.method.lower():
|
if method == getattr(view, 'method', view.request.method).lower():
|
||||||
form_instance = getattr(view, 'bound_form_instance', None)
|
form_instance = getattr(view, 'bound_form_instance', None)
|
||||||
|
|
||||||
if not form_instance and hasattr(view, 'get_bound_form'):
|
if not form_instance and hasattr(view, 'get_bound_form'):
|
||||||
|
|
|
@ -20,12 +20,11 @@ class BaseResource(Serializer):
|
||||||
"""
|
"""
|
||||||
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, depth=None, stack=[], **kwargs):
|
def __init__(self, view=None, depth=None, stack=[], **kwargs):
|
||||||
super(BaseResource, self).__init__(depth, stack, **kwargs)
|
super(BaseResource, self).__init__(depth, stack, **kwargs)
|
||||||
self.view = view
|
self.view = view
|
||||||
|
|
||||||
|
@ -49,20 +48,19 @@ 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
|
|
||||||
|
|
||||||
# By default the set of returned fields will be the set of:
|
# By default the set of returned fields will be the set of:
|
||||||
#
|
#
|
||||||
# 0. All the fields on the model, excluding 'id'.
|
# 0. All the fields on the model, excluding 'id'.
|
||||||
# 1. All the properties on the model.
|
# 1. All the properties on the model.
|
||||||
# 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
|
# 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
|
||||||
#
|
#
|
||||||
# If you wish to override this behaviour,
|
# If you wish to override this behaviour,
|
||||||
# you should explicitly set the fields attribute on your class.
|
# you should explicitly set the fields attribute on your class.
|
||||||
fields = None
|
fields = None
|
||||||
|
|
||||||
|
|
||||||
class FormResource(Resource):
|
class FormResource(Resource):
|
||||||
|
@ -74,12 +72,11 @@ 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`.
|
"""
|
||||||
"""
|
form = None
|
||||||
form = None
|
|
||||||
|
|
||||||
|
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
|
@ -189,7 +186,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', None) or self.Meta.form
|
form = getattr(self.view, 'form', None) or self.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'):
|
||||||
|
@ -235,44 +232,43 @@ 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.
|
|
||||||
|
|
||||||
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`.
|
||||||
"""
|
"""
|
||||||
form = None
|
form = None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The model class which this resource maps to.
|
The model class which this resource maps to.
|
||||||
|
|
||||||
This can be overridden by a :attr:`model` attribute on the :class:`views.View`.
|
This can be overridden by a :attr:`model` attribute on the :class:`views.View`.
|
||||||
"""
|
"""
|
||||||
model = None
|
model = None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The list of fields to use on the output.
|
The list of fields to use on the output.
|
||||||
|
|
||||||
May be any of:
|
May be any of:
|
||||||
|
|
||||||
The name of a model field.
|
The name of a model field.
|
||||||
The name of an attribute on the model.
|
The name of an attribute on the model.
|
||||||
The name of an attribute on the resource.
|
The name of an attribute on the resource.
|
||||||
The name of a method on the model, with a signature like ``func(self)``.
|
The name of a method on the model, with a signature like ``func(self)``.
|
||||||
The name of a method on the resource, with a signature like ``func(self, instance)``.
|
The name of a method on the resource, with a signature like ``func(self, instance)``.
|
||||||
"""
|
"""
|
||||||
fields = None
|
fields = None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The list of fields to exclude. This is only used if :attr:`fields` is not set.
|
The list of fields to exclude. This is only used if :attr:`fields` is not set.
|
||||||
"""
|
"""
|
||||||
exclude = ('id', 'pk')
|
exclude = ('id', 'pk')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The list of extra fields to include. This is only used if :attr:`fields` is not set.
|
The list of extra fields to include. This is only used if :attr:`fields` is not set.
|
||||||
"""
|
"""
|
||||||
include = ('url',)
|
include = ('url',)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, view):
|
def __init__(self, view):
|
||||||
|
@ -283,7 +279,7 @@ class ModelResource(FormResource):
|
||||||
"""
|
"""
|
||||||
super(ModelResource, self).__init__(view)
|
super(ModelResource, self).__init__(view)
|
||||||
|
|
||||||
self.model = getattr(view, 'model', None) or self.Meta.model
|
self.model = getattr(view, 'model', None) or self.model
|
||||||
|
|
||||||
|
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
|
@ -399,7 +395,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.Meta.fields:
|
if self.fields:
|
||||||
return property_fields & set(as_tuple(self.Meta.fields))
|
return property_fields & set(as_tuple(self.fields))
|
||||||
|
|
||||||
return property_fields.union(set(as_tuple(self.Meta.include))) - set(as_tuple(self.Meta.exclude))
|
return property_fields.union(set(as_tuple(self.include))) - set(as_tuple(self.exclude))
|
||||||
|
|
|
@ -46,30 +46,14 @@ class _SkipField(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _BuildInnerMeta(type):
|
class _RegisterSerializer(type):
|
||||||
"""
|
"""
|
||||||
Some magic so that an inner Meta class gets inheriting behavior.
|
Metaclass to register serializers.
|
||||||
"""
|
"""
|
||||||
def __new__(cls, name, bases, attrs):
|
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.
|
# Build the class and register it.
|
||||||
ret = super(_BuildInnerMeta, cls).__new__(cls, name, bases, attrs)
|
ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs)
|
||||||
_serializers[name] = ret
|
_serializers[name] = ret
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,8 +62,8 @@ class Serializer(object):
|
||||||
Converts python objects into plain old native types suitable for
|
Converts python objects into plain old native types suitable for
|
||||||
serialization. In particular it handles models and querysets.
|
serialization. In particular it handles models and querysets.
|
||||||
|
|
||||||
The output format is specified by setting a number of attributes on the
|
The output format is specified by setting a number of attributes
|
||||||
inner `Meta` class.
|
on the class.
|
||||||
|
|
||||||
You may also override any of the serialization methods, to provide
|
You may also override any of the serialization methods, to provide
|
||||||
for more flexible behavior.
|
for more flexible behavior.
|
||||||
|
@ -87,75 +71,61 @@ class Serializer(object):
|
||||||
Valid output types include anything that may be directly rendered into
|
Valid output types include anything that may be directly rendered into
|
||||||
json, xml etc...
|
json, xml etc...
|
||||||
"""
|
"""
|
||||||
__metaclass__ = _BuildInnerMeta
|
__metaclass__ = _RegisterSerializer
|
||||||
|
|
||||||
class Meta:
|
fields = ()
|
||||||
"""
|
"""
|
||||||
Information on how to serialize the data.
|
Specify the fields to be serialized on a model or dict.
|
||||||
"""
|
Overrides `include` and `exclude`.
|
||||||
|
"""
|
||||||
|
|
||||||
fields = ()
|
include = ()
|
||||||
"""
|
"""
|
||||||
Specify the fields to be serialized on a model or dict.
|
Fields to add to the default set to be serialized on a model/dict.
|
||||||
Overrides `include` and `exclude`.
|
"""
|
||||||
"""
|
|
||||||
|
|
||||||
include = ()
|
exclude = ()
|
||||||
"""
|
"""
|
||||||
Fields to add to the default set to be serialized on a model/dict.
|
Fields to remove from the default set to be serialized on a model/dict.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
exclude = ()
|
rename = {}
|
||||||
"""
|
"""
|
||||||
Fields to remove from the default set to be serialized on a model/dict.
|
A dict of key->name to use for the field keys.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rename = {}
|
related_serializer = None
|
||||||
"""
|
"""
|
||||||
A dict of key->name to use for the field keys.
|
The default serializer class to use for any related models.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
related_serializer = None
|
depth = None
|
||||||
"""
|
"""
|
||||||
The default serializer class to use for any related models.
|
The maximum depth to serialize to, or `None`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
depth = None
|
|
||||||
"""
|
|
||||||
The maximum depth to serialize to, or `None`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, depth=None, stack=[], **kwargs):
|
def __init__(self, depth=None, stack=[], **kwargs):
|
||||||
"""
|
self.depth = depth or self.depth
|
||||||
Allow `Meta` items to be set on init.
|
|
||||||
"""
|
|
||||||
self.depth = depth
|
|
||||||
self.stack = stack
|
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):
|
def get_fields(self, obj):
|
||||||
"""
|
"""
|
||||||
Return the set of field names/keys to use for a model instance/dict.
|
Return the set of field names/keys to use for a model instance/dict.
|
||||||
"""
|
"""
|
||||||
fields = self.Meta.fields
|
fields = self.fields
|
||||||
|
|
||||||
# If Meta.fields is not set, we use the default fields and modify
|
# If `fields` is not set, we use the default fields and modify
|
||||||
# them with Meta.include and Meta.exclude
|
# them with `include` and `exclude`
|
||||||
if not fields:
|
if not fields:
|
||||||
default = self.get_default_fields(obj)
|
default = self.get_default_fields(obj)
|
||||||
include = self.Meta.include or ()
|
include = self.include or ()
|
||||||
exclude = self.Meta.exclude or ()
|
exclude = self.exclude or ()
|
||||||
fields = set(default + list(include)) - set(exclude)
|
fields = set(default + list(include)) - set(exclude)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
fields = _fields_to_list(self.Meta.fields)
|
fields = _fields_to_list(self.fields)
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
@ -163,7 +133,7 @@ class Serializer(object):
|
||||||
def get_default_fields(self, obj):
|
def get_default_fields(self, obj):
|
||||||
"""
|
"""
|
||||||
Return the default list of field names/keys for a model instance/dict.
|
Return the default list of field names/keys for a model instance/dict.
|
||||||
These are used if `Meta.fields` is not given.
|
These are used if `fields` is not given.
|
||||||
"""
|
"""
|
||||||
if isinstance(obj, models.Model):
|
if isinstance(obj, models.Model):
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
|
@ -173,15 +143,14 @@ class Serializer(object):
|
||||||
|
|
||||||
|
|
||||||
def get_related_serializer(self, key):
|
def get_related_serializer(self, key):
|
||||||
info = _fields_to_dict(self.Meta.fields).get(key, None)
|
info = _fields_to_dict(self.fields).get(key, None)
|
||||||
|
|
||||||
# If an element in `fields` is a 2-tuple of (str, tuple)
|
# If an element in `fields` is a 2-tuple of (str, tuple)
|
||||||
# then the second element of the tuple is the fields to
|
# then the second element of the tuple is the fields to
|
||||||
# set on the related serializer
|
# set on the related serializer
|
||||||
if isinstance(info, (list, tuple)):
|
if isinstance(info, (list, tuple)):
|
||||||
class OnTheFlySerializer(Serializer):
|
class OnTheFlySerializer(Serializer):
|
||||||
class Meta:
|
fields = info
|
||||||
fields = info
|
|
||||||
return OnTheFlySerializer
|
return OnTheFlySerializer
|
||||||
|
|
||||||
# If an element in `fields` is a 2-tuple of (str, Serializer)
|
# If an element in `fields` is a 2-tuple of (str, Serializer)
|
||||||
|
@ -200,15 +169,15 @@ class Serializer(object):
|
||||||
return _serializers[info]
|
return _serializers[info]
|
||||||
|
|
||||||
# Otherwise use `related_serializer` or fall back to `Serializer`
|
# Otherwise use `related_serializer` or fall back to `Serializer`
|
||||||
return getattr(self.Meta, 'related_serializer') or Serializer
|
return getattr(self, 'related_serializer') or Serializer
|
||||||
|
|
||||||
|
|
||||||
def serialize_key(self, key):
|
def serialize_key(self, key):
|
||||||
"""
|
"""
|
||||||
Keys serialize to their string value,
|
Keys serialize to their string value,
|
||||||
unless they exist in the `Meta.rename` dict.
|
unless they exist in the `rename` dict.
|
||||||
"""
|
"""
|
||||||
return getattr(self.Meta.rename, key, key)
|
return getattr(self.rename, key, key)
|
||||||
|
|
||||||
|
|
||||||
def serialize_val(self, key, obj):
|
def serialize_val(self, key, obj):
|
||||||
|
@ -251,8 +220,7 @@ class Serializer(object):
|
||||||
|
|
||||||
def serialize_model(self, instance):
|
def serialize_model(self, instance):
|
||||||
"""
|
"""
|
||||||
Given a model instance or dict, serialize it to a dict, using
|
Given a model instance or dict, serialize it to a dict..
|
||||||
the given behavior given on Meta.
|
|
||||||
"""
|
"""
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
|
|
@ -69,12 +69,10 @@ class TestFieldNesting(TestCase):
|
||||||
Test tuple nesting on `fields` attr
|
Test tuple nesting on `fields` attr
|
||||||
"""
|
"""
|
||||||
class SerializerM2(Serializer):
|
class SerializerM2(Serializer):
|
||||||
class Meta:
|
fields = (('field', ('field1',)),)
|
||||||
fields = (('field', ('field1',)),)
|
|
||||||
|
|
||||||
class SerializerM3(Serializer):
|
class SerializerM3(Serializer):
|
||||||
class Meta:
|
fields = (('field', ('field2',)),)
|
||||||
fields = (('field', ('field2',)),)
|
|
||||||
|
|
||||||
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
||||||
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
||||||
|
@ -85,20 +83,16 @@ class TestFieldNesting(TestCase):
|
||||||
Test related model serialization
|
Test related model serialization
|
||||||
"""
|
"""
|
||||||
class NestedM2(Serializer):
|
class NestedM2(Serializer):
|
||||||
class Meta:
|
fields = ('field1', )
|
||||||
fields = ('field1', )
|
|
||||||
|
|
||||||
class NestedM3(Serializer):
|
class NestedM3(Serializer):
|
||||||
class Meta:
|
fields = ('field2', )
|
||||||
fields = ('field2', )
|
|
||||||
|
|
||||||
class SerializerM2(Serializer):
|
class SerializerM2(Serializer):
|
||||||
class Meta:
|
fields = [('field', NestedM2)]
|
||||||
fields = [('field', NestedM2)]
|
|
||||||
|
|
||||||
class SerializerM3(Serializer):
|
class SerializerM3(Serializer):
|
||||||
class Meta:
|
fields = [('field', NestedM3)]
|
||||||
fields = [('field', NestedM3)]
|
|
||||||
|
|
||||||
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
||||||
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
||||||
|
@ -108,20 +102,16 @@ class TestFieldNesting(TestCase):
|
||||||
Test related model serialization
|
Test related model serialization
|
||||||
"""
|
"""
|
||||||
class SerializerM2(Serializer):
|
class SerializerM2(Serializer):
|
||||||
class Meta:
|
fields = [('field', 'NestedM2')]
|
||||||
fields = [('field', 'NestedM2')]
|
|
||||||
|
|
||||||
class SerializerM3(Serializer):
|
class SerializerM3(Serializer):
|
||||||
class Meta:
|
fields = [('field', 'NestedM3')]
|
||||||
fields = [('field', 'NestedM3')]
|
|
||||||
|
|
||||||
class NestedM2(Serializer):
|
class NestedM2(Serializer):
|
||||||
class Meta:
|
fields = ('field1', )
|
||||||
fields = ('field1', )
|
|
||||||
|
|
||||||
class NestedM3(Serializer):
|
class NestedM3(Serializer):
|
||||||
class Meta:
|
fields = ('field2', )
|
||||||
fields = ('field2', )
|
|
||||||
|
|
||||||
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
|
||||||
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
|
||||||
|
|
|
@ -75,8 +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):
|
||||||
pass
|
pass
|
||||||
|
@ -100,12 +99,10 @@ 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):
|
||||||
resource = MockFormResource
|
resource = MockFormResource
|
||||||
|
@ -278,8 +275,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):
|
||||||
resource = MockResource
|
resource = MockResource
|
||||||
|
|
|
@ -18,9 +18,19 @@ In this example we're working from two related models:
|
||||||
Creating the resources
|
Creating the resources
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Once we have some existing models there's very little we need to do to create the API.
|
We need to create two resources that we map to our two existing models, in order to describe how the models should be serialized.
|
||||||
Firstly create a resource for each model that defines which fields we want to expose on the model.
|
Our resource descriptions will typically go into a module called something like 'resources.py'
|
||||||
Secondly we map a base view and an instance view for each resource.
|
|
||||||
|
``resources.py``
|
||||||
|
|
||||||
|
.. include:: ../../examples/blogpost/resources.py
|
||||||
|
:literal:
|
||||||
|
|
||||||
|
Creating views for our resources
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Once we've created the resources there's very little we need to do to create the API.
|
||||||
|
For each resource we'll create a base view, and an instance view.
|
||||||
The generic views :class:`.ListOrCreateModelView` and :class:`.InstanceModelView` provide default operations for listing, creating and updating our models via the API, and also automatically provide input validation using default ModelForms for each model.
|
The generic views :class:`.ListOrCreateModelView` and :class:`.InstanceModelView` provide default operations for listing, creating and updating our models via the API, and also automatically provide input validation using default ModelForms for each model.
|
||||||
|
|
||||||
``urls.py``
|
``urls.py``
|
||||||
|
|
|
@ -25,7 +25,14 @@ Here's the model we're working from in this example:
|
||||||
.. include:: ../../examples/modelresourceexample/models.py
|
.. include:: ../../examples/modelresourceexample/models.py
|
||||||
:literal:
|
:literal:
|
||||||
|
|
||||||
To add an API for the model, all we need to do is create a Resource for the model, and map a couple of views to it in our urlconf.
|
To add an API for the model, first we need to create a Resource for the model.
|
||||||
|
|
||||||
|
``resources.py``
|
||||||
|
|
||||||
|
.. include:: ../../examples/modelresourceexample/resources.py
|
||||||
|
:literal:
|
||||||
|
|
||||||
|
Then we simply map a couple of views to the Resource in our urlconf.
|
||||||
|
|
||||||
``urls.py``
|
``urls.py``
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,35 @@
|
||||||
Alternative Frameworks
|
Alternative frameworks & Why Django REST framework
|
||||||
======================
|
==================================================
|
||||||
|
|
||||||
#. `django-piston <https://bitbucket.org/jespern/django-piston/wiki/Home>`_ is excellent, and has a great community behind it. This project is based on piston code in parts.
|
Alternative frameworks
|
||||||
|
----------------------
|
||||||
|
|
||||||
#. `django-tasypie <https://github.com/toastdriven/django-tastypie>`_ is also well worth looking at.
|
There are a number of alternative REST frameworks for Django:
|
||||||
|
|
||||||
|
* `django-piston <https://bitbucket.org/jespern/django-piston/wiki/Home>`_ is very mature, and has a large community behind it. This project was originally based on piston code in parts.
|
||||||
|
* `django-tasypie <https://github.com/toastdriven/django-tastypie>`_ is also very good, and has a very active and helpful developer community and maintainers.
|
||||||
|
* Other interesting projects include `dagny <https://github.com/zacharyvoase/dagny>`_ and `dj-webmachine <http://benoitc.github.com/dj-webmachine/>`_
|
||||||
|
|
||||||
|
|
||||||
|
Why use Django REST framework?
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
The big benefits of using Django REST framework come down to:
|
||||||
|
|
||||||
|
1. It's based on Django's class based views, which makes it simple, modular, and future-proof.
|
||||||
|
2. It stays as close as possible to Django idioms and language throughout.
|
||||||
|
3. The browse-able API makes working with the APIs extremely quick and easy.
|
||||||
|
|
||||||
|
|
||||||
|
Why was this project created?
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
For me the browse-able API is the most important aspect of Django REST framework.
|
||||||
|
|
||||||
|
I wanted to show that Web APIs could easily be made Web browse-able,
|
||||||
|
and demonstrate how much better browse-able Web APIs are to work with.
|
||||||
|
|
||||||
|
Being able to navigate and use a Web API directly in the browser is a huge win over only having command line and programmatic
|
||||||
|
access to the API. It enables the API to be properly self-describing, and it makes it much much quicker and easier to work with.
|
||||||
|
There's no fundamental reason why the Web APIs we're creating shouldn't be able to render to HTML as well as JSON/XML/whatever,
|
||||||
|
and I really think that more Web API frameworks *in whatever language* ought to be taking a similar approach.
|
||||||
|
|
|
@ -80,8 +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('',
|
||||||
url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)),
|
url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)),
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
:mod:`serializer`
|
:mod:`serializer`
|
||||||
=================
|
=================
|
||||||
|
|
||||||
.. module:: serializer
|
.. automodule:: serializer
|
||||||
|
|
||||||
.. autoclass:: serializer::Serializer.Meta
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: serializer::Serializer
|
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -22,6 +22,9 @@ class BlogPost(models.Model):
|
||||||
slug = models.SlugField(editable=False, default='')
|
slug = models.SlugField(editable=False, default='')
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
For the purposes of the sandbox, limit the maximum number of stored models.
|
||||||
|
"""
|
||||||
self.slug = slugify(self.title)
|
self.slug = slugify(self.title)
|
||||||
super(self.__class__, self).save(*args, **kwargs)
|
super(self.__class__, self).save(*args, **kwargs)
|
||||||
for obj in self.__class__.objects.order_by('-created')[MAX_POSTS:]:
|
for obj in self.__class__.objects.order_by('-created')[MAX_POSTS:]:
|
||||||
|
|
|
@ -1,38 +1,11 @@
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
||||||
from djangorestframework.resources import ModelResource
|
from blogpost.resources import BlogPostResource, CommentResource
|
||||||
|
|
||||||
from blogpost.models import BlogPost, Comment
|
|
||||||
|
|
||||||
|
|
||||||
class BlogPostResource(ModelResource):
|
|
||||||
"""
|
|
||||||
A Blog Post has a *title* and *content*, and can be associated with zero or more comments.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = BlogPost
|
|
||||||
fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
|
|
||||||
ordering = ('-created',)
|
|
||||||
|
|
||||||
def comments(self, instance):
|
|
||||||
return reverse('comments', kwargs={'blogpost': instance.key})
|
|
||||||
|
|
||||||
|
|
||||||
class CommentResource(ModelResource):
|
|
||||||
"""
|
|
||||||
A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = Comment
|
|
||||||
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
|
||||||
ordering = ('-created',)
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', ListOrCreateModelView.as_view(resource=BlogPostResource), name='blog-posts-root'),
|
url(r'^$', ListOrCreateModelView.as_view(resource=BlogPostResource), name='blog-posts-root'),
|
||||||
url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource)),
|
url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource), name='blog-post'),
|
||||||
url(r'^(?P<blogpost>[^/]+)/comments/$', ListOrCreateModelView.as_view(resource=CommentResource), name='comments'),
|
url(r'^(?P<blogpost>[^/]+)/comments/$', ListOrCreateModelView.as_view(resource=CommentResource), name='comments'),
|
||||||
url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)),
|
url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
||||||
from djangorestframework.resources import ModelResource
|
from modelresourceexample.resources import MyModelResource
|
||||||
from modelresourceexample.models import MyModel
|
|
||||||
|
|
||||||
class MyModelResource(ModelResource):
|
|
||||||
class Meta:
|
|
||||||
model = MyModel
|
|
||||||
fields = ('foo', 'bar', 'baz', 'url')
|
|
||||||
ordering = ('created',)
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'),
|
url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'),
|
||||||
url(r'^([0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)),
|
url(r'^(?P<pk>[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,19 +46,12 @@ class HTMLRenderer(BaseRenderer):
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PygmentsFormResource(FormResource):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
form = PygmentsForm
|
|
||||||
|
|
||||||
|
|
||||||
class PygmentsRoot(View):
|
class PygmentsRoot(View):
|
||||||
"""
|
"""
|
||||||
This example demonstrates a simple RESTful Web API aound the awesome pygments library.
|
This example demonstrates a simple RESTful Web API around the awesome pygments library.
|
||||||
This top level resource is used to create highlighted code snippets, and to list all the existing code snippets.
|
This top level resource is used to create highlighted code snippets, and to list all the existing code snippets.
|
||||||
"""
|
"""
|
||||||
resource = PygmentsFormResource
|
form = PygmentsForm
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user