mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-27 00:19:53 +03:00
Handle json_patch (rfc 6902)
This commit is contained in:
parent
580bf45ccf
commit
f8813b89f4
|
@ -7,6 +7,7 @@ on the request, such as form content or json encoded data.
|
|||
import codecs
|
||||
from urllib import parse
|
||||
|
||||
import jsonpatch
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadhandler import StopFutureHandlers
|
||||
from django.http import QueryDict
|
||||
|
@ -67,6 +68,33 @@ class JSONParser(BaseParser):
|
|||
raise ParseError('JSON parse error - %s' % str(exc))
|
||||
|
||||
|
||||
class JSONPatchParser(BaseParser):
|
||||
"""
|
||||
Parses PATCH RFC 6902 JSON-serialized data.
|
||||
"""
|
||||
|
||||
media_type = 'application/json-patch+json'
|
||||
renderer_class = renderers.JSONRenderer
|
||||
strict = api_settings.STRICT_JSON
|
||||
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
"""
|
||||
Parses the incoming bytestream as JSON and returns the resulting data as json patch.
|
||||
"""
|
||||
parser_context = parser_context or {}
|
||||
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
|
||||
|
||||
try:
|
||||
decoded_stream = codecs.getreader(encoding)(stream)
|
||||
parse_constant = json.strict_constant if self.strict else None
|
||||
data = json.load(decoded_stream, parse_constant=parse_constant)
|
||||
return jsonpatch.JsonPatch(data)
|
||||
except ValueError as exc:
|
||||
raise ParseError('JSON parse error - %s' % str(exc))
|
||||
except jsonpatch.InvalidJsonPatch as exc:
|
||||
raise ParseError('JSON Patch (rfc 6902) invalid - %s' % str(exc))
|
||||
|
||||
|
||||
class FormParser(BaseParser):
|
||||
"""
|
||||
Parser for form data.
|
||||
|
|
|
@ -23,6 +23,7 @@ from django.db.models.fields import Field as DjangoModelField
|
|||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from jsonpatch import JsonPatch
|
||||
|
||||
from rest_framework.compat import postgres_fields
|
||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||
|
@ -33,6 +34,7 @@ from rest_framework.utils.field_mapping import (
|
|||
ClassLookupDict, get_field_kwargs, get_nested_relation_kwargs,
|
||||
get_relation_kwargs, get_url_kwargs
|
||||
)
|
||||
from rest_framework.utils.json_patch import apply_json_patch
|
||||
from rest_framework.utils.serializer_helpers import (
|
||||
BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict,
|
||||
ReturnList
|
||||
|
@ -108,6 +110,12 @@ class BaseSerializer(Field):
|
|||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
self.instance = instance
|
||||
|
||||
if isinstance(data, JsonPatch):
|
||||
# serialise current instance to get a dict
|
||||
instance_serialized = self.__class__(instance).data
|
||||
data = apply_json_patch(patch=data, current_state=instance_serialized)
|
||||
|
||||
if data is not empty:
|
||||
self.initial_data = data
|
||||
self.partial = kwargs.pop('partial', False)
|
||||
|
|
|
@ -32,6 +32,7 @@ DEFAULTS = {
|
|||
],
|
||||
'DEFAULT_PARSER_CLASSES': [
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.JSONPatchParser',
|
||||
'rest_framework.parsers.FormParser',
|
||||
'rest_framework.parsers.MultiPartParser'
|
||||
],
|
||||
|
|
53
rest_framework/utils/json_patch.py
Normal file
53
rest_framework/utils/json_patch.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from jsonpatch import (
|
||||
JsonPatch,
|
||||
InvalidJsonPatch,
|
||||
JsonPatchConflict,
|
||||
JsonPatchTestFailed,
|
||||
JsonPointerException,
|
||||
)
|
||||
|
||||
from rest_framework.exceptions import ValidationError, ParseError
|
||||
|
||||
|
||||
def filter_state(state, paths_parts):
|
||||
filtered_state = {}
|
||||
for parts in paths_parts:
|
||||
if len(parts) > 1:
|
||||
parts = iter(parts)
|
||||
next_part = next(parts)
|
||||
parts = [list(parts)]
|
||||
filtered_state[next_part] = filter_state(state[next_part], parts)
|
||||
elif len(parts) == 1:
|
||||
filtered_state[parts[0]] = state[parts[0]]
|
||||
else:
|
||||
# empty parts will raise JsonPointerException during apply()
|
||||
# this type of error should be checked by json_patch at the
|
||||
# initilization not during the application.
|
||||
continue
|
||||
|
||||
return filtered_state
|
||||
|
||||
|
||||
def apply_json_patch(patch: JsonPatch, current_state: dict):
|
||||
field = None
|
||||
try:
|
||||
# empty parts will raise JsonPointerException during apply()
|
||||
# this type of error should be checked by json_patch at the
|
||||
# initilization not during the application.
|
||||
paths_parts = [[part for op in patch._ops for part in op.pointer.parts if part]]
|
||||
filtered_state = filter_state(current_state, paths_parts)
|
||||
return patch.apply(filtered_state)
|
||||
except KeyError:
|
||||
raise ValidationError(
|
||||
{'details': f'JSON Patch (rfc 6902) path does not exist - {field}'}
|
||||
)
|
||||
except JsonPatchConflict as exc:
|
||||
raise ValidationError({'details': f'JSON Patch (rfc 6902) conflict - {exc}'})
|
||||
except JsonPatchTestFailed as exc:
|
||||
raise ValidationError({'details': f'JSON Patch (rfc 6902) test failed - {exc}'})
|
||||
except JsonPointerException as exc:
|
||||
raise ValidationError(
|
||||
{'details': f"JSON Patch (rfc 6902) path's part invalid - {exc}"}
|
||||
)
|
||||
except InvalidJsonPatch as exc:
|
||||
raise ParseError(f'JSON Patch (rfc 6902) invalid - {exc}')
|
Loading…
Reference in New Issue
Block a user