From 40caa4dac3fd629b013b26a460fe29d12ae68b08 Mon Sep 17 00:00:00 2001 From: Wes Williams Date: Thu, 20 Mar 2025 17:21:48 -0400 Subject: [PATCH 1/2] Rename jsonate_request -> jsonate response. Add deserialize_request. Add APIReqHandler. --- jsonate/decorators.py | 16 +++++++++--- jsonate/request_handler.py | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 jsonate/request_handler.py diff --git a/jsonate/decorators.py b/jsonate/decorators.py index a41562f..a2d71f3 100644 --- a/jsonate/decorators.py +++ b/jsonate/decorators.py @@ -1,3 +1,4 @@ +import json from jsonate.http import JsonateResponse from django.http import HttpResponse @@ -14,7 +15,7 @@ def inner(wrapper): return wrapper return inner -def jsonate_request(func): +def jsonate_response(func): """ Serializes whatever the view returns to JSON and returns it with mimetype "application/json" (uses jsonate.http.JsonateResponse) @@ -24,11 +25,11 @@ def jsonate_request(func): examples: - @jsonate_request + @jsonate_response def my_view(request): return User.objects.all() - @jsonate_request + @jsonate_response def my_view(request): form = MyForm(request.POST or None) if form.is_valid(): @@ -46,4 +47,11 @@ def wrapper(request, *args, **kwargs): if request.GET.get("callback"): return JsonateResponse(resp, jsonp_callback=request.GET['callback']) return JsonateResponse(resp) - return wrapper \ No newline at end of file + return wrapper + + +def deserialize_request(view_f): + "Deserialize a json request body to a Python object." + def _view_wrapper(request, *args, **kwargs): + return view_f(json.loads(request.body), *args, **kwargs) + return _view_wrapper diff --git a/jsonate/request_handler.py b/jsonate/request_handler.py new file mode 100644 index 0000000..c2e40c2 --- /dev/null +++ b/jsonate/request_handler.py @@ -0,0 +1,52 @@ +from django.http import Http404 +from django.core.exceptions import ValidationError + + +class APIReqHandler: + """ + The APIReqHandler class provides a wrapper that adds + a message parameter to a function that returns a dictionary. + Combine with the jsonate_response decorator to use with + clients expecting json. + + If the view raised no errors, the message content is 'ok'. + Http404 and validation errors return the error message to the client, + all other exceptions are raised. + + Example: + @jsonate_response + def my_view(request, some_arg) + return APIReqHandler(my_func).handle(some_arg) + + Parameters: + - view_f (function): A function that returns a dictionary. + - resp_ok (function, optional): A function to execute if no errors are raised. + - resp_err (function, optional): A function to execute if errors are raised. + + Methods: + - handle(*args, **kwargs) -- return the function with added message. + """ + def __init__(self, view_f, resp_ok=None, resp_err=None): + self.view_f = view_f + self.resp_ok = resp_ok or self._resp_ok + self.resp_err = resp_err or self._resp_error + + @staticmethod + def _resp_ok(extra): + extra_data = extra or {} + return { + "message": "ok", + **{k: v for k, v in extra_data.items()} + } + + @staticmethod + def _resp_error(err): + return {"message": f"{err.__class__.__name__}: {err}"} + + def handle(self, *args, **kwargs): + try: + return self.resp_ok(self.view_f(*args, **kwargs)) + except (Http404, ValidationError) as err: + return self.resp_err(err) + except Exception: + raise From ba39c50fe958ca075ad41d65fb7ce90e80534b7c Mon Sep 17 00:00:00 2001 From: Wes Williams Date: Fri, 21 Mar 2025 10:55:28 -0400 Subject: [PATCH 2/2] Update readme, inc version. --- README.md | 4 ++-- pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 48fa10d..c47879a 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ def my_view(request): ## Decorator The `JsonateResponse` is great, but life could get even easier! The -`@jsonate_request` decorator (inspired by the ajax_request decorator +`@jsonate_response` decorator (inspired by the ajax_request decorator in django-annoying) will try to serialize anything a view returns (via JsonateResponse) return it in an HttpResponse with mimetype "application/json" @@ -241,7 +241,7 @@ The only thing it will *not* try to serialize is an HttpResponse. example: ```python -@jsonate_request +@jsonate_response def my_view(request): form = MyForm(request.POST) if form.is_valid(): diff --git a/pyproject.toml b/pyproject.toml index c65013c..ea96d9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "jsonate" -version = "0.7.14" +version = "1.0.0" authors = [ {name = "James Robert"}, {email = "jiaaro@gmail.com"} @@ -14,7 +14,7 @@ license = {text = "MIT"} keywords = ["django", "json", "templatetags"] urls = {homepage = "http://jsonate.com"} requires-python = ">=3.12" -dependencies = ["django>=4.2.6"] +dependencies = ["django>=5.1.0"] readme = "README.md" classifiers= [ "Development Status :: 4 - Beta",