mixins.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import unicode_literals, print_function
  2. import traceback
  3. from django.core.exceptions import ImproperlyConfigured, ValidationError
  4. from rest_framework import status
  5. from rest_framework.mixins import CreateModelMixin
  6. from rest_framework.response import Response
  7. __all__ = [
  8. 'BulkCreateModelMixin',
  9. 'BulkDestroyModelMixin',
  10. 'BulkUpdateModelMixin',
  11. ]
  12. class BulkCreateModelMixin(CreateModelMixin):
  13. """
  14. Either create a single or many model instances in bulk by using the
  15. Serializer's ``many=True`` ability from Django REST >= 2.2.5.
  16. .. note::
  17. This mixin uses the same method to create model instances
  18. as ``CreateModelMixin`` because both non-bulk and bulk
  19. requests will use ``POST`` request method.
  20. """
  21. def create(self, request, *args, **kwargs):
  22. bulk = isinstance(request.DATA, list)
  23. if not bulk:
  24. return super(BulkCreateModelMixin, self).create(request, *args, **kwargs)
  25. else:
  26. serializer = self.get_serializer(data=request.DATA, many=True)
  27. if serializer.is_valid():
  28. for obj in serializer.object:
  29. self.pre_save(obj)
  30. self.object = serializer.save(force_insert=True)
  31. for obj in self.object:
  32. self.post_save(obj, created=True)
  33. return Response(serializer.data, status=status.HTTP_201_CREATED)
  34. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  35. class BulkUpdateModelMixin(object):
  36. """
  37. Update model instances in bulk by using the Serializer's
  38. ``many=True`` ability from Django REST >= 2.2.5.
  39. """
  40. def get_object(self):
  41. try:
  42. super(BulkUpdateModelMixin, self).get_object()
  43. except ImproperlyConfigured:
  44. # probably happened when called get_object() within metdata()
  45. # which is not allowed on list viewset however since we are enabling
  46. # PUT here, we should handle the exception
  47. # if called within metadata(), we can simply swallow exception
  48. # since that method does not actually do anything
  49. # with the returned object
  50. for file, line, function, code in traceback.extract_stack():
  51. if all((file.endswith('rest_framework/generics.py'),
  52. function == 'metadata')):
  53. return
  54. # not called inside metadata() so probably something went
  55. # wrong and so we should reraise exception
  56. raise
  57. def bulk_update(self, request, *args, **kwargs):
  58. partial = kwargs.pop('partial', False)
  59. # restrict the update to the filtered queryset
  60. serializer = self.get_serializer(self.filter_queryset(self.get_queryset()),
  61. data=request.DATA,
  62. many=True,
  63. partial=partial)
  64. if serializer.is_valid():
  65. try:
  66. for obj in serializer.object:
  67. self.pre_save(obj)
  68. except ValidationError as err:
  69. # full_clean on model instances may be called in pre_save
  70. # so we have to handle eventual errors.
  71. return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
  72. self.object = serializer.save(force_update=True)
  73. for obj in self.object:
  74. self.post_save(obj, created=False)
  75. return Response(serializer.data, status=status.HTTP_200_OK)
  76. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  77. def partial_bulk_update(self, request, *args, **kwargs):
  78. kwargs['partial'] = True
  79. return self.bulk_update(request, *args, **kwargs)
  80. class BulkDestroyModelMixin(object):
  81. """
  82. Destroy model instances.
  83. """
  84. def allow_bulk_destroy(self, qs, filtered):
  85. """
  86. Hook to ensure that the bulk destroy should be allowed.
  87. By default this checks that the destroy is only applied to
  88. filtered querysets.
  89. """
  90. return qs is not filtered
  91. def bulk_destroy(self, request, *args, **kwargs):
  92. qs = self.get_queryset()
  93. filtered = self.filter_queryset(qs)
  94. if not self.allow_bulk_destroy(qs, filtered):
  95. return Response(status=status.HTTP_400_BAD_REQUEST)
  96. for obj in filtered:
  97. self.pre_delete(obj)
  98. obj.delete()
  99. self.post_delete(obj)
  100. return Response(status=status.HTTP_204_NO_CONTENT)