This commit is contained in:
GitHub Merge Button 2012-01-11 02:14:02 -08:00
commit 3f4318ab98
5 changed files with 100 additions and 61 deletions

View File

@ -471,12 +471,21 @@ class ResourceMixin(object):
""" """
return self.deserialize(self.request.GET) return self.deserialize(self.request.GET)
@property def get_resource_class(self):
def resource(self): if self.resource_class:
if not hasattr(self, '_resource'): return self.resource_class
self._resource = self.resource_class(view=self) elif getattr(self, 'model', None):
return self._resource return ModelResource
elif getattr(self, 'form', None):
return FormResource
elif hasattr(self, 'request') and getattr(self, '%s_form' % self.method.lower(), None):
return FormResource
else:
return Resource
def get_resource(self):
resource_class = self.get_resource_class()
return resource_class(view=self)
@ -496,6 +505,7 @@ class InstanceMixin(object):
associated with this view. associated with this view.
""" """
view = super(InstanceMixin, cls).as_view(**initkwargs) view = super(InstanceMixin, cls).as_view(**initkwargs)
# TODO: FIX !!! Very bad now, since this is attached on the class (thread-safety)
resource_class = getattr(cls(**initkwargs), 'resource_class', None) resource_class = getattr(cls(**initkwargs), 'resource_class', None)
if resource_class: if resource_class:
# We do a little dance when we store the view callable... # We do a little dance when we store the view callable...
@ -512,51 +522,55 @@ class InstanceMixin(object):
class GetResourceMixin(object): class GetResourceMixin(object):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
resource = self.get_resource()
try: try:
self.resource.retrieve(request, *args, **kwargs) resource.retrieve(*args, **kwargs)
except self.resource.DoesNotExist: except resource.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND) raise ErrorResponse(status.HTTP_404_NOT_FOUND)
return self.resource.instance return resource.instance
class PostResourceMixin(object): class PostResourceMixin(object):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.resource.create(request, *args, **kwargs) resource = self.get_resource()
self.resource.update(self.CONTENT, request, *args, **kwargs) resource.create(*args, **kwargs)
headers = {'Location': self.resource.get_url()} resource.update(self.CONTENT, *args, **kwargs)
return Response(status.HTTP_201_CREATED, self.resource.instance, headers) headers = {'Location': resource.get_url()}
return Response(status.HTTP_201_CREATED, resource.instance, headers)
class PutResourceMixin(object): class PutResourceMixin(object):
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
headers = {} resource = self.get_resource()
try: try:
self.resource.retrieve(request, *args, **kwargs) resource.retrieve(*args, **kwargs)
status_code = status.HTTP_204_NO_CONTENT status_code = status.HTTP_204_NO_CONTENT
except self.resource.DoesNotExist: except resource.DoesNotExist:
self.resource.create(request, *args, **kwargs) resource.create(*args, **kwargs)
status_code = status.HTTP_201_CREATED status_code = status.HTTP_201_CREATED
self.resource.update(self.CONTENT, request, *args, **kwargs) resource.update(self.CONTENT, *args, **kwargs)
return Response(status_code, self.resource.instance, {}) return Response(status_code, resource.instance, {})
class DeleteResourceMixin(object): class DeleteResourceMixin(object):
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
resource = self.get_resource()
try: try:
self.resource.retrieve(request, *args, **kwargs) resource.retrieve(*args, **kwargs)
except self.resource.DoesNotExist: except resource.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND) raise ErrorResponse(status.HTTP_404_NOT_FOUND)
self.resource.delete(request, *args, **kwargs) resource.delete(*args, **kwargs)
return return
class ListResourceMixin(object): class ListResourceMixin(object):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.resource.list(request, *args, **kwargs) resource = self.get_resource()
return resource.list(*args, **kwargs)
########## Pagination Mixins ########## ########## Pagination Mixins ##########

View File

@ -216,4 +216,4 @@ class PerResourceThrottling(BaseThrottle):
""" """
def get_cache_key(self): def get_cache_key(self):
return 'throttle_resource_%s' % self.view.resource.__class__.__name__ return 'throttle_resource_%s' % self.view.get_resource_class().__name__

View File

@ -1,20 +1,13 @@
from django import forms from django import forms
from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
from django.db import models from django.db import models
from django.core.exceptions import ImproperlyConfigured
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework.serializer import Serializer, _SkipField from djangorestframework.serializer import Serializer, _SkipField
def bound_resource_required(meth): class BaseResource(object):
def _decorated(self, *args, **kwargs):
if not self.is_bound():
raise Exception("resource needs to be bound") #TODO: what exception?
return meth(self, *args, **kwargs)
return _decorated
class BaseResource(Serializer):
""" """
Base class for all Resource classes, which simply defines the interface Base class for all Resource classes, which simply defines the interface
they provide. they provide.
@ -26,7 +19,8 @@ class BaseResource(Serializer):
# TODO: Inheritance, like for models # TODO: Inheritance, like for models
class DoesNotExist(Exception): pass class DoesNotExist(Exception): pass
def __init__(self, instance=None, view=None, depth=None, stack=[], **kwargs): # !!! `view` should be first kwarg to avoid backward incompatibilities. (lol)
def __init__(self, view=None, instance=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
self.instance = instance self.instance = instance
@ -37,23 +31,20 @@ class BaseResource(Serializer):
Typically raises a :exc:`response.ErrorResponse` with status code 400 Typically raises a :exc:`response.ErrorResponse` with status code 400
(Bad Request) on failure. (Bad Request) on failure.
""" """
return data
def retrieve(self, request, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
def create(self, request, *args, **kwargs): def retrieve(self, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
@bound_resource_required def create(self, *args, **kwargs):
def update(self, data, request, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
@bound_resource_required def update(self, data, *args, **kwargs):
def delete(self, request, *args, **kwargs): raise NotImplementedError()
def delete(self, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
@bound_resource_required
def get_url(self): def get_url(self):
raise NotImplementedError() raise NotImplementedError()
@ -61,7 +52,7 @@ class BaseResource(Serializer):
return not self.instance is None return not self.instance is None
class Resource(BaseResource): class Resource(Serializer, BaseResource):
""" """
A Resource determines how a python object maps to some serializable data. A Resource determines how a python object maps to some serializable data.
Objects that a resource can act on include plain Python object instances, Objects that a resource can act on include plain Python object instances,
@ -83,6 +74,9 @@ class Resource(BaseResource):
# you should explicitly set the fields attribute on your class. # you should explicitly set the fields attribute on your class.
fields = None fields = None
def deserialize(self, data, files=None):
return data
class FormResource(Resource): class FormResource(Resource):
""" """
@ -148,7 +142,8 @@ class FormResource(Resource):
if bound_form is None: if bound_form is None:
return data return data
self.view.bound_form_instance = bound_form if self.view is not None:
self.view.bound_form_instance = bound_form
data = data and data or {} data = data and data or {}
files = files and files or {} files = files and files or {}
@ -314,17 +309,17 @@ class ModelResource(FormResource):
is not set. is not set.
""" """
def __init__(self, instance=None, view=None, depth=None, stack=[], **kwargs): def __init__(self, view=None, instance=None, depth=None, stack=[], **kwargs):
""" """
Allow :attr:`form` and :attr:`model` attributes set on the Allow :attr:`form` and :attr:`model` attributes set on the
:class:`View` to override the :attr:`form` and :attr:`model` :class:`View` to override the :attr:`form` and :attr:`model`
attributes set on the :class:`Resource`. attributes set on the :class:`Resource`.
""" """
super(ModelResource, self).__init__(instance=instance, view=view, depth=depth, stack=stack, **kwargs) super(ModelResource, self).__init__(view=None, instance=instance, depth=depth, stack=stack, **kwargs)
self.model = getattr(view, 'model', None) or self.model self.model = getattr(view, 'model', None) or self.model
def retrieve(self, request, *args, **kwargs): def retrieve(self, *args, **kwargs):
""" """
Return a model instance or None. Return a model instance or None.
""" """
@ -339,7 +334,7 @@ class ModelResource(FormResource):
self.instance = instance self.instance = instance
return self.instance return self.instance
def create(self, request, *args, **kwargs): def create(self, *args, **kwargs):
model = self.get_model() model = self.get_model()
kwargs = self._clean_url_kwargs(kwargs) kwargs = self._clean_url_kwargs(kwargs)
@ -347,8 +342,12 @@ class ModelResource(FormResource):
self.instance.save() self.instance.save()
return self.instance return self.instance
@bound_resource_required def update(self, data, *args, **kwargs):
def update(self, data, request, *args, **kwargs): # The resource needs to be bound to an
# instance, or updating is not possible
if not self.is_bound():
raise Exception("resource needs to be bound") #TODO: what exception?
model = self.get_model() model = self.get_model()
kwargs = self._clean_url_kwargs(kwargs) kwargs = self._clean_url_kwargs(kwargs)
data = dict(data, **kwargs) data = dict(data, **kwargs)
@ -382,12 +381,16 @@ class ModelResource(FormResource):
self.instance.save() self.instance.save()
return self.instance return self.instance
@bound_resource_required def delete(self, *args, **kwargs):
def delete(self, request, *args, **kwargs): # The resource needs to be bound to an
# instance, or deleting is not possible
if not self.is_bound():
raise Exception("resource needs to be bound") #TODO: what exception?
self.instance.delete() self.instance.delete()
return self.instance return self.instance
def list(self, request, *args, **kwargs): def list(self, *args, **kwargs):
# TODO: QuerysetResource instead !? # TODO: QuerysetResource instead !?
kwargs = self._clean_url_kwargs(kwargs) kwargs = self._clean_url_kwargs(kwargs)
queryset = self.get_queryset() queryset = self.get_queryset()
@ -397,7 +400,6 @@ class ModelResource(FormResource):
queryset = queryset.order_by(ordering) queryset = queryset.order_by(ordering)
return queryset.filter(**kwargs) return queryset.filter(**kwargs)
@bound_resource_required
def get_url(self): def get_url(self):
""" """
Attempts to reverse resolve the url of the given model *instance* for Attempts to reverse resolve the url of the given model *instance* for
@ -409,6 +411,10 @@ class ModelResource(FormResource):
This method can be overridden if you need to set the resource url This method can be overridden if you need to set the resource url
reversing explicitly. reversing explicitly.
""" """
# The resource needs to be bound to an
# instance, or getting url is not possible
if not self.is_bound():
raise Exception("resource needs to be bound") #TODO: what exception?
if not hasattr(self, 'view_callable'): if not hasattr(self, 'view_callable'):
raise _SkipField raise _SkipField
@ -527,18 +533,36 @@ class ModelResource(FormResource):
def get_model(self): def get_model(self):
""" """
Return the model class for this view. Return the model class for this resource.
""" """
return getattr(self, 'model', getattr(self.view, 'model', None)) model = getattr(self, 'model', None)
if model is None:
model = getattr(self.view, 'model', None)
if model is None:
raise ImproperlyConfigured(u"%(cls)s is missing a model. Define "
u"%(cls)s.model." % {
'cls': self.__class__
})
return model
def get_queryset(self): def get_queryset(self):
""" """
Return the queryset that should be used when retrieving or listing Return the queryset that should be used when retrieving or listing
instances. instances.
""" """
return getattr(self, 'queryset', queryset = getattr(self, 'queryset', None)
getattr(self.view, 'queryset', if queryset is None:
self.get_model().objects.all())) queryset = getattr(self.view, 'queryset', None)
if queryset is None:
try:
model = self.get_model()
except ImproperlyConfigured:
raise ImproperlyConfigured(u"%(cls)s is missing a queryset. Define "
u"%(cls)s.model or %(cls)s.queryset." % {
'cls': self.__class__
})
queryset = model._default_manager.all()
return queryset._clone()
def get_ordering(self): def get_ordering(self):
""" """

View File

@ -157,7 +157,7 @@ class RendererIntegrationTests(TestCase):
_flat_repr = '{"foo": ["bar", "baz"]}' _flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = '{\n "foo": [\n "bar", \n "baz"\n ]\n}' _indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
class JSONRendererTests(TestCase): class JSONRendererTests(TestCase):

View File

@ -201,10 +201,11 @@ class ListModelView(ListResourceMixin, ModelView):
A view which provides default operations for list, against a model in the A view which provides default operations for list, against a model in the
database. database.
""" """
queryset = None
_suffix = 'List' _suffix = 'List'
class ListOrCreateModelView(PostResourceMixin, ListResourceMixin, ModelView): class ListOrCreateModelView(PostResourceMixin, ListModelView, ModelView):
""" """
A view which provides default operations for list and create, against a A view which provides default operations for list and create, against a
model in the database. model in the database.