From 28604efe547bc991b9a86365eff76b9872803e65 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 5 Jun 2025 18:31:03 +0100 Subject: [PATCH 1/2] feat: Controllable `ROOT_URLCONF` --- api/app/settings/common.py | 4 +-- api/app/urls/__init__.py | 0 api/app/urls/api.py | 69 ++++++++++++++++++++++++++++++++++++++ api/app/urls/common.py | 7 ++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 api/app/urls/__init__.py create mode 100644 api/app/urls/api.py create mode 100644 api/app/urls/common.py diff --git a/api/app/settings/common.py b/api/app/settings/common.py index 7e1405a4398f..1d56b8654fe1 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -414,8 +414,6 @@ ) MIDDLEWARE.append("core.middleware.admin.AdminWhitelistMiddleware") -ROOT_URLCONF = "app.urls" - TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", @@ -1153,6 +1151,8 @@ default=5, ) +ROOT_URLCONF = "app.urls.common" if TASK_PROCESSOR_MODE else "app.urls.api" + # Webhook settings DISABLE_WEBHOOKS = env.bool("DISABLE_WEBHOOKS", False) RETRY_WEBHOOKS = TASK_RUN_METHOD == TaskRunMethod.TASK_PROCESSOR diff --git a/api/app/urls/__init__.py b/api/app/urls/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/api/app/urls/api.py b/api/app/urls/api.py new file mode 100644 index 000000000000..00a53991fbe5 --- /dev/null +++ b/api/app/urls/api.py @@ -0,0 +1,69 @@ +import importlib + +from django.conf import settings +from django.contrib import admin +from django.urls import include, path, re_path +from django.views.generic.base import TemplateView + +from app import views +from app.urls.common import urlpatterns as common_urlpatterns +from users.views import password_reset_redirect + +urlpatterns = [ + *common_urlpatterns, + re_path(r"^api/v1/", include("api.urls.deprecated", namespace="api-deprecated")), + re_path(r"^api/v1/", include("api.urls.v1", namespace="api-v1")), + re_path(r"^api/v2/", include("api.urls.v2", namespace="api-v2")), + re_path(r"^admin/", admin.site.urls), + re_path( + r"^sales-dashboard/", + include("sales_dashboard.urls", namespace="sales_dashboard"), + ), + # this url is used to generate email content for the password reset workflow + re_path( + r"^password-reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1," + r"13}-[0-9A-Za-z]{1,20})/$", + password_reset_redirect, + name="password_reset_confirm", + ), + re_path( + r"^config/project-overrides", + views.project_overrides, + name="project_overrides", + ), + path( + "robots.txt", + TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), + ), +] + +if settings.DEBUG: + urlpatterns = [ + re_path(r"^__debug__/", include("debug_toolbar.urls")), + ] + urlpatterns + +if settings.SAML_INSTALLED: # pragma: no cover + urlpatterns += [ + path("api/v1/auth/saml/", include("saml.urls")), + ] + +if settings.WORKFLOWS_LOGIC_INSTALLED: # pragma: no cover + workflow_views = importlib.import_module("workflows_logic.views") + urlpatterns += [ + path("api/v1/features/workflows/", include("workflows_logic.urls")), + path( + "api/v1/environments//create-change-request/", + workflow_views.create_change_request, + name="create-change-request", + ), + path( + "api/v1/environments//list-change-requests/", + workflow_views.list_change_requests, + name="list-change-requests", + ), + ] + + +if settings.SERVE_FE_ASSETS: # pragma: no cover + # add route to serve FE assets for any unrecognised paths + urlpatterns.append(re_path(r"^.*$", views.index, name="index")) diff --git a/api/app/urls/common.py b/api/app/urls/common.py new file mode 100644 index 000000000000..a6b96f943948 --- /dev/null +++ b/api/app/urls/common.py @@ -0,0 +1,7 @@ +from common.core.urls import urlpatterns as core_urlpatterns +from django.urls import include, path + +urlpatterns = [ + *core_urlpatterns, + path("processor/", include("task_processor.urls")), +] From 562457fe087e4725b6f7e993df9c5c24a5fbdbbb Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 6 Jun 2025 16:32:49 +0100 Subject: [PATCH 2/2] fix tests, remove old module --- api/app/urls.py | 77 ------------------------- api/tests/unit/onboarding/conftest.py | 9 +-- api/tests/unit/onboarding/test_views.py | 14 +++-- 3 files changed, 11 insertions(+), 89 deletions(-) delete mode 100644 api/app/urls.py diff --git a/api/app/urls.py b/api/app/urls.py deleted file mode 100644 index f936b8d70953..000000000000 --- a/api/app/urls.py +++ /dev/null @@ -1,77 +0,0 @@ -import importlib - -from common.core.urls import urlpatterns as core_urlpatterns -from django.conf import settings -from django.contrib import admin -from django.urls import include, path, re_path -from django.views.generic.base import TemplateView - -from users.views import password_reset_redirect - -from . import views - -urlpatterns = [ - *core_urlpatterns, - path("processor/", include("task_processor.urls")), -] - -if not settings.TASK_PROCESSOR_MODE: - urlpatterns += [ - re_path( - r"^api/v1/", include("api.urls.deprecated", namespace="api-deprecated") - ), - re_path(r"^api/v1/", include("api.urls.v1", namespace="api-v1")), - re_path(r"^api/v2/", include("api.urls.v2", namespace="api-v2")), - re_path(r"^admin/", admin.site.urls), - re_path( - r"^sales-dashboard/", - include("sales_dashboard.urls", namespace="sales_dashboard"), - ), - # this url is used to generate email content for the password reset workflow - re_path( - r"^password-reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1," - r"13}-[0-9A-Za-z]{1,20})/$", - password_reset_redirect, - name="password_reset_confirm", - ), - re_path( - r"^config/project-overrides", - views.project_overrides, - name="project_overrides", - ), - path( - "robots.txt", - TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), - ), - ] - -if settings.DEBUG: - urlpatterns = [ - re_path(r"^__debug__/", include("debug_toolbar.urls")), - ] + urlpatterns - -if settings.SAML_INSTALLED: # pragma: no cover - urlpatterns += [ - path("api/v1/auth/saml/", include("saml.urls")), - ] - -if settings.WORKFLOWS_LOGIC_INSTALLED: # pragma: no cover - workflow_views = importlib.import_module("workflows_logic.views") - urlpatterns += [ - path("api/v1/features/workflows/", include("workflows_logic.urls")), - path( - "api/v1/environments//create-change-request/", - workflow_views.create_change_request, - name="create-change-request", - ), - path( - "api/v1/environments//list-change-requests/", - workflow_views.list_change_requests, - name="list-change-requests", - ), - ] - - -if settings.SERVE_FE_ASSETS: # pragma: no cover - # add route to serve FE assets for any unrecognised paths - urlpatterns.append(re_path(r"^.*$", views.index, name="index")) diff --git a/api/tests/unit/onboarding/conftest.py b/api/tests/unit/onboarding/conftest.py index 8188fe4f4050..c4b1d28cd996 100644 --- a/api/tests/unit/onboarding/conftest.py +++ b/api/tests/unit/onboarding/conftest.py @@ -2,12 +2,11 @@ import pytest from django.urls import clear_url_caches -from pytest_mock import MockerFixture def reload_onboarding_urls() -> None: import api.urls.v1 as v1_urls - import app.urls as root_urls + import app.urls.api as root_urls import onboarding.urls as onboarding_urls reload(onboarding_urls) @@ -18,12 +17,10 @@ def reload_onboarding_urls() -> None: @pytest.fixture() -def is_oss(mocker: MockerFixture) -> None: - mocker.patch("common.core.utils.is_oss", return_value=True) +def oss_mode() -> None: reload_onboarding_urls() @pytest.fixture() -def is_saas(mocker: MockerFixture) -> None: - mocker.patch("common.core.utils.is_saas", return_value=True) +def saas_mode(saas_mode: None) -> None: reload_onboarding_urls() diff --git a/api/tests/unit/onboarding/test_views.py b/api/tests/unit/onboarding/test_views.py index 1892379767fb..411e2d61221c 100644 --- a/api/tests/unit/onboarding/test_views.py +++ b/api/tests/unit/onboarding/test_views.py @@ -12,7 +12,8 @@ def test_send_onboarding_request_to_saas_flagsmith_view_for_non_admin_user( - test_user_client: APIClient, is_oss: MagicMock + test_user_client: APIClient, + oss_mode: None, ) -> None: # Given url = reverse("api-v1:onboarding:send-onboarding-request") @@ -25,7 +26,8 @@ def test_send_onboarding_request_to_saas_flagsmith_view_for_non_admin_user( def test_send_onboarding_request_to_saas_flagsmith_view_without_org( - admin_client_original: APIClient, is_oss: MagicMock + admin_client_original: APIClient, + oss_mode: None, ) -> None: # Given url = reverse("api-v1:onboarding:send-onboarding-request") @@ -46,7 +48,7 @@ def test_send_onboarding_request_to_saas_flagsmith_view( mocker: MockerFixture, organisation: Organisation, admin_user: FFAdminUser, - is_oss: MagicMock, + oss_mode: None, ) -> None: # Given mocked_requests = mocker.patch("onboarding.tasks.requests") @@ -73,7 +75,7 @@ def test_receive_support_request_from_self_hosted_view_without_hubspot_token( settings: SettingsWrapper, api_client: APIClient, db: None, - is_saas: MagicMock, + saas_mode: MagicMock, ) -> None: # Given settings.HUBSPOT_ACCESS_TOKEN = None @@ -93,7 +95,7 @@ def test_receive_support_request_from_self_hosted_view( api_client: APIClient, mocker: MockerFixture, db: None, - is_saas: None, + saas_mode: None, ) -> None: # Given settings.HUBSPOT_ACCESS_TOKEN = "some-token" @@ -123,7 +125,7 @@ def test_receive_support_request_throttling( api_client: APIClient, mocker: MockerFixture, db: None, - is_saas: MagicMock, + saas_mode: MagicMock, ) -> None: # Given settings.HUBSPOT_ACCESS_TOKEN = "some-token"