apipatch.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. from rest_framework.response import Response
  2. from django.core.exceptions import PermissionDenied
  3. from django.db import transaction
  4. from django.http import Http404
  5. ALLOWED_OPS = ('add', 'remove', 'replace')
  6. class InvalidAction(Exception):
  7. pass
  8. class ApiPatch(object):
  9. def __init__(self):
  10. self._actions = []
  11. def add(self, path, handler):
  12. self._actions.append({
  13. 'op': 'add',
  14. 'path': path,
  15. 'handler': handler,
  16. })
  17. def remove(self, path, handler):
  18. self._actions.append({
  19. 'op': 'remove',
  20. 'path': path,
  21. 'handler': handler,
  22. })
  23. def replace(self, path, handler):
  24. self._actions.append({
  25. 'op': 'replace',
  26. 'path': path,
  27. 'handler': handler,
  28. })
  29. def dispatch(self, request, target):
  30. if not isinstance(request.data, list):
  31. return Response({
  32. 'detail': "PATCH request should be list of operations",
  33. }, status=400)
  34. detail = []
  35. is_errored = False
  36. patch = {'id': target.pk}
  37. for action in request.data:
  38. try:
  39. self.validate_action(action)
  40. self.dispatch_action(patch, request, target, action)
  41. detail.append('ok')
  42. except Http404:
  43. is_errored = True
  44. detail.append('NOT FOUND')
  45. break
  46. except (InvalidAction, PermissionDenied) as e:
  47. is_errored = True
  48. detail.append(e.args[0])
  49. break
  50. patch['detail'] = detail
  51. if is_errored:
  52. return Response(patch, status=400)
  53. else:
  54. return Response(patch)
  55. def dispatch_bulk(self, request, targets):
  56. if not isinstance(request.data.get('ops'), list):
  57. return Response({
  58. 'detail': "bulk PATCH request's ops value should be list of operations",
  59. }, status=400)
  60. is_errored = False
  61. result = []
  62. for target in targets:
  63. detail = []
  64. patch = {'id': target.pk}
  65. for action in request.data['ops']:
  66. try:
  67. self.validate_action(action)
  68. self.dispatch_action(patch, request, target, action)
  69. detail.append('ok')
  70. except Http404:
  71. is_errored = True
  72. detail.append('NOT FOUND')
  73. break
  74. except (InvalidAction, PermissionDenied) as e:
  75. is_errored = True
  76. detail.append(e.args[0])
  77. break
  78. patch['detail'] = detail
  79. result.append(patch)
  80. if is_errored:
  81. return Response(result, status=400)
  82. else:
  83. return Response(result)
  84. def validate_action(self, action):
  85. if not action.get('op'):
  86. raise InvalidAction(u"undefined op")
  87. if action.get('op') not in ALLOWED_OPS:
  88. raise InvalidAction(u'"%s" op is unsupported' % action.get('op'))
  89. if not action.get('path'):
  90. raise InvalidAction(u'"%s" op has to specify path' % action.get('op'))
  91. if 'value' not in action:
  92. raise InvalidAction(u'"%s" op has to specify value' % action.get('op'))
  93. def dispatch_action(self, patch, request, target, action):
  94. for handler in self._actions:
  95. if action['op'] == handler['op'] and action['path'] == handler['path']:
  96. with transaction.atomic():
  97. patch.update(handler['handler'](request, target, action['value']))