apipatch.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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({"op": "add", "path": path, "handler": handler})
  13. def remove(self, path, handler):
  14. self._actions.append({"op": "remove", "path": path, "handler": handler})
  15. def replace(self, path, handler):
  16. self._actions.append({"op": "replace", "path": path, "handler": handler})
  17. def dispatch(self, request, target):
  18. if not isinstance(request.data, list):
  19. return Response(
  20. {"detail": "PATCH request should be list of operations"}, status=400
  21. )
  22. detail = []
  23. is_errored = False
  24. patch = {"id": target.pk}
  25. for action in request.data:
  26. try:
  27. self.validate_action(action)
  28. self.dispatch_action(patch, request, target, action)
  29. detail.append("ok")
  30. except Http404:
  31. is_errored = True
  32. detail.append("NOT FOUND")
  33. break
  34. except (InvalidAction, PermissionDenied) as e:
  35. is_errored = True
  36. detail.append(e.args[0])
  37. break
  38. patch["detail"] = detail
  39. if is_errored:
  40. return Response(patch, status=400)
  41. else:
  42. return Response(patch)
  43. def dispatch_bulk(self, request, targets):
  44. is_errored = False
  45. result = []
  46. for target in targets:
  47. detail = []
  48. patch = {"id": target.pk}
  49. for action in request.data["ops"]:
  50. try:
  51. self.validate_action(action)
  52. self.dispatch_action(patch, request, target, action)
  53. except Http404:
  54. is_errored = True
  55. detail.append("NOT FOUND")
  56. break
  57. except (InvalidAction, PermissionDenied) as e:
  58. is_errored = True
  59. detail.append(e.args[0])
  60. break
  61. if detail:
  62. patch["detail"] = detail
  63. result.append(patch)
  64. if is_errored:
  65. return Response(result, status=400)
  66. else:
  67. return Response(result)
  68. def validate_action(self, action):
  69. if not action.get("op"):
  70. raise InvalidAction("undefined op")
  71. if action.get("op") not in ALLOWED_OPS:
  72. raise InvalidAction('"%s" op is unsupported' % action.get("op"))
  73. if not action.get("path"):
  74. raise InvalidAction('"%s" op has to specify path' % action.get("op"))
  75. if "value" not in action:
  76. raise InvalidAction('"%s" op has to specify value' % action.get("op"))
  77. def dispatch_action(self, patch, request, target, action):
  78. for handler in self._actions:
  79. if action["op"] == handler["op"] and action["path"] == handler["path"]:
  80. with transaction.atomic():
  81. patch.update(handler["handler"](request, target, action["value"]))