Files
edx-platform/common/djangoapps/util/json_request.py
Jesper Hodge a6c57811cd feat add xblock api endpoint (#32282)
* feat: add xblock endpoint for updating an xblock

fix: remove debugger

feat: make function call more generic

refactor: just use request.json for request data as before

refactor: extract method

fix: revert wrong method change

fix: refactor correct method

feat: use handle_xblock method so that we can do more than update xblocks

fix: usage_key_string defaults to None

add all CRUD operations

fix usage key parameter

refactor: create /views folder

refactor: move xblock view functions to xblock_services

fix: tests

fix: tests

refactor: move xblock API endpoint to contentstore

* docs: add explanatory comment to new xblock_service

* feat: add feature flag for enabling content editing api

* feat: raise 404 if studio content api is disabled

* tests: test xblock endpoint

* test: make all post tests work

* test: check that xblock_handler receives correct args

* refactor: create util mixin for course factories with staff

* refactor: extract course staff authorization tests

* refactor: extract tests to api view testcase class

* test: add get tests

* test: fix tests

* test: fix tests

* test: fix tests

* test: add all crud tests

* fix: refactor to fix tests

* fix: merge conflict

* fix: merge conflict

* fix: tests after merge

* fix: json request decorator

* fix: lint

* fix: lint

* fix: lint

* fix: lint

* fix: new test files

* fix: lint

* fix: lint and apply PR suggestions

* fix: lint

* fix: lint

* fix: lint

* fix: lint

* fix: lint

* fix: lint
2023-06-15 14:17:49 -04:00

128 lines
4.7 KiB
Python

# lint-amnesty, pylint: disable=missing-module-docstring
import decimal
import json
from functools import wraps
from django.core.serializers import serialize
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.query import QuerySet
from django.http import HttpResponse, HttpResponseBadRequest
class EDXJSONEncoder(DjangoJSONEncoder):
"""
Encoder for Decimal object, other objects will be encoded as per DjangoJSONEncoder default implementation.
NOTE:
Please see https://docs.djangoproject.com/en/1.8/releases/1.5/#system-version-of-simplejson-no-longer-used
DjangoJSONEncoder will now use the Python's json module but Python's json module don't know about how to
encode Decimal object, so as per default implementation Decimal objects will be encoded to `str` which we don't
want and also this is different from Django 1.4, In Django 1.4 if Decimal object has zeros after the decimal
point then object will be serialized as `int` else `float`, so we are keeping this behavior.
"""
def default(self, o): # pylint: disable=method-hidden
"""
Encode Decimal objects. If decimal object has zeros after the
decimal point then object will be serialized as `int` else `float`
"""
if isinstance(o, decimal.Decimal):
if o == o.to_integral():
return int(o)
return float(o)
else:
return super().default(o)
def expect_json(view_function):
"""
View decorator for simplifying handing of requests that expect json. If the request's
CONTENT_TYPE is application/json, parses the json dict from request.body, and updates
request.POST with the contents.
"""
@wraps(view_function)
def parse_json_into_request(request, *args, **kwargs):
# cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information
# e.g. 'charset', so we can't do a direct string compare
if "application/json" in request.META.get("CONTENT_TYPE", "") and request.body:
try:
request.json = json.loads(request.body.decode("utf8"))
except ValueError:
return JsonResponseBadRequest({"error": "Invalid JSON"})
else:
request.json = {}
return view_function(request, *args, **kwargs)
return parse_json_into_request
def expect_json_in_class_view(view):
"""
Class-based View decorator for simplifying handing of requests that expect json. If the request's
CONTENT_TYPE is application/json, parses the json dict from request.body, and updates
request.POST with the contents.
"""
def _wrapper_view(self, request, *args, **kwargs):
if "application/json" in request.META.get("CONTENT_TYPE", "") and request.body:
try:
request.json = json.loads(request.body.decode("utf8"))
except ValueError:
return JsonResponseBadRequest({"error": "Invalid JSON"})
else:
request.json = {}
return view(self, request, *args, **kwargs)
return _wrapper_view
class JsonResponse(HttpResponse):
"""
Django HttpResponse subclass that has sensible defaults for outputting JSON.
"""
def __init__( # lint-amnesty, pylint: disable=keyword-arg-before-vararg
self,
resp_obj=None,
status=None,
encoder=EDXJSONEncoder,
*args,
**kwargs
):
if resp_obj in (None, ""):
content = ""
status = status or 204
elif isinstance(resp_obj, QuerySet):
content = serialize("json", resp_obj)
else:
content = json.dumps(resp_obj, cls=encoder, indent=2, ensure_ascii=True)
kwargs.setdefault("content_type", "application/json")
if status:
kwargs["status"] = status
super().__init__(content, *args, **kwargs)
class JsonResponseBadRequest(HttpResponseBadRequest):
"""
Subclass of HttpResponseBadRequest that defaults to outputting JSON.
Use this to send BadRequestResponse & some Json object along with it.
Defaults:
dictionary: empty dictionary
status: 400
encoder: DjangoJSONEncoder
"""
def __init__(
self, obj=None, status=400, encoder=DjangoJSONEncoder, *args, **kwargs
): # lint-amnesty, pylint: disable=keyword-arg-before-vararg
if obj in (None, ""):
content = ""
else:
content = json.dumps(obj, cls=encoder, indent=2, ensure_ascii=False)
kwargs.setdefault("content_type", "application/json")
kwargs["status"] = status
super().__init__(content, *args, **kwargs)