Browse Source

initial DRF3 support except update which requires to implement update logic

Miroslav Shubernetskiy 10 năm trước cách đây
mục cha
commit
7ce139e025

+ 27 - 6
rest_framework_bulk/drf2/mixins.py

@@ -1,5 +1,6 @@
 from __future__ import unicode_literals, print_function
-from django.core.exceptions import ValidationError
+import traceback
+from django.core.exceptions import ImproperlyConfigured, ValidationError
 from rest_framework import status
 from rest_framework.mixins import CreateModelMixin
 from rest_framework.response import Response
@@ -32,9 +33,11 @@ class BulkCreateModelMixin(CreateModelMixin):
         else:
             serializer = self.get_serializer(data=request.DATA, many=True)
             if serializer.is_valid():
-                [self.pre_save(obj) for obj in serializer.object]
+                for obj in serializer.object:
+                    self.pre_save(obj)
                 self.object = serializer.save(force_insert=True)
-                [self.post_save(obj, created=True) for obj in self.object]
+                for obj in self.object:
+                    self.post_save(obj, created=True)
                 return Response(serializer.data, status=status.HTTP_201_CREATED)
 
         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -47,7 +50,23 @@ class BulkUpdateModelMixin(object):
     """
 
     def get_object(self):
-        return self.get_queryset()
+        try:
+            super(BulkUpdateModelMixin, self).get_object()
+        except ImproperlyConfigured:
+            # probably happened when called get_object() within metdata()
+            # which is not allowed on list viewset however since we are enabling
+            # PUT here, we should handle the exception
+            # if called within metadata(), we can simply swallow exception
+            # since that method does not actually do anything
+            # with the returned object
+            for file, line, function, code in traceback.extract_stack():
+                if all((file.endswith('rest_framework/generics.py'),
+                        function == 'metadata')):
+                    return
+
+            # not called inside metadata() so probably something went
+            # wrong and so we should reraise exception
+            raise
 
     def bulk_update(self, request, *args, **kwargs):
         partial = kwargs.pop('partial', False)
@@ -60,13 +79,15 @@ class BulkUpdateModelMixin(object):
 
         if serializer.is_valid():
             try:
-                [self.pre_save(obj) for obj in serializer.object]
+                for obj in serializer.object:
+                    self.pre_save(obj)
             except ValidationError as err:
                 # full_clean on model instances 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(force_update=True)
-            [self.post_save(obj, created=False) for obj in self.object]
+            for obj in self.object:
+                self.post_save(obj, created=False)
             return Response(serializer.data, status=status.HTTP_200_OK)
 
         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

+ 112 - 0
rest_framework_bulk/drf3/mixins.py

@@ -1 +1,113 @@
 from __future__ import print_function, unicode_literals
+import traceback
+from rest_framework import status
+from rest_framework.mixins import CreateModelMixin
+from rest_framework.response import Response
+
+
+__all__ = [
+    'BulkCreateModelMixin',
+    'BulkDestroyModelMixin',
+    'BulkUpdateModelMixin',
+]
+
+
+class BulkCreateModelMixin(CreateModelMixin):
+    """
+    Either create a single or many model instances in bulk by using the
+    Serializer's ``many=True`` ability from Django REST >= 2.2.5.
+
+    .. note::
+        This mixin uses the same method to create model instances
+        as ``CreateModelMixin`` because both non-bulk and bulk
+        requests will use ``POST`` request method.
+    """
+
+    def create(self, request, *args, **kwargs):
+        bulk = isinstance(request.data, list)
+
+        if not bulk:
+            return super(BulkCreateModelMixin, self).create(request, *args, **kwargs)
+
+        else:
+            serializer = self.get_serializer(data=request.data, many=True)
+            serializer.is_valid(raise_exception=True)
+            self.perform_create(serializer)
+            return Response(serializer.data, status=status.HTTP_201_CREATED)
+
+
+class BulkUpdateModelMixin(object):
+    """
+    Update model instances in bulk by using the Serializer's
+    ``many=True`` ability from Django REST >= 2.2.5.
+    """
+
+    def get_object(self):
+        try:
+            super(BulkUpdateModelMixin, self).get_object()
+        except AssertionError:
+            # probably happened when called get_object() within options()
+            # via self.metadata_class which is not allowed on list viewset
+            # however since we are enabling PUT here, we should handle the
+            # exception if called within options()
+            # We can simply swallow the exception since that method
+            # does not actually do anythingwith the returned object
+            for file, line, function, code in traceback.extract_stack():
+                if all((file.endswith('rest_framework/views.py'),
+                        function == 'options')):
+                    return
+
+            # not called inside metadata() so probably something went
+            # wrong and so we should reraise exception
+            raise
+
+    def bulk_update(self, request, *args, **kwargs):
+        partial = kwargs.pop('partial', False)
+
+        # restrict the update to the filtered queryset
+        serializer = self.get_serializer(
+            self.filter_queryset(self.get_queryset()),
+            data=request.data,
+            many=True,
+            partial=partial,
+        )
+        serializer.is_valid(raise_exception=True)
+        self.perform_update(serializer)
+        return Response(serializer.data, status=status.HTTP_200_OK)
+
+    def perform_update(self, serializer):
+        serializer.save()
+
+    def partial_bulk_update(self, request, *args, **kwargs):
+        kwargs['partial'] = True
+        return self.bulk_update(request, *args, **kwargs)
+
+
+class BulkDestroyModelMixin(object):
+    """
+    Destroy model instances.
+    """
+
+    def allow_bulk_destroy(self, qs, filtered):
+        """
+        Hook to ensure that the bulk destroy should be allowed.
+
+        By default this checks that the destroy is only applied to
+        filtered querysets.
+        """
+        return qs is not filtered
+
+    def bulk_destroy(self, request, *args, **kwargs):
+        qs = self.get_queryset()
+
+        filtered = self.filter_queryset(qs)
+        if not self.allow_bulk_destroy(qs, filtered):
+            return Response(status=status.HTTP_400_BAD_REQUEST)
+
+        for obj in filtered:
+            self.perform_destroy(obj)
+
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+    def perform_destroy(self, instance):
+        instance.delete()

+ 9 - 0
rest_framework_bulk/tests/simple_app/serializers.py

@@ -0,0 +1,9 @@
+from __future__ import print_function, unicode_literals
+from rest_framework.serializers import ModelSerializer
+
+from .models import SimpleModel
+
+
+class SimpleSerializer(ModelSerializer):
+    class Meta(object):
+        model = SimpleModel

+ 11 - 8
rest_framework_bulk/tests/simple_app/views.py

@@ -1,22 +1,25 @@
 from __future__ import unicode_literals, print_function
 from rest_framework_bulk import generics
 
-from . import models
+from .models import SimpleModel
+from .serializers import SimpleSerializer
 
 
-class SimpleBulkAPIView(generics.ListBulkCreateUpdateDestroyAPIView):
-    model = models.SimpleModel
+class SimpleMixin(object):
+    model = SimpleModel
+    queryset = SimpleModel.objects.all()
+    serializer_class = SimpleSerializer
 
 
-class FilteredBulkAPIView(generics.ListBulkCreateUpdateDestroyAPIView):
-    model = models.SimpleModel
+class SimpleBulkAPIView(SimpleMixin, generics.ListBulkCreateUpdateDestroyAPIView):
+    pass
 
+
+class FilteredBulkAPIView(SimpleMixin, generics.ListBulkCreateUpdateDestroyAPIView):
     def filter_queryset(self, queryset):
         return queryset.filter(number__gt=5)
 
 
-class SimpleViewSet(generics.BulkModelViewSet):
-    model = models.SimpleModel
-
+class SimpleViewSet(SimpleMixin, generics.BulkModelViewSet):
     def filter_queryset(self, queryset):
         return queryset.filter(number__gt=5)

+ 11 - 1
rest_framework_bulk/tests/test_generics.py

@@ -152,11 +152,21 @@ class TestBulkAPIViewSet(TestCase):
         super(TestBulkAPIViewSet, self).setUp()
         self.url = reverse('api:simple-list')
 
+    def test_get_single(self):
+        """
+        Test that we are still able to query single resource
+        """
+        response = self.client.get(self.url)
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
     def test_get(self):
         """
         Test that GET returns 200
         """
-        response = self.client.get(self.url)
+        obj = SimpleModel.objects.create(contents='hello world', number=7)
+
+        response = self.client.get(reverse('api:simple-detail', args=[obj.pk]))
 
         self.assertEqual(response.status_code, status.HTTP_200_OK)