123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- from django.core.exceptions import PermissionDenied
- from django.db import transaction
- from django.http import Http404
- from rest_framework.response import Response
- ALLOWED_OPS = ("add", "remove", "replace")
- class InvalidAction(Exception):
- pass
- class ApiPatch(object):
- def __init__(self):
- self._actions = []
- def add(self, path, handler):
- self._actions.append({"op": "add", "path": path, "handler": handler})
- def remove(self, path, handler):
- self._actions.append({"op": "remove", "path": path, "handler": handler})
- def replace(self, path, handler):
- self._actions.append({"op": "replace", "path": path, "handler": handler})
- def dispatch(self, request, target):
- if not isinstance(request.data, list):
- return Response(
- {"detail": "PATCH request should be list of operations"}, status=400
- )
- detail = []
- is_errored = False
- patch = {"id": target.pk}
- for action in request.data:
- try:
- self.validate_action(action)
- self.dispatch_action(patch, request, target, action)
- detail.append("ok")
- except Http404:
- is_errored = True
- detail.append("NOT FOUND")
- break
- except (InvalidAction, PermissionDenied) as e:
- is_errored = True
- detail.append(e.args[0])
- break
- patch["detail"] = detail
- if is_errored:
- return Response(patch, status=400)
- else:
- return Response(patch)
- def dispatch_bulk(self, request, targets):
- is_errored = False
- result = []
- for target in targets:
- detail = []
- patch = {"id": target.pk}
- for action in request.data["ops"]:
- try:
- self.validate_action(action)
- self.dispatch_action(patch, request, target, action)
- except Http404:
- is_errored = True
- detail.append("NOT FOUND")
- break
- except (InvalidAction, PermissionDenied) as e:
- is_errored = True
- detail.append(e.args[0])
- break
- if detail:
- patch["detail"] = detail
- result.append(patch)
- if is_errored:
- return Response(result, status=400)
- else:
- return Response(result)
- def validate_action(self, action):
- if not action.get("op"):
- raise InvalidAction("undefined op")
- if action.get("op") not in ALLOWED_OPS:
- raise InvalidAction('"%s" op is unsupported' % action.get("op"))
- if not action.get("path"):
- raise InvalidAction('"%s" op has to specify path' % action.get("op"))
- if "value" not in action:
- raise InvalidAction('"%s" op has to specify value' % action.get("op"))
- def dispatch_action(self, patch, request, target, action):
- for handler in self._actions:
- if action["op"] == handler["op"] and action["path"] == handler["path"]:
- with transaction.atomic():
- patch.update(handler["handler"](request, target, action["value"]))
|