django-rest-framework/rest_framework/mixins.py

200 lines
6.9 KiB
Python
Raw Normal View History

"""
Basic building blocks for generic class based views.
We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
2012-11-22 03:20:49 +04:00
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
2012-09-30 20:31:28 +04:00
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
2013-02-10 20:50:46 +04:00
from rest_framework.request import clone_request
2014-01-15 00:35:09 +04:00
from rest_framework.settings import api_settings
import warnings
def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None):
2013-02-23 02:02:42 +04:00
"""
Given a model instance, and an optional pk and slug field,
return the full list of all other field names on that model.
For use when performing full_clean on a model instance,
so we only clean the required fields.
"""
include = []
if pk:
# Pending deprecation
2013-02-23 02:02:42 +04:00
pk_field = obj._meta.pk
while pk_field.rel:
pk_field = pk_field.rel.to._meta.pk
include.append(pk_field.name)
if slug_field:
# Pending deprecation
2013-02-23 02:02:42 +04:00
include.append(slug_field)
if lookup_field and lookup_field != 'pk':
include.append(lookup_field)
2013-02-23 02:02:42 +04:00
return [field.name for field in obj._meta.fields if field.name not in include]
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
2013-01-02 17:39:24 +04:00
if serializer.is_valid():
2012-10-25 15:15:31 +04:00
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
2013-02-07 01:28:03 +04:00
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
2013-01-02 17:39:24 +04:00
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
2012-11-14 16:24:20 +04:00
def get_success_headers(self, data):
try:
2014-01-15 00:35:09 +04:00
return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}
class ListModelMixin(object):
"""
List a queryset.
"""
2012-11-22 03:20:49 +04:00
empty_error = "Empty list and '%(class_name)s.allow_empty' is False."
2012-09-30 20:31:28 +04:00
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
2012-09-30 20:31:28 +04:00
# Default is to allow empty querysets. This can be altered by setting
# `.allow_empty = False`, to raise 404 errors on empty querysets.
if not self.allow_empty and not self.object_list:
warnings.warn(
'The `allow_empty` parameter is due to be deprecated. '
'To use `allow_empty=False` style behavior, You should override '
'`get_queryset()` and explicitly raise a 404 on empty querysets.',
PendingDeprecationWarning
)
class_name = self.__class__.__name__
error_msg = self.empty_error % {'class_name': class_name}
raise Http404(error_msg)
2012-09-30 20:31:28 +04:00
2013-04-25 20:40:17 +04:00
# Switch between paginated or standard style responses
page = self.paginate_queryset(self.object_list)
if page is not None:
2012-09-30 20:31:28 +04:00
serializer = self.get_pagination_serializer(page)
else:
serializer = self.get_serializer(self.object_list, many=True)
2012-09-30 20:31:28 +04:00
return Response(serializer.data)
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()
2012-11-06 21:04:48 +04:00
serializer = self.get_serializer(self.object)
return Response(serializer.data)
class UpdateModelMixin(object):
"""
Update a model instance.
"""
2013-05-18 01:09:23 +04:00
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
if self.object is None:
2013-02-07 01:28:03 +04:00
created = True
save_kwargs = {'force_insert': True}
2013-01-02 17:39:24 +04:00
success_status_code = status.HTTP_201_CREATED
2013-02-07 01:28:03 +04:00
else:
created = False
save_kwargs = {'force_update': True}
2013-02-07 01:28:03 +04:00
success_status_code = status.HTTP_200_OK
2012-10-05 19:24:52 +04:00
2013-01-02 17:39:24 +04:00
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
2012-10-08 15:52:56 +04:00
if serializer.is_valid():
try:
self.pre_save(serializer.object)
except ValidationError as err:
# full_clean on model instance may be called in pre_save, so we
# have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
self.object = serializer.save(**save_kwargs)
2013-02-07 01:28:03 +04:00
self.post_save(self.object, created=created)
2013-01-02 17:39:24 +04:00
return Response(serializer.data, status=success_status_code)
2012-10-08 15:52:56 +04:00
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
2013-04-05 01:24:30 +04:00
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
2013-08-23 16:35:50 +04:00
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
2012-10-25 15:15:31 +04:00
def pre_save(self, obj):
2012-10-05 19:24:52 +04:00
"""
2012-10-25 15:15:31 +04:00
Set any attributes on the object that are implicit in the request.
2012-10-05 19:24:52 +04:00
"""
2012-10-25 15:15:31 +04:00
# pk and/or slug attributes are implicit in the URL.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup = self.kwargs.get(lookup_url_kwarg, None)
2012-10-05 19:24:52 +04:00
pk = self.kwargs.get(self.pk_url_kwarg, None)
2013-02-23 02:02:42 +04:00
slug = self.kwargs.get(self.slug_url_kwarg, None)
slug_field = slug and self.slug_field or None
2013-02-23 02:02:42 +04:00
if lookup:
setattr(obj, self.lookup_field, lookup)
2012-10-05 19:24:52 +04:00
if pk:
setattr(obj, 'pk', pk)
if slug:
setattr(obj, slug_field, slug)
# Ensure we clean the attributes so that we don't eg return integer
# pk using a string representation, as provided by the url conf kwarg.
2013-01-02 17:39:24 +04:00
if hasattr(obj, 'full_clean'):
exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field)
2013-02-23 02:02:42 +04:00
obj.full_clean(exclude)
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
self.pre_delete(obj)
obj.delete()
self.post_delete(obj)
return Response(status=status.HTTP_204_NO_CONTENT)