summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Meyers <chris.meyers.fsu@gmail.com>2024-04-08 21:58:14 +0200
committerChris Meyers <chrismeyersfsu@users.noreply.github.com>2024-04-10 22:03:09 +0200
commit0645d342dd3dbc39632ca35cb538bf34c82c0b58 (patch)
tree5b4a7347f5c431dc8bbac58bd0944a2eed0b5596
parentMove named url init out of Middleware init (diff)
downloadawx-0645d342dd3dbc39632ca35cb538bf34c82c0b58.tar.xz
awx-0645d342dd3dbc39632ca35cb538bf34c82c0b58.zip
Implement optional url prefix the Django way
* Before, the optional url prefix feature required calling our versioning version of reverse(). This worked _ok_ until we added more and more urls from 3rd party apps. Those 3rd party apps do not call our reverse(), writefully so. * This implementation looks at the incoming request path. If it includes the special optional prefix url, then we register ALL the urls WITH the optional url prefix. If the incoming request path does NOT contain the options url prefix then we register ALL the urls WITHOUT the optional url prefix. * Before this, we were registering BOTH sets of urls and then reverse() + the request as context to decide which url.
-rw-r--r--awx/api/versioning.py4
-rw-r--r--awx/main/middleware.py26
-rw-r--r--awx/settings/defaults.py1
-rw-r--r--awx/urls.py58
4 files changed, 60 insertions, 29 deletions
diff --git a/awx/api/versioning.py b/awx/api/versioning.py
index 09da66b620..ff10d9875b 100644
--- a/awx/api/versioning.py
+++ b/awx/api/versioning.py
@@ -29,9 +29,7 @@ def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra
kwargs = {}
if 'version' not in kwargs:
kwargs['version'] = settings.REST_FRAMEWORK['DEFAULT_VERSION']
- url = drf_reverse(viewname, args, kwargs, request, format, **extra)
-
- return transform_optional_api_urlpattern_prefix_url(request, url)
+ return drf_reverse(viewname, args, kwargs, request, format, **extra)
class URLPathVersioning(BaseVersioning):
diff --git a/awx/main/middleware.py b/awx/main/middleware.py
index b5c39b03a3..d485ce45f7 100644
--- a/awx/main/middleware.py
+++ b/awx/main/middleware.py
@@ -1,6 +1,7 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
+import functools
import logging
import threading
import time
@@ -18,6 +19,7 @@ from django.urls import reverse, resolve
from awx.main import migrations
from awx.main.utils.profiling import AWXProfiler
from awx.main.utils.common import memoize
+from awx.urls import get_urlpatterns
logger = logging.getLogger('awx.main.middleware')
@@ -173,3 +175,27 @@ class MigrationRanCheckMiddleware(MiddlewareMixin):
def process_request(self, request):
if is_migrating() and getattr(resolve(request.path), 'url_name', '') != 'migrations_notran':
return redirect(reverse("ui:migrations_notran"))
+
+
+class OptionalURLPrefixPath(MiddlewareMixin):
+ @functools.lru_cache
+ def _url_optional(self, prefix):
+ # Relavant Django code path https://github.com/django/django/blob/stable/4.2.x/django/core/handlers/base.py#L300
+ #
+ # resolve_request(request)
+ # get_resolver(request.urlconf)
+ # _get_cached_resolver(request.urlconf) <-- cached via @functools.cache
+ #
+ # Django will attempt to cache the value(s) of request.urlconf
+ # Being hashable is a prerequisit for being cachable.
+ # tuple() is hashable list() is not.
+ # Hence the tuple(list()) wrap.
+ return tuple(get_urlpatterns(prefix=prefix))
+
+ def process_request(self, request):
+ prefix = settings.OPTIONAL_API_URLPATTERN_PREFIX
+
+ if request.path.startswith(f"/api/{prefix}"):
+ request.urlconf = self._url_optional(prefix)
+ else:
+ request.urlconf = 'awx.urls'
diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py
index 2ff82d7fc5..b573d042b9 100644
--- a/awx/settings/defaults.py
+++ b/awx/settings/defaults.py
@@ -1002,6 +1002,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'awx.main.middleware.DisableLocalAuthMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
+ 'awx.main.middleware.OptionalURLPrefixPath',
'awx.sso.middleware.SocialAuthMiddleware',
'crum.CurrentRequestUserMiddleware',
'awx.main.middleware.URLModificationMiddleware',
diff --git a/awx/urls.py b/awx/urls.py
index 28ed6148ef..2fcfc650f9 100644
--- a/awx/urls.py
+++ b/awx/urls.py
@@ -9,36 +9,42 @@ from ansible_base.resource_registry.urls import urlpatterns as resource_api_urls
from awx.main.views import handle_400, handle_403, handle_404, handle_500, handle_csp_violation, handle_login_redirect
-urlpatterns = [
- re_path(r'', include('awx.ui.urls', namespace='ui')),
- re_path(r'^ui_next/.*', include('awx.ui_next.urls', namespace='ui_next')),
- path('api/', include('awx.api.urls', namespace='api')),
-]
+def get_urlpatterns(prefix=None):
+ if not prefix:
+ prefix = '/'
+ else:
+ prefix = f'/{prefix}/'
+
+ urlpatterns = [
+ re_path(r'', include('awx.ui.urls', namespace='ui')),
+ re_path(r'^ui_next/.*', include('awx.ui_next.urls', namespace='ui_next')),
+ path(f'api{prefix}', include('awx.api.urls', namespace='api')),
+ ]
-if settings.OPTIONAL_API_URLPATTERN_PREFIX:
urlpatterns += [
- path(f'api/{settings.OPTIONAL_API_URLPATTERN_PREFIX}/', include('awx.api.urls')),
+ path(f'api{prefix}v2/', include(resource_api_urls)),
+ re_path(r'^sso/', include('awx.sso.urls', namespace='sso')),
+ re_path(r'^sso/', include('social_django.urls', namespace='social')),
+ re_path(r'^(?:api/)?400.html$', handle_400),
+ re_path(r'^(?:api/)?403.html$', handle_403),
+ re_path(r'^(?:api/)?404.html$', handle_404),
+ re_path(r'^(?:api/)?500.html$', handle_500),
+ re_path(r'^csp-violation/', handle_csp_violation),
+ re_path(r'^login/', handle_login_redirect),
]
-urlpatterns += [
- re_path(r'^api/v2/', include(resource_api_urls)),
- re_path(r'^sso/', include('awx.sso.urls', namespace='sso')),
- re_path(r'^sso/', include('social_django.urls', namespace='social')),
- re_path(r'^(?:api/)?400.html$', handle_400),
- re_path(r'^(?:api/)?403.html$', handle_403),
- re_path(r'^(?:api/)?404.html$', handle_404),
- re_path(r'^(?:api/)?500.html$', handle_500),
- re_path(r'^csp-violation/', handle_csp_violation),
- re_path(r'^login/', handle_login_redirect),
-]
-
-if settings.SETTINGS_MODULE == 'awx.settings.development':
- try:
- import debug_toolbar
-
- urlpatterns += [re_path(r'^__debug__/', include(debug_toolbar.urls))]
- except ImportError:
- pass
+ if settings.SETTINGS_MODULE == 'awx.settings.development':
+ try:
+ import debug_toolbar
+
+ urlpatterns += [re_path(r'^__debug__/', include(debug_toolbar.urls))]
+ except ImportError:
+ pass
+
+ return urlpatterns
+
+
+urlpatterns = get_urlpatterns()
handler400 = 'awx.main.views.handle_400'
handler403 = 'awx.main.views.handle_403'