apipatch.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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({'detail': "PATCH request should be list of operations"}, status=400)
  32. detail = []
  33. is_errored = False
  34. patch = {'id': target.pk}
  35. for action in request.data:
  36. try:
  37. self.validate_action(action)
  38. self.dispatch_action(patch, request, target, action)
  39. detail.append('ok')
  40. except Http404:
  41. is_errored = True
  42. detail.append('NOT FOUND')
  43. break
  44. except (InvalidAction, PermissionDenied) as e:
  45. is_errored = True
  46. detail.append(e.args[0])
  47. break
  48. patch['detail'] = detail
  49. if is_errored:
  50. return Response(patch, status=400)
  51. else:
  52. return Response(patch)
  53. def validate_action(self, action):
  54. if not action.get('op'):
  55. raise InvalidAction(u"undefined op")
  56. if action.get('op') not in ALLOWED_OPS:
  57. raise InvalidAction(u'"%s" op is unsupported' % action.get('op'))
  58. if not action.get('path'):
  59. raise InvalidAction(u'"%s" op has to specify path' % action.get('op'))
  60. if 'value' not in action:
  61. raise InvalidAction(u'"%s" op has to specify value' % action.get('op'))
  62. def dispatch_action(self, patch, request, target, action):
  63. for handler in self._actions:
  64. if action['op'] == handler['op'] and action['path'] == handler['path']:
  65. with transaction.atomic():
  66. patch.update(handler['handler'](request, target, action['value']))