diff --git a/superset/models/slice.py b/superset/models/slice.py index 8b864f4ddc2..04c698ce95d 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -164,10 +164,13 @@ class Slice( # pylint: disable=too-many-public-methods @renders("datasource_url") def datasource_url(self) -> str | None: + # Use getattr to guard against datasource types that don't have explore_url + # (e.g. Query objects), which would otherwise raise AttributeError and cause + # the entire chart list response to fail. if self.table: - return self.table.explore_url + return getattr(self.table, "explore_url", None) datasource = self.datasource - return datasource.explore_url if datasource else None + return getattr(datasource, "explore_url", None) if datasource else None def datasource_name_text(self) -> str | None: if self.table: diff --git a/tests/unit_tests/models/slice_test.py b/tests/unit_tests/models/slice_test.py index dc6bbb86552..e55d00dede0 100644 --- a/tests/unit_tests/models/slice_test.py +++ b/tests/unit_tests/models/slice_test.py @@ -85,3 +85,41 @@ class TestSlice: """Test id_or_uuid_filter returns correct BinaryExpression.""" result = id_or_uuid_filter(input_value) assert result is not None + + def test_datasource_url_returns_none_when_datasource_lacks_explore_url(self): + """datasource_url() must not raise when the datasource has no explore_url. + + Charts whose datasource resolves to a Query (or any other type without + explore_url) used to raise AttributeError, which caused the entire chart + list API response to fail instead of just skipping that one chart. + """ + slc = Slice() + slc.id = 1 + + # Simulate a datasource object that does NOT have explore_url (e.g. Query) + mock_datasource = MagicMock(spec=[]) # spec=[] means no attributes at all + slc.table = mock_datasource + + result = slc.datasource_url() + assert result is None + + def test_datasource_url_returns_explore_url_when_present(self): + """datasource_url() returns the datasource explore_url when it exists.""" + slc = Slice() + slc.id = 1 + + mock_table = MagicMock() + mock_table.explore_url = "/explore/?datasource_type=table&datasource_id=1" + slc.table = mock_table + + result = slc.datasource_url() + assert result == "/explore/?datasource_type=table&datasource_id=1" + + def test_datasource_url_returns_none_when_no_datasource(self): + """datasource_url() returns None when there is no datasource.""" + slc = Slice() + slc.id = 1 + slc.table = None + + result = slc.datasource_url() + assert result is None