fix(subdirectory): avoid explore redirect loop when form_data has no datasource

CI 25898464041 (Python-Integration, postgres + mysql) failed
`test_slices` and `test_slice_id_is_always_logged_correctly_on_web_request`
with werkzeug.test.ClientRedirectError: Loop detected: A 302 redirect to
/explore/?slice_id=216&form_data={"slice_id": 216} was already made.

`Slice.slice_url` builds /explore/?slice_id=X&form_data={"slice_id": X},
i.e. no datasource. The ExploreView.root redirect added in d8335c0e1b
fires unconditionally when form_data is present, but
Superset.get_redirect_url() only rewrites the URL when
parsed_form_data["datasource"] exists (the form_data → form_data_key
caching path is gated on a datasource). Without one, the redirect target
equals the source and the test client follows itself in circles.

Fix: pre-parse form_data and only delegate to get_redirect_url when a
datasource is present; otherwise fall through to render_app_template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe Li
2026-05-15 09:26:37 -07:00
parent 9389a2a348
commit 89919a1c96

View File

@@ -21,6 +21,7 @@ from flask_appbuilder.security.decorators import has_access
from superset import event_logger
from superset.superset_typing import FlaskResponse
from superset.views.utils import loads_request_json
from .base import BaseSupersetView
@@ -37,11 +38,20 @@ class ExploreView(BaseSupersetView):
# After `Superset.route_base = ""`, both `Superset.explore` and this
# view register at `/explore/`; this view wins. Preserve the legacy
# form_data → form_data_key cache-and-redirect contract here so
# callers passing `?form_data=...` still get the short cache-key URL.
if request.args.get("form_data"):
from superset.views.core import Superset # avoid circular import
# callers passing `?form_data=...` with a datasource still get the
# short cache-key URL. Form_data without a datasource (e.g. legacy
# `slice_url` payloads carrying only `slice_id`) cannot be cached,
# so `get_redirect_url` would return the same URL — falling back to
# SPA rendering avoids a 302 loop.
if request_form_data := request.args.get("form_data"):
try:
parsed_form_data = loads_request_json(request_form_data)
except ValueError:
parsed_form_data = {}
if parsed_form_data.get("datasource"):
from superset.views.core import Superset # avoid circular import
return redirect(Superset.get_redirect_url())
return redirect(Superset.get_redirect_url())
return super().render_app_template()