mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-22 14:32:55 +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
|
||||
response.
|
||||
"""
|
||||
resource = None
|
||||
resource_class = None
|
||||
|
||||
@property
|
||||
def CONTENT(self):
|
||||
|
@ -431,8 +431,8 @@ class ResourceMixin(object):
|
|||
|
||||
@property
|
||||
def _resource(self):
|
||||
if self.resource:
|
||||
return self.resource(self)
|
||||
if self.resource_class:
|
||||
return self.resource_class(self)
|
||||
elif getattr(self, 'model', None):
|
||||
return ModelResource(self)
|
||||
elif getattr(self, 'form', None):
|
||||
|
@ -490,150 +490,59 @@ class InstanceMixin(object):
|
|||
return view
|
||||
|
||||
|
||||
########## Model Mixins ##########
|
||||
########## Resource operation Mixins ##########
|
||||
|
||||
class ReadResourceMixin(object):
|
||||
|
||||
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()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
try:
|
||||
return queryset.get(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
return None
|
||||
resource = self.resource_class.retrieve(request, *args, **kwargs)
|
||||
except self.resource_class.DoesNotExist:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
||||
return resource
|
||||
|
||||
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]
|
||||
class CreateResourceMixin(object):
|
||||
|
||||
instance = model(**kwargs)
|
||||
instance.save()
|
||||
def post(self, request, *args, **kwargs):
|
||||
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'):
|
||||
manager.add(*m2m_data[fieldname][1])
|
||||
else:
|
||||
data = {}
|
||||
data[manager.source_field_name] = instance
|
||||
class CreateSubResourceMixin(object):
|
||||
|
||||
for related_item in m2m_data[fieldname][1]:
|
||||
data[m2m_data[fieldname][0]] = related_item
|
||||
manager.through(**data).save()
|
||||
def post(self, request, *args, **kwargs):
|
||||
sub_resource = self.resource_class.create(request, *args, **kwargs)
|
||||
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):
|
||||
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)
|
||||
class UpdateResourceMixin(object):
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
headers = {}
|
||||
try:
|
||||
headers['Location'] = self.resource(self).url(instance)
|
||||
except: # TODO: _SkipField should not really happen.
|
||||
pass
|
||||
resource = self.resource_class.retrieve(request, *args, **kwargs)
|
||||
status_code = status.HTTP_204_NO_CONTENT
|
||||
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):
|
||||
kwargs = self._get_url_kwargs(kwargs)
|
||||
instance = self.delete_instance(**kwargs)
|
||||
if not instance:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
|
||||
class DeleteResourceMixin(object):
|
||||
|
||||
return instance
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
return self.list_instances(**kwargs)
|
||||
def delete(self, request, *args, **kwargs):
|
||||
try:
|
||||
resource = self.resource_class.retrieve(request, *args, **kwargs)
|
||||
except self.resource_class.DoesNotExist:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
||||
resource.delete(request, *args, **kwargs)
|
||||
return
|
||||
|
||||
|
||||
########## Pagination Mixins ##########
|
||||
|
|
|
@ -15,7 +15,10 @@ class BaseResource(Serializer):
|
|||
include = ()
|
||||
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)
|
||||
self.view = view
|
||||
|
||||
|
@ -33,6 +36,23 @@ class BaseResource(Serializer):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -426,3 +446,148 @@ class ModelResource(FormResource):
|
|||
return property_fields & set(self.fields)
|
||||
|
||||
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,
|
||||
or `None` to use default behaviour.
|
||||
"""
|
||||
resource = None
|
||||
resource_class = None
|
||||
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
resource = resources.ModelResource
|
||||
resource_class = resources.ModelResource
|
||||
|
||||
|
||||
class InstanceModelView(InstanceMixin, ModelView):
|
||||
|
|
Loading…
Reference in New Issue
Block a user