aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/mixins.py
diff options
context:
space:
mode:
authorTom Christie2013-03-18 21:03:05 +0000
committerTom Christie2013-03-18 21:03:05 +0000
commit74fb366c595db87bb71baeffcacfb7d2482e3a18 (patch)
tree2e28cb52542742f32cdd3fbeb625f7f59cba0a3f /rest_framework/mixins.py
parent4c6396108704d38f534a16577de59178b1d0df3b (diff)
parent034c4ce4081dd6d15ea47fb8318754321a3faf0c (diff)
downloaddjango-rest-framework-74fb366c595db87bb71baeffcacfb7d2482e3a18.tar.bz2
Merge branch 'master' into resources-routers
Diffstat (limited to 'rest_framework/mixins.py')
-rw-r--r--rest_framework/mixins.py67
1 files changed, 53 insertions, 14 deletions
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index e0ae216e..7d9a6e65 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -4,22 +4,48 @@ 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.
"""
+from __future__ import unicode_literals
+
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
+from rest_framework.request import clone_request
+
+
+def _get_validation_exclusions(obj, pk=None, slug_field=None):
+ """
+ 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:
+ 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:
+ include.append(slug_field)
+
+ return [field.name for field in obj._meta.fields if field.name not in include]
class CreateModelMixin(object):
"""
Create a model instance.
- Should be mixed in with any `BaseView`.
+ Should be mixed in with any `GenericAPIView`.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object)
- self.object = serializer.save()
+ self.object = serializer.save(force_insert=True)
+ self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
@@ -38,7 +64,7 @@ class ListModelMixin(object):
List a queryset.
Should be mixed in with `MultipleObjectAPIView`.
"""
- empty_error = u"Empty list and '%(class_name)s.allow_empty' is False."
+ empty_error = "Empty list and '%(class_name)s.allow_empty' is False."
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
@@ -60,7 +86,7 @@ class ListModelMixin(object):
paginator, page, queryset, is_paginated = packed
serializer = self.get_pagination_serializer(page)
else:
- serializer = self.get_serializer(self.object_list)
+ serializer = self.get_serializer(self.object_list, many=True)
return Response(serializer.data)
@@ -68,10 +94,12 @@ class ListModelMixin(object):
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
- Should be mixed in with `SingleObjectBaseView`.
+ Should be mixed in with `SingleObjectAPIView`.
"""
def retrieve(self, request, *args, **kwargs):
- self.object = self.get_object()
+ queryset = self.get_queryset()
+ filtered_queryset = self.filter_queryset(queryset)
+ self.object = self.get_object(filtered_queryset)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
@@ -79,23 +107,32 @@ class RetrieveModelMixin(object):
class UpdateModelMixin(object):
"""
Update a model instance.
- Should be mixed in with `SingleObjectBaseView`.
+ Should be mixed in with `SingleObjectAPIView`.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
+ self.object = None
try:
self.object = self.get_object()
- success_status_code = status.HTTP_200_OK
except Http404:
- self.object = None
+ # If this is a PUT-as-create operation, we need to ensure that
+ # we have relevant permissions, as if this was a POST request.
+ self.check_permissions(clone_request(request, 'POST'))
+ created = True
+ save_kwargs = {'force_insert': True}
success_status_code = status.HTTP_201_CREATED
+ else:
+ created = False
+ save_kwargs = {'force_update': True}
+ success_status_code = status.HTTP_200_OK
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
if serializer.is_valid():
self.pre_save(serializer.object)
- self.object = serializer.save()
+ self.object = serializer.save(**save_kwargs)
+ self.post_save(self.object, created=created)
return Response(serializer.data, status=success_status_code)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -106,24 +143,26 @@ class UpdateModelMixin(object):
"""
# pk and/or slug attributes are implicit in the URL.
pk = self.kwargs.get(self.pk_url_kwarg, None)
+ slug = self.kwargs.get(self.slug_url_kwarg, None)
+ slug_field = slug and self.get_slug_field() or None
+
if pk:
setattr(obj, 'pk', pk)
- slug = self.kwargs.get(self.slug_url_kwarg, None)
if slug:
- slug_field = self.get_slug_field()
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.
if hasattr(obj, 'full_clean'):
- obj.full_clean()
+ exclude = _get_validation_exclusions(obj, pk, slug_field)
+ obj.full_clean(exclude)
class DestroyModelMixin(object):
"""
Destroy a model instance.
- Should be mixed in with `SingleObjectBaseView`.
+ Should be mixed in with `SingleObjectAPIView`.
"""
def destroy(self, request, *args, **kwargs):
obj = self.get_object()