apipatch.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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(u'"%s" op is unsupported' % action.get('op'))
  60. if not action.get('path'):
  61. raise InvalidAction(u'"%s" op has to specify path' % action.get('op'))
  62. if 'value' not in action:
  63. raise InvalidAction(u'"%s" op has to specify value' % action.get('op'))
  64. def dispatch_action(self, patch, request, target, action):
  65. for handler in self._actions:
  66. if action['op'] == handler['op'] and action['path'] == handler['path']:
  67. with transaction.atomic():
  68. patch.update(handler['handler'](request, target, action['value']))