Source code for banjo.urls

from django.urls import path
from django.http import JsonResponse, HttpResponseNotAllowed
from django.shortcuts import render
from django.conf import settings
from banjo import http
from banjo import views
from banjo.forms import ApiRouteForm
import json

urlpatterns = [
    path(settings.API_PREFIX, views.api, name="api"),
    path(settings.API_PREFIX + ".json", views.api_json, name="api_json"),
]
user_defined_routes = []

[docs] class JsonRequiresDict(TypeError): pass
[docs] def validate_args(args): """Checks that args (the param type signature) is valid. """ if args is None: return allowed_types = [str, bool, int, float] if not isinstance(args, dict): raise ValueError("args must be a dict if provided") for param, type_ in args.items(): if type_ not in allowed_types: raise ValueError("param {} in args must be one of: {}".format( param, ', '.join(str(t) for t in allowed_types)))
[docs] def create_view(fn, method, args=None): """Creates a django view function. The view function checks that the HTTP method is correct, extracts the request's params, passes them to ``fn``, and returns the result. Args, if provided, should be a dict of ``{param_name: type}``, where type is in ``[str, bool, int, float]``. """ method = method.upper() def view(request): if request.method != method: return HttpResponseNotAllowed([method]) if args: if request.headers.get('Content-Type') == 'application/json': data = json.loads(request.body.decode("utf-8")) elif method == 'GET': data = request.GET else: data = request.POST FormClass = ApiRouteForm.for_args(args) form = FormClass(data) if form.is_valid(): try: result = fn(form.cleaned_data) if not isinstance(result, dict): raise JsonRequiresDict(f"Banjo views must return a dict, not {result}.") return JsonResponse(result) except http.BadRequest as e: return JsonResponse({'error': str(e)}, status=e.status_code) else: return JsonResponse({'errors': form.errors}, status=400) else: try: result = fn({}) if not isinstance(result, dict): raise JsonRequiresDict(f"Banjo views must return a dict, not {result}.") return JsonResponse(result) except http.BadRequest as e: return JsonResponse({'error': str(e)}, status=e.status_code) view.__name__ = fn.__name__ view.__doc__ = fn.__doc__ view.method = method validate_args(args) view.args = args or {} return view
[docs] def create_api_view(url, fn, method, args=None): """Creates an API view. An API view responds to GET requests with a HTML template-- the response data for GET routes and a form for POST routes. For POST routes, the view processes the form. """ method = method.upper() fn.method = method def api_view(request): fn.args = args or {} route = views.describe_route(path(url, fn, name=fn.__name__)) if method == "GET" and request.method == "GET": if args: form = ApiRouteForm.for_args(args)(request.GET) if form.is_valid(): try: result = fn(form.cleaned_data) except http.BadRequest as e: result = {'error': str(e), 'status_code': e.status_code} else: result = None else: form = None try: result = fn({}) except http.BadRequest as e: result = {'error': str(e), 'status_code': e.status_code} return render(request, "banjo/api_get.html", { "route": route, "result": json.dumps(result, indent=True) if result else None, "form": form, }) if method == "POST": if request.method == "POST": if args: form = ApiRouteForm.for_args(args)(request.POST) if form.is_valid(): try: result = fn(form.cleaned_data) except http.BadRequest as e: result = {'error': str(e), 'status_code': e.status_code} form = ApiRouteForm.for_args(args)() else: result = None else: form = None try: result = fn({}) except http.BadRequest as e: result = {'error': str(e), 'status_code': e.status_code} else: if args: form = ApiRouteForm.for_args(args)() else: form = None result = None return render(request, "banjo/api_post.html", { "route": route, "result": json.dumps(result, indent=True) if result else None, "form": form, }) return HttpResponseNotAllowed([request.method]) api_view.__name__ = fn.__name__ api_view.__doc__ = fn.__doc__ validate_args(args) api_view.args = args or {} return api_view
[docs] def route_get(url, args=None): """A decorator which registers a HTTP GET route in the Banjo app. """ def bind_url_to_view(fn): view = create_view(fn, "GET", args=args) api_view = create_api_view(url, fn, "GET", args=args) urlpatterns.append(path(url, view, name=view.__name__)) urlpatterns.append(path(settings.API_PREFIX + '/' + url, api_view, name="api_" + view.__name__)) user_defined_routes.append(path(url, view, name=view.__name__)) return view return bind_url_to_view
[docs] def route_post(url, args=None): """A decorator which registers a HTTP POST route in the Banjo app. """ def bind_url_to_view(fn): view = create_view(fn, "POST", args=args) api_view = create_api_view(url, fn, "POST", args=args) urlpatterns.append(path(url, view, name=view.__name__)) urlpatterns.append(path(settings.API_PREFIX + '/' + url, api_view, name="api_" + view.__name__)) user_defined_routes.append(path(url, view, name=view.__name__)) return view return bind_url_to_view