Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vendor decorator-include #4083

Merged
merged 1 commit into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/openforms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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(
Expand Down
113 changes: 111 additions & 2 deletions src/openforms/utils/urls.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -110,3 +114,108 @@
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(

Check warning on line 166 in src/openforms/utils/urls.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/utils/urls.py#L166

Added line #L166 was not covered by tests
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)

Check warning on line 194 in src/openforms/utils/urls.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/utils/urls.py#L194

Added line #L194 was not covered by tests
else:
return self.urlconf

@cached_property
def app_name(self):
return getattr(self.urlconf_module, "app_name", None)

Check warning on line 200 in src/openforms/utils/urls.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/utils/urls.py#L200

Added line #L200 was not covered by tests


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
Loading