mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-22 22:42:57 +03:00
started resource-agnostic views
This commit is contained in:
parent
9dbe8b646e
commit
d987b745f9
|
@ -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 ##########
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user