apipatch.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. from django.core.exceptions import PermissionDenied
  2. from django.db import transaction
  3. from django.http import Http404
  4. from rest_framework.response import Response
  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 validate_action(self, action):
  56. if not action.get('op'):
  57. raise InvalidAction(u"undefined op")
  58. if action.get('op') not in ALLOWED_OPS:
  59. raise InvalidAction(
  60. u'"%s" op is unsupported' % action.get('op'))
  61. if not action.get('path'):
  62. raise InvalidAction(
  63. u'"%s" op has to specify path' % action.get('op'))
  64. if 'value' not in action:
  65. raise InvalidAction(
  66. u'"%s" op has to specify value' % action.get('op'))
  67. def dispatch_action(self, patch, request, target, action):
  68. for handler in self._actions:
  69. if (action['op'] == handler['op'] and
  70. action['path'] == handler['path']):
  71. with transaction.atomic():
  72. patch.update(
  73. handler['handler'](request, target, action['value']))