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 /djangorestframework/resources.py | |
| 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
Diffstat (limited to 'djangorestframework/resources.py')
| -rw-r--r-- | djangorestframework/resources.py | 245 |
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)) |
