From bc73c2d11b9ea73f41b906994bbadc746b7363f3 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 24 May 2017 11:58:56 +0300 Subject: [PATCH] Add superpowers to Router's URLconf generation --- lepo/decorators.py | 3 +++ lepo/router.py | 55 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 lepo/decorators.py diff --git a/lepo/decorators.py b/lepo/decorators.py new file mode 100644 index 0000000..b3bf223 --- /dev/null +++ b/lepo/decorators.py @@ -0,0 +1,3 @@ +def csrf_exempt(view): + view.csrf_exempt = True + return view diff --git a/lepo/router.py b/lepo/router.py index f927763..ae28200 100644 --- a/lepo/router.py +++ b/lepo/router.py @@ -1,7 +1,10 @@ +from collections import Iterable from copy import deepcopy +from functools import reduce from importlib import import_module from django.conf.urls import url +from django.http import HttpResponse from django.utils.text import camel_case_to_spaces from jsonschema import RefResolver @@ -10,6 +13,10 @@ from lepo.utils import maybe_resolve +def root_view(request): + return HttpResponse('API root') + + class Router: path_class = Path @@ -46,10 +53,54 @@ def get_paths(self): for path in self.api['paths']: yield self.get_path(path) - def get_urls(self): + def get_urls( + self, + root_view_name=None, + optional_trailing_slash=False, + decorate=(), + name_template='{name}', + ): + """ + Get the router's URLs, ready to be installed in `urlpatterns` (directly or via `include`). + + :param root_view_name: The optional url name for an API root view. + This may be useful for projects that do not explicitly know where the + router is mounted; those projects can then use `reverse('api:root')`, + for instance, if they need to construct URLs based on the API's root URL. + :type root_view_name: str|None + + :param optional_trailing_slash: Whether to fix up the regexen for the router to make any trailing + slashes optional. + :type optional_trailing_slash: bool + + :param decorate: A function to decorate view functions with, or an iterable of such decorators. + Use `(lepo.decorators.csrf_exempt,)` to mark all API views as CSRF exempt. + :type decorate: function|Iterable[function] + + :param name_template: A `.format()` template for view naming. + :type name_template: str + + :return: List of URL tuples. + :rtype: list[tuple] + """ + if isinstance(decorate, Iterable): + decorators = decorate + def decorate(view): + return reduce(lambda view, decorator: decorator(view), decorators, view) + urls = [] for path in self.get_paths(): - urls.append(url(path.regex, path.view_class.as_view(), name=path.name)) + regex = path.regex + if optional_trailing_slash: + regex = regex.rstrip('$') + if not regex.endswith('/'): + regex += '/' + regex += '?$' + view = decorate(path.view_class.as_view()) + urls.append(url(regex, view, name=name_template.format(name=path.name))) + + if root_view_name: + urls.append(url(r'^$', root_view, name=name_template.format(name=root_view_name))) return urls def get_handler(self, operation_id):