Parcourir la source

Implement support for bulk deletion of objects via a single REST API request

Jeremy Stretch il y a 5 ans
Parent
commit
6694ec78bc
1 fichiers modifiés avec 53 ajouts et 3 suppressions
  1. 53 3
      netbox/utilities/api.py

+ 53 - 3
netbox/utilities/api.py

@@ -8,13 +8,13 @@ from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDo
 from django.db import transaction
 from django.db.models import ManyToManyField, ProtectedError
 from django.urls import reverse
-from rest_framework import serializers
+from rest_framework import mixins, serializers, status
 from rest_framework.exceptions import APIException, ValidationError
 from rest_framework.permissions import BasePermission
 from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
 from rest_framework.response import Response
 from rest_framework.routers import DefaultRouter
-from rest_framework.viewsets import ModelViewSet as _ModelViewSet
+from rest_framework.viewsets import GenericViewSet
 
 from .utils import dict_to_filter_params, dynamic_import
 
@@ -291,11 +291,53 @@ class WritableNestedSerializer(serializers.ModelSerializer):
             )
 
 
+class BulkDeleteSerializer(serializers.Serializer):
+    id = serializers.IntegerField()
+
+
+#
+# Mixins
+#
+
+class BulkDestroyModelMixin:
+    """
+    Support bulk deletion of objects using the list endpoint for a model. Accepts a DELETE action with a list of one
+    or more JSON objects, each specifying the numeric ID of an object to be deleted. For example:
+
+    DELETE /api/dcim/sites/
+    [
+        {"id": 123},
+        {"id": 456}
+    ]
+    """
+    def bulk_destroy(self, request):
+        serializer = BulkDeleteSerializer(data=request.data, many=True)
+        serializer.is_valid(raise_exception=True)
+
+        pk_list = [o['id'] for o in serializer.data]
+        qs = self.get_queryset().filter(pk__in=pk_list)
+
+        self.perform_bulk_destroy(qs)
+
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+    def perform_bulk_destroy(self, objects):
+        with transaction.atomic():
+            for obj in objects:
+                self.perform_destroy(obj)
+
+
 #
 # Viewsets
 #
 
-class ModelViewSet(_ModelViewSet):
+class ModelViewSet(mixins.CreateModelMixin,
+                   mixins.RetrieveModelMixin,
+                   mixins.UpdateModelMixin,
+                   mixins.DestroyModelMixin,
+                   mixins.ListModelMixin,
+                   BulkDestroyModelMixin,
+                   GenericViewSet):
     """
     Accept either a single object or a list of objects to create.
     """
@@ -408,6 +450,14 @@ class ModelViewSet(_ModelViewSet):
 
 class OrderedDefaultRouter(DefaultRouter):
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # Extend the list view mappings to support the DELETE operation
+        self.routes[0].mapping.update({
+            'delete': 'bulk_destroy',
+        })
+
     def get_api_root_view(self, api_urls=None):
         """
         Wrap DRF's DefaultRouter to return an alphabetized list of endpoints.