Support for nesting resources etc...

--HG--
rename : djangorestframework/tests/resources.py => djangorestframework/tests/serializer.py
This commit is contained in:
Tom Christie 2011-06-14 18:22:13 +01:00
parent 323d52e7c4
commit 412b5fc2d5
11 changed files with 584 additions and 253 deletions

View File

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

View File

@ -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.
""" """
fields = None class Meta:
include = None fields = None
exclude = None include = 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,19 +49,20 @@ 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.
""" """
# The model attribute refers to the Django Model which this Resource maps to. class Meta:
# (The Model's class, rather than an instance of the Model) # The model attribute refers to the Django Model which this Resource maps to.
model = None # (The Model's class, rather than an instance of the Model)
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):
@ -183,11 +74,12 @@ 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. """
This can be overridden by a :attr:`form` attribute on the :class:`views.View`. 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`.
form = None """
form = None
def validate_request(self, data, files=None): def validate_request(self, data, files=None):
@ -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,43 +235,44 @@ 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. """
If set to :const:`None` then the default model form validation will be used. The form class that should be used for request validation.
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):
@ -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))

View 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)

View File

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

View 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'}})

View File

@ -75,7 +75,8 @@ class TestNonFieldErrors(TestCase):
return self.cleaned_data #pragma: no cover return self.cleaned_data #pragma: no cover
class MockResource(FormResource): class MockResource(FormResource):
form = MockForm class Meta:
form = MockForm
class MockView(View): class MockView(View):
pass pass
@ -99,10 +100,12 @@ class TestFormValidation(TestCase):
qwerty = forms.CharField(required=True) qwerty = forms.CharField(required=True)
class MockFormResource(FormResource): class MockFormResource(FormResource):
form = MockForm class Meta:
form = MockForm
class MockModelResource(ModelResource): class MockModelResource(ModelResource):
form = MockForm class Meta:
form = MockForm
class MockFormView(View): class MockFormView(View):
resource = MockFormResource resource = MockFormResource
@ -275,7 +278,8 @@ class TestModelFormValidator(TestCase):
return 'read only' return 'read only'
class MockResource(ModelResource): class MockResource(ModelResource):
model = MockModel class Meta:
model = MockModel
class MockView(View): class MockView(View):
resource = MockResource resource = MockResource

View File

@ -80,7 +80,8 @@ 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):
model = MyModel class Meta:
model = MyModel
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)), url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)),
@ -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

View File

@ -0,0 +1,10 @@
:mod:`serializer`
=================
.. module:: serializer
.. autoclass:: serializer::Serializer.Meta
:members:
.. autoclass:: serializer::Serializer
:members:

View File

@ -11,9 +11,10 @@ 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.
""" """
model = BlogPost class Meta:
fields = ('created', 'title', 'slug', 'content', 'url', 'comments') model = BlogPost
ordering = ('-created',) fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
ordering = ('-created',)
def comments(self, instance): def comments(self, instance):
return reverse('comments', kwargs={'blogpost': instance.key}) return reverse('comments', kwargs={'blogpost': instance.key})
@ -23,9 +24,10 @@ 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*.
""" """
model = Comment class Meta:
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') model = Comment
ordering = ('-created',) fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
ordering = ('-created',)
urlpatterns = patterns('', urlpatterns = patterns('',

View File

@ -4,9 +4,10 @@ from djangorestframework.resources import ModelResource
from modelresourceexample.models import MyModel from modelresourceexample.models import MyModel
class MyModelResource(ModelResource): class MyModelResource(ModelResource):
model = MyModel class Meta:
fields = ('foo', 'bar', 'baz', 'url') model = MyModel
ordering = ('created',) 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'),