diff options
| author | Tom Christie | 2011-06-14 18:22:13 +0100 |
|---|---|---|
| committer | Tom Christie | 2011-06-14 18:22:13 +0100 |
| commit | 412b5fc2d54def2f2601b860b80afaa52d595e58 (patch) | |
| tree | b700e41c2d17a52d4726da7f62027c50f391b5e9 | |
| parent | 323d52e7c44c208dce545a8084f7401384fd731e (diff) | |
| download | django-rest-framework-412b5fc2d54def2f2601b860b80afaa52d595e58.tar.bz2 | |
Support for nesting resources etc...
--HG--
rename : djangorestframework/tests/resources.py => djangorestframework/tests/serializer.py
| -rw-r--r-- | djangorestframework/mixins.py | 7 | ||||
| -rw-r--r-- | djangorestframework/resources.py | 245 | ||||
| -rw-r--r-- | djangorestframework/serializer.py | 347 | ||||
| -rw-r--r-- | djangorestframework/tests/resources.py | 60 | ||||
| -rw-r--r-- | djangorestframework/tests/serializer.py | 127 | ||||
| -rw-r--r-- | djangorestframework/tests/validators.py | 12 | ||||
| -rw-r--r-- | djangorestframework/views.py | 4 | ||||
| -rw-r--r-- | docs/index.rst | 4 | ||||
| -rw-r--r-- | docs/library/serializer.rst | 10 | ||||
| -rw-r--r-- | examples/blogpost/urls.py | 14 | ||||
| -rw-r--r-- | examples/modelresourceexample/urls.py | 7 |
11 files changed, 584 insertions, 253 deletions
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 11e3bb38..75fd3646 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -582,7 +582,12 @@ class ListModelMixin(object): def get(self, request, *args, **kwargs): 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: args = as_tuple(ordering) queryset = queryset.order_by(*args) diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py index 07c97d43..7a4f31d5 100644 --- a/djangorestframework/resources.py +++ b/djangorestframework/resources.py @@ -6,6 +6,7 @@ from django.db.models.fields.related import RelatedField from django.utils.encoding import smart_unicode from djangorestframework.response import ErrorResponse +from djangorestframework.serializer import Serializer from djangorestframework.utils import as_tuple import decimal @@ -13,130 +14,19 @@ import inspect import re -# TODO: _IgnoreFieldException -# Map model classes to resource classes -#_model_to_resource = {} - -def _model_to_dict(instance, resource=None): - """ - 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): +class BaseResource(Serializer): """ Base class for all Resource classes, which simply defines the interface they provide. """ - fields = None - include = None - exclude = None + class Meta: + fields = 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 def validate_request(self, data, files=None): @@ -150,7 +40,7 @@ class BaseResource(object): """ Given the response content, filter it into a serializable object. """ - return _object_to_data(obj, self) + return self.serialize(obj) 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. """ - # The model attribute refers to the Django Model which this Resource maps to. - # (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: - # - # 0. All the fields on the model, excluding 'id'. - # 1. All the properties on 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, - # you should explicitly set the fields attribute on your class. - fields = None + class Meta: + # The model attribute refers to the Django Model which this Resource maps to. + # (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: + # + # 0. All the fields on the model, excluding 'id'. + # 1. All the properties on 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, + # you should explicitly set the fields attribute on your class. + fields = None class FormResource(Resource): @@ -183,11 +74,12 @@ class FormResource(Resource): view, which may be used by some renderers. """ - """ - 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 + 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`. + """ + form = 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. - 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 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 #__metaclass__ = _RegisterModelResource - """ - 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`. - """ - form = None - - """ - The model class which this resource maps to. - - This can be overridden by a :attr:`model` attribute on the :class:`views.View`. - """ - model = None - - """ - The list of fields to use on the output. + 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. - May be any of: + This can be overridden by a :attr:`form` attribute on the :class:`views.View`. + """ + form = None - The name of a model field. - The name of an attribute on the model. - 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 resource, with a signature like ``func(self, instance)``. - """ - fields = None + """ + The model class which this resource maps to. - """ - The list of fields to exclude. This is only used if :attr:`fields` is not set. - """ - exclude = ('id', 'pk') + This can be overridden by a :attr:`model` attribute on the :class:`views.View`. + """ + model = None - """ - The list of extra fields to include. This is only used if :attr:`fields` is not set. - """ - include = ('url',) + """ + The list of fields to use on the output. + + May be any of: + + The name of a model field. + The name of an attribute on the model. + 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 resource, with a signature like ``func(self, instance)``. + """ + fields = None + + """ + The list of fields to exclude. This is only used if :attr:`fields` is not set. + """ + exclude = ('id', 'pk') + + """ + The list of extra fields to include. This is only used if :attr:`fields` is not set. + """ + include = ('url',) def __init__(self, view): @@ -390,8 +283,8 @@ class ModelResource(FormResource): """ super(ModelResource, self).__init__(view) - if getattr(view, 'model', None): - self.model = view.model + self.model = getattr(view, 'model', None) or self.Meta.model + def validate_request(self, data, files=None): """ @@ -506,7 +399,7 @@ class ModelResource(FormResource): isinstance(getattr(self.model, attr, None), property) and not attr.startswith('_')) - if self.fields: - return property_fields & set(as_tuple(self.fields)) + if self.Meta.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)) diff --git a/djangorestframework/serializer.py b/djangorestframework/serializer.py new file mode 100644 index 00000000..47c631bc --- /dev/null +++ b/djangorestframework/serializer.py @@ -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) diff --git a/djangorestframework/tests/resources.py b/djangorestframework/tests/resources.py deleted file mode 100644 index 088e3159..00000000 --- a/djangorestframework/tests/resources.py +++ /dev/null @@ -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'))) - diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py new file mode 100644 index 00000000..cf00b104 --- /dev/null +++ b/djangorestframework/tests/serializer.py @@ -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'}}) diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py index a1e5d2d7..45b75c4f 100644 --- a/djangorestframework/tests/validators.py +++ b/djangorestframework/tests/validators.py @@ -75,7 +75,8 @@ class TestNonFieldErrors(TestCase): return self.cleaned_data #pragma: no cover class MockResource(FormResource): - form = MockForm + class Meta: + form = MockForm class MockView(View): pass @@ -99,10 +100,12 @@ class TestFormValidation(TestCase): qwerty = forms.CharField(required=True) class MockFormResource(FormResource): - form = MockForm + class Meta: + form = MockForm class MockModelResource(ModelResource): - form = MockForm + class Meta: + form = MockForm class MockFormView(View): resource = MockFormResource @@ -275,7 +278,8 @@ class TestModelFormValidator(TestCase): return 'read only' class MockResource(ModelResource): - model = MockModel + class Meta: + model = MockModel class MockView(View): resource = MockResource diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 6f2ab5b7..49d722c5 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -150,8 +150,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): response.headers['Allow'] = ', '.join(self.allowed_methods) response.headers['Vary'] = 'Authenticate, Accept' - return self.render(response) - + return self.render(response) + class ModelView(View): """A RESTful view that maps to a model in the database.""" diff --git a/docs/index.rst b/docs/index.rst index 481bdfe2..2bd37a85 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 class MyResource(ModelResource): - model = MyModel + class Meta: + model = MyModel urlpatterns = patterns('', url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)), @@ -134,6 +135,7 @@ Library Reference library/renderers library/resource library/response + library/serializer library/status library/views diff --git a/docs/library/serializer.rst b/docs/library/serializer.rst new file mode 100644 index 00000000..7942c506 --- /dev/null +++ b/docs/library/serializer.rst @@ -0,0 +1,10 @@ +:mod:`serializer` +================= + +.. module:: serializer + +.. autoclass:: serializer::Serializer.Meta + :members: + +.. autoclass:: serializer::Serializer + :members: diff --git a/examples/blogpost/urls.py b/examples/blogpost/urls.py index c677b8fa..6fde612e 100644 --- a/examples/blogpost/urls.py +++ b/examples/blogpost/urls.py @@ -11,9 +11,10 @@ class BlogPostResource(ModelResource): """ A Blog Post has a *title* and *content*, and can be associated with zero or more comments. """ - model = BlogPost - fields = ('created', 'title', 'slug', 'content', 'url', 'comments') - ordering = ('-created',) + class Meta: + model = BlogPost + fields = ('created', 'title', 'slug', 'content', 'url', 'comments') + ordering = ('-created',) def comments(self, instance): 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*. """ - model = Comment - fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') - ordering = ('-created',) + class Meta: + model = Comment + fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') + ordering = ('-created',) urlpatterns = patterns('', diff --git a/examples/modelresourceexample/urls.py b/examples/modelresourceexample/urls.py index bb71ddd3..86960367 100644 --- a/examples/modelresourceexample/urls.py +++ b/examples/modelresourceexample/urls.py @@ -4,9 +4,10 @@ from djangorestframework.resources import ModelResource from modelresourceexample.models import MyModel class MyModelResource(ModelResource): - model = MyModel - fields = ('foo', 'bar', 'baz', 'url') - ordering = ('created',) + class Meta: + model = MyModel + fields = ('foo', 'bar', 'baz', 'url') + ordering = ('created',) urlpatterns = patterns('', url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'), |
