aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/resources.py
diff options
context:
space:
mode:
authorTom Christie2011-06-14 18:22:13 +0100
committerTom Christie2011-06-14 18:22:13 +0100
commit412b5fc2d54def2f2601b860b80afaa52d595e58 (patch)
treeb700e41c2d17a52d4726da7f62027c50f391b5e9 /djangorestframework/resources.py
parent323d52e7c44c208dce545a8084f7401384fd731e (diff)
downloaddjango-rest-framework-412b5fc2d54def2f2601b860b80afaa52d595e58.tar.bz2
Support for nesting resources etc...
--HG-- rename : djangorestframework/tests/resources.py => djangorestframework/tests/serializer.py
Diffstat (limited to 'djangorestframework/resources.py')
-rw-r--r--djangorestframework/resources.py245
1 files changed, 69 insertions, 176 deletions
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))