started resource-agnostic views

This commit is contained in:
Sébastien Piquemal 2012-01-03 11:59:56 +02:00
parent 9dbe8b646e
commit d987b745f9
3 changed files with 207 additions and 133 deletions

View File

@ -405,7 +405,7 @@ class ResourceMixin(object):
and filters the object representation into a serializable object for the and filters the object representation into a serializable object for the
response. response.
""" """
resource = None resource_class = None
@property @property
def CONTENT(self): def CONTENT(self):
@ -431,8 +431,8 @@ class ResourceMixin(object):
@property @property
def _resource(self): def _resource(self):
if self.resource: if self.resource_class:
return self.resource(self) return self.resource_class(self)
elif getattr(self, 'model', None): elif getattr(self, 'model', None):
return ModelResource(self) return ModelResource(self)
elif getattr(self, 'form', None): elif getattr(self, 'form', None):
@ -490,150 +490,59 @@ class InstanceMixin(object):
return view return view
########## Model Mixins ########## ########## Resource operation Mixins ##########
class ReadResourceMixin(object):
class ModelMixin(object): def get(self, request, *args, **kwargs):
def get_model(self):
"""
Return the model class for this view.
"""
return getattr(self, 'model', self.resource.model)
def get_queryset(self):
"""
Return the queryset that should be used when retrieving or listing
instances.
"""
return getattr(self, 'queryset',
getattr(self.resource, 'queryset',
self.get_model().objects.all()))
def get_ordering(self):
"""
Return the ordering that should be used when listing instances.
"""
return getattr(self, 'ordering',
getattr(self.resource, 'ordering',
None))
# Underlying instance API...
def get_instance(self, *args, **kwargs):
"""
Return a model instance or None.
"""
model = self.get_model()
queryset = self.get_queryset()
try: try:
return queryset.get(**kwargs) resource = self.resource_class.retrieve(request, *args, **kwargs)
except model.DoesNotExist: except self.resource_class.DoesNotExist:
return None raise ErrorResponse(status.HTTP_404_NOT_FOUND)
return resource
def create_instance(self, *args, **kwargs):
model = self.get_model()
m2m_data = {} class CreateResourceMixin(object):
for field in model._meta.many_to_many:
if field.name in kwargs:
m2m_data[field.name] = (
field.m2m_reverse_field_name(), kwargs[field.name]
)
del kwargs[field.name]
instance = model(**kwargs) def post(self, request, *args, **kwargs):
instance.save() resource = self.resource_class.create(request, *args, **kwargs)
resource.update(self.CONTENT, request, *args, **kwargs)
headers = {'Location': resource.get_url()}
return Response(status.HTTP_201_CREATED, resource, headers)
for fieldname in m2m_data:
manager = getattr(instance, fieldname)
if hasattr(manager, 'add'): class CreateSubResourceMixin(object):
manager.add(*m2m_data[fieldname][1])
else:
data = {}
data[manager.source_field_name] = instance
for related_item in m2m_data[fieldname][1]: def post(self, request, *args, **kwargs):
data[m2m_data[fieldname][0]] = related_item sub_resource = self.resource_class.create(request, *args, **kwargs)
manager.through(**data).save() sub_resource.update(self.CONTENT, request, *args, **kwargs)
headers = {'Location': sub_resource.get_url()}
return Response(status.HTTP_201_CREATED, sub_resource, headers)
return instance
def update_instance(self, instance, *args, **kwargs): class UpdateResourceMixin(object):
for (key, val) in kwargs.items():
setattr(instance, key, val)
instance.save()
return instance
def delete_instance(self, instance, *args, **kwargs):
instance.delete()
return instance
def list_instances(self, *args, **kwargs):
queryset = self.get_queryset()
ordering = self.get_ordering()
if ordering:
queryset = queryset.order_by(ordering)
return queryset.filter(**kwargs)
# Request/Response layer...
def _get_url_kwargs(self, kwargs):
format_arg = BaseRenderer._FORMAT_QUERY_PARAM
if format_arg in kwargs:
kwargs = kwargs.copy()
del kwargs[format_arg]
return kwargs
def _get_content_kwargs(self, kwargs):
return dict(self._get_url_kwargs(kwargs).items() +
self.CONTENT.items())
def read(self, request, *args, **kwargs):
kwargs = self._get_url_kwargs(kwargs)
instance = self.get_instance(**kwargs)
if instance is None:
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
return instance
def update(self, request, *args, **kwargs):
kwargs = self._get_url_kwargs(kwargs)
instance = self.get_instance(**kwargs)
kwargs = self._get_content_kwargs(kwargs)
if instance:
instance = self.update_instance(instance, **kwargs)
else:
instance = self.create_instance(**kwargs)
return instance
def create(self, request, *args, **kwargs):
kwargs = self._get_content_kwargs(kwargs)
instance = self.create_instance(**kwargs)
def put(self, request, *args, **kwargs):
headers = {} headers = {}
try: try:
headers['Location'] = self.resource(self).url(instance) resource = self.resource_class.retrieve(request, *args, **kwargs)
except: # TODO: _SkipField should not really happen. status_code = status.HTTP_204_NO_CONTENT
pass except self.resource_class.DoesNotExist:
resource = self.resource_class.create(request, *args, **kwargs)
status_code = status.HTTP_201_CREATED
resource.update(self.CONTENT, request, *args, **kwargs)
return Response(status_code, resource, {})
return Response(status.HTTP_201_CREATED, instance, headers)
def destroy(self, request, *args, **kwargs): class DeleteResourceMixin(object):
kwargs = self._get_url_kwargs(kwargs)
instance = self.delete_instance(**kwargs)
if not instance:
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
return instance def delete(self, request, *args, **kwargs):
try:
def list(self, request, *args, **kwargs): resource = self.resource_class.retrieve(request, *args, **kwargs)
return self.list_instances(**kwargs) except self.resource_class.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
resource.delete(request, *args, **kwargs)
return
########## Pagination Mixins ########## ########## Pagination Mixins ##########

View File

@ -15,7 +15,10 @@ class BaseResource(Serializer):
include = () include = ()
exclude = () exclude = ()
def __init__(self, view=None, depth=None, stack=[], **kwargs): # TODO: Inheritance, like for models
class DoesNotExist(Exception)
def __init__(self, depth=None, stack=[], **kwargs):
super(BaseResource, self).__init__(depth, stack, **kwargs) super(BaseResource, self).__init__(depth, stack, **kwargs)
self.view = view self.view = view
@ -33,6 +36,23 @@ class BaseResource(Serializer):
""" """
return self.serialize(obj) return self.serialize(obj)
@classmethod
def retrieve(cls, request, *args, **kwargs):
raise NotImplementedError()
@classmethod
def create(cls, request, *args, **kwargs):
raise NotImplementedError()
def update(self, data, request, *args, **kwargs):
raise NotImplementedError()
def delete(self, request, *args, **kwargs):
raise NotImplementedError()
def get_url(self):
raise NotImplementedError()
class Resource(BaseResource): class Resource(BaseResource):
""" """
@ -426,3 +446,148 @@ class ModelResource(FormResource):
return property_fields & set(self.fields) return property_fields & set(self.fields)
return property_fields.union(set(self.include)) - set(self.exclude) return property_fields.union(set(self.include)) - set(self.exclude)
class ModelMixin(object):
def get_model(self):
"""
Return the model class for this view.
"""
return getattr(self, 'model', self.resource.model)
def get_queryset(self):
"""
Return the queryset that should be used when retrieving or listing
instances.
"""
return getattr(self, 'queryset',
getattr(self.resource, 'queryset',
self.get_model().objects.all()))
def get_ordering(self):
"""
Return the ordering that should be used when listing instances.
"""
return getattr(self, 'ordering',
getattr(self.resource, 'ordering',
None))
# Underlying instance API...
def get_instance(self, *args, **kwargs):
"""
Return a model instance or None.
"""
model = self.get_model()
queryset = self.get_queryset()
try:
return queryset.get(**kwargs)
except model.DoesNotExist:
return None
def create_instance(self, *args, **kwargs):
model = self.get_model()
m2m_data = {}
for field in model._meta.many_to_many:
if field.name in kwargs:
m2m_data[field.name] = (
field.m2m_reverse_field_name(), kwargs[field.name]
)
del kwargs[field.name]
instance = model(**kwargs)
instance.save()
for fieldname in m2m_data:
manager = getattr(instance, fieldname)
if hasattr(manager, 'add'):
manager.add(*m2m_data[fieldname][1])
else:
data = {}
data[manager.source_field_name] = instance
for related_item in m2m_data[fieldname][1]:
data[m2m_data[fieldname][0]] = related_item
manager.through(**data).save()
return instance
def update_instance(self, instance, *args, **kwargs):
for (key, val) in kwargs.items():
setattr(instance, key, val)
instance.save()
return instance
def delete_instance(self, instance, *args, **kwargs):
instance.delete()
return instance
def list_instances(self, *args, **kwargs):
queryset = self.get_queryset()
ordering = self.get_ordering()
if ordering:
queryset = queryset.order_by(ordering)
return queryset.filter(**kwargs)
# Request/Response layer...
def _get_url_kwargs(self, kwargs):
format_arg = BaseRenderer._FORMAT_QUERY_PARAM
if format_arg in kwargs:
kwargs = kwargs.copy()
del kwargs[format_arg]
return kwargs
def _get_content_kwargs(self, kwargs):
return dict(self._get_url_kwargs(kwargs).items() +
self.CONTENT.items())
def read(self, request, *args, **kwargs):
kwargs = self._get_url_kwargs(kwargs)
instance = self.get_instance(**kwargs)
if instance is None:
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
return instance
def update(self, request, *args, **kwargs):
kwargs = self._get_url_kwargs(kwargs)
instance = self.get_instance(**kwargs)
kwargs = self._get_content_kwargs(kwargs)
if instance:
instance = self.update_instance(instance, **kwargs)
else:
instance = self.create_instance(**kwargs)
return instance
def create(self, request, *args, **kwargs):
kwargs = self._get_content_kwargs(kwargs)
instance = self.create_instance(**kwargs)
headers = {}
try:
headers['Location'] = self.resource(self).url(instance)
except: # TODO: _SkipField should not really happen.
pass
return Response(status.HTTP_201_CREATED, instance, headers)
def destroy(self, request, *args, **kwargs):
kwargs = self._get_url_kwargs(kwargs)
instance = self.delete_instance(**kwargs)
if not instance:
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
return instance
def list(self, request, *args, **kwargs):
return self.list_instances(**kwargs)

View File

@ -35,7 +35,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
The resource to use when validating requests and filtering responses, The resource to use when validating requests and filtering responses,
or `None` to use default behaviour. or `None` to use default behaviour.
""" """
resource = None resource_class = None
""" """
List of renderers the resource can serialize the response with, ordered by preference. List of renderers the resource can serialize the response with, ordered by preference.
@ -182,7 +182,7 @@ class ModelView(ModelMixin, View):
""" """
A RESTful view that maps to a model in the database. A RESTful view that maps to a model in the database.
""" """
resource = resources.ModelResource resource_class = resources.ModelResource
class InstanceModelView(InstanceMixin, ModelView): class InstanceModelView(InstanceMixin, ModelView):