From 5f4e4ea69a3f0cf8dcbed1050b3b139d816f4f9f Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:03:18 +0100 Subject: [PATCH] Vendor `decorator-include` --- src/openforms/urls.py | 5 +- src/openforms/utils/urls.py | 113 +++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/openforms/urls.py b/src/openforms/urls.py index 5bc988637b..b33ae47dcf 100644 --- a/src/openforms/urls.py +++ b/src/openforms/urls.py @@ -8,10 +8,9 @@ from django.urls import include, path from django.utils.translation import gettext_lazy as _ -from decorator_include import decorator_include - from openforms.emails.views import EmailWrapperTestView from openforms.submissions.dev_views import SubmissionPDFTestView +from openforms.utils.urls import decorator_include from openforms.utils.views import ErrorDetailView, SDKRedirectView handler500 = "openforms.utils.views.server_error" @@ -33,7 +32,7 @@ name="password_reset_complete", ), path("cookies/", include("cookie_consent.urls")), - path("tinymce/", decorator_include(login_required, "tinymce.urls")), + path("tinymce/", decorator_include(login_required, "tinymce.urls")), # type: ignore path("api/", include("openforms.api.urls", namespace="api")), path("auth/", include("openforms.authentication.urls", namespace="authentication")), path( diff --git a/src/openforms/utils/urls.py b/src/openforms/utils/urls.py index 598d3ec422..10b0c0cb1f 100644 --- a/src/openforms/utils/urls.py +++ b/src/openforms/utils/urls.py @@ -1,9 +1,13 @@ -from typing import Any +from __future__ import annotations + +from importlib import import_module +from typing import Any, Callable from urllib.parse import urljoin, urlsplit from django.conf import settings -from django.urls import reverse +from django.urls import URLPattern, URLResolver, include, reverse from django.utils.encoding import iri_to_uri +from django.utils.functional import cached_property from furl import furl @@ -110,3 +114,108 @@ def is_admin_request(request: AnyRequest) -> bool: return False admin_base = request.build_absolute_uri(admin_path_prefix) return referrer.startswith(admin_base) + + +class _DecoratedPatterns: + """ + A wrapper for an urlconf that applies a decorator to all its views. + """ + + # Vendored from https://github.com/twidi/django-decorator-include + + # BSD 2-Clause License + + # Copyright (c) 2016, Jeff Kistler + # All rights reserved. + + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + + # * Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. + + # * Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + def __init__(self, urlconf_module, decorators) -> None: + # ``urlconf_module`` may be: + # - an object with an ``urlpatterns`` attribute + # - an ``urlpatterns`` itself + # - the dotted Python path to a module with an ``urlpatters`` attribute + self.urlconf = urlconf_module + try: + iter(decorators) + except TypeError: + decorators = [decorators] + self.decorators = decorators + + def decorate_pattern(self, pattern): + if isinstance(pattern, URLResolver): + decorated = URLResolver( + pattern.pattern, + _DecoratedPatterns(pattern.urlconf_module, self.decorators), + pattern.default_kwargs, + pattern.app_name, + pattern.namespace, + ) + else: + callback = pattern.callback + for decorator in reversed(self.decorators): + callback = decorator(callback) + decorated = URLPattern( + pattern.pattern, + callback, + pattern.default_args, + pattern.name, + ) + return decorated + + @cached_property + def urlpatterns(self): + # urlconf_module might be a valid set of patterns, so we default to it. + patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module) + return [self.decorate_pattern(pattern) for pattern in patterns] + + @cached_property + def urlconf_module(self): + if isinstance(self.urlconf, str): + return import_module(self.urlconf) + else: + return self.urlconf + + @cached_property + def app_name(self): + return getattr(self.urlconf_module, "app_name", None) + + +def decorator_include( + decorators: Callable[..., Any] | list[Callable[..., Any]], + arg: Any, + namespace: str | None = None, +) -> tuple[_DecoratedPatterns, str | None, str | None]: + """ + Works like ``django.conf.urls.include`` but takes a view decorator + or a list of view decorators as the first argument and applies them, + in reverse order, to all views in the included urlconf. + """ + if isinstance(arg, tuple) and len(arg) == 3 and not isinstance(arg[0], str): + # Special case where the function is used for something like `admin.site.urls`, which + # returns a tuple with the object containing the urls, the app name, and the namespace + # `include` does not support this pattern (you pass directly `admin.site.urls`, without + # using `include`) but we have to + urlconf_module, app_name, namespace = arg + else: + urlconf_module, app_name, namespace = include(arg, namespace=namespace) + return _DecoratedPatterns(urlconf_module, decorators), app_name, namespace