diff --git a/superset/charts/commands/export.py b/superset/charts/commands/export.py index 23bdb55b24..765431f9e3 100644 --- a/superset/charts/commands/export.py +++ b/superset/charts/commands/export.py @@ -21,13 +21,14 @@ import logging from typing import Iterator, Tuple import yaml +from werkzeug.utils import secure_filename from superset.charts.commands.exceptions import ChartNotFoundError from superset.charts.dao import ChartDAO from superset.datasets.commands.export import ExportDatasetsCommand from superset.importexport.commands.base import ExportModelsCommand from superset.models.slice import Slice -from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION, sanitize +from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION logger = logging.getLogger(__name__) @@ -43,7 +44,7 @@ class ExportChartsCommand(ExportModelsCommand): @staticmethod def export(model: Slice) -> Iterator[Tuple[str, str]]: - chart_slug = sanitize(model.slice_name) + chart_slug = secure_filename(model.slice_name) file_name = f"charts/{chart_slug}.yaml" payload = model.export_to_dict( diff --git a/superset/dashboards/commands/export.py b/superset/dashboards/commands/export.py index ba55b64be4..a172969df0 100644 --- a/superset/dashboards/commands/export.py +++ b/superset/dashboards/commands/export.py @@ -21,13 +21,14 @@ import logging from typing import Iterator, Tuple import yaml +from werkzeug.utils import secure_filename from superset.charts.commands.export import ExportChartsCommand from superset.dashboards.commands.exceptions import DashboardNotFoundError from superset.dashboards.dao import DashboardDAO from superset.importexport.commands.base import ExportModelsCommand from superset.models.dashboard import Dashboard -from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION, sanitize +from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION logger = logging.getLogger(__name__) @@ -43,7 +44,7 @@ class ExportDashboardsCommand(ExportModelsCommand): @staticmethod def export(model: Dashboard) -> Iterator[Tuple[str, str]]: - dashboard_slug = sanitize(model.dashboard_title) + dashboard_slug = secure_filename(model.dashboard_title) file_name = f"dashboards/{dashboard_slug}.yaml" payload = model.export_to_dict( diff --git a/superset/databases/commands/export.py b/superset/databases/commands/export.py index a7715f5c0e..8cd32120c2 100644 --- a/superset/databases/commands/export.py +++ b/superset/databases/commands/export.py @@ -21,12 +21,13 @@ import logging from typing import Iterator, Tuple import yaml +from werkzeug.utils import secure_filename from superset.databases.commands.exceptions import DatabaseNotFoundError from superset.databases.dao import DatabaseDAO from superset.importexport.commands.base import ExportModelsCommand from superset.models.core import Database -from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION, sanitize +from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION logger = logging.getLogger(__name__) @@ -38,7 +39,7 @@ class ExportDatabasesCommand(ExportModelsCommand): @staticmethod def export(model: Database) -> Iterator[Tuple[str, str]]: - database_slug = sanitize(model.database_name) + database_slug = secure_filename(model.database_name) file_name = f"databases/{database_slug}.yaml" payload = model.export_to_dict( @@ -61,7 +62,7 @@ class ExportDatabasesCommand(ExportModelsCommand): yield file_name, file_content for dataset in model.tables: - dataset_slug = sanitize(dataset.table_name) + dataset_slug = secure_filename(dataset.table_name) file_name = f"datasets/{database_slug}/{dataset_slug}.yaml" payload = dataset.export_to_dict( diff --git a/superset/datasets/commands/export.py b/superset/datasets/commands/export.py index a14cdcd67e..b4c9371240 100644 --- a/superset/datasets/commands/export.py +++ b/superset/datasets/commands/export.py @@ -21,12 +21,13 @@ import logging from typing import Iterator, Tuple import yaml +from werkzeug.utils import secure_filename from superset.connectors.sqla.models import SqlaTable from superset.datasets.commands.exceptions import DatasetNotFoundError from superset.datasets.dao import DatasetDAO from superset.importexport.commands.base import ExportModelsCommand -from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION, sanitize +from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION logger = logging.getLogger(__name__) @@ -38,8 +39,8 @@ class ExportDatasetsCommand(ExportModelsCommand): @staticmethod def export(model: SqlaTable) -> Iterator[Tuple[str, str]]: - database_slug = sanitize(model.database.database_name) - dataset_slug = sanitize(model.table_name) + database_slug = secure_filename(model.database.database_name) + dataset_slug = secure_filename(model.table_name) file_name = f"datasets/{database_slug}/{dataset_slug}.yaml" payload = model.export_to_dict( diff --git a/superset/queries/saved_queries/commands/export.py b/superset/queries/saved_queries/commands/export.py index 33dfffc86e..16ce4fc335 100644 --- a/superset/queries/saved_queries/commands/export.py +++ b/superset/queries/saved_queries/commands/export.py @@ -21,12 +21,13 @@ import logging from typing import Iterator, Tuple import yaml +from werkzeug.utils import secure_filename from superset.importexport.commands.base import ExportModelsCommand from superset.models.sql_lab import SavedQuery from superset.queries.saved_queries.commands.exceptions import SavedQueryNotFoundError from superset.queries.saved_queries.dao import SavedQueryDAO -from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION, sanitize +from superset.utils.dict_import_export import IMPORT_EXPORT_VERSION logger = logging.getLogger(__name__) @@ -39,9 +40,9 @@ class ExportSavedQueriesCommand(ExportModelsCommand): @staticmethod def export(model: SavedQuery) -> Iterator[Tuple[str, str]]: # build filename based on database, optional schema, and label - database_slug = sanitize(model.database.database_name) - schema_slug = sanitize(model.schema) - query_slug = sanitize(model.label) or str(model.uuid) + database_slug = secure_filename(model.database.database_name) + schema_slug = secure_filename(model.schema) + query_slug = secure_filename(model.label) or str(model.uuid) file_name = f"queries/{database_slug}/{schema_slug}/{query_slug}.yaml" payload = model.export_to_dict( diff --git a/superset/utils/dict_import_export.py b/superset/utils/dict_import_export.py index 732acbf907..c2606f4f58 100644 --- a/superset/utils/dict_import_export.py +++ b/superset/utils/dict_import_export.py @@ -15,8 +15,6 @@ # specific language governing permissions and limitations # under the License. import logging -import re -import unicodedata from typing import Any, Dict, List, Optional from sqlalchemy.orm import Session @@ -98,18 +96,3 @@ def import_from_dict( session.commit() else: logger.info("Supplied object is not a dictionary.") - - -def strip_accents(text: str) -> str: - text = unicodedata.normalize("NFD", text).encode("ascii", "ignore").decode("utf-8") - - return str(text) - - -def sanitize(name: str) -> str: - """Sanitize a post title into a directory name.""" - name = name.lower().replace(" ", "_") - name = re.sub(r"[^\w]", "", name) - name = strip_accents(name) - - return name diff --git a/tests/charts/commands_tests.py b/tests/charts/commands_tests.py index 13dec7bd30..9189d4cc44 100644 --- a/tests/charts/commands_tests.py +++ b/tests/charts/commands_tests.py @@ -37,13 +37,13 @@ class TestExportChartsCommand(SupersetTestCase): expected = [ "metadata.yaml", - "charts/energy_sankey.yaml", + "charts/Energy_Sankey.yaml", "datasets/examples/energy_usage.yaml", "databases/examples.yaml", ] assert expected == list(contents.keys()) - metadata = yaml.safe_load(contents["charts/energy_sankey.yaml"]) + metadata = yaml.safe_load(contents["charts/Energy_Sankey.yaml"]) assert metadata == { "slice_name": "Energy Sankey", "viz_type": "sankey", @@ -90,7 +90,7 @@ class TestExportChartsCommand(SupersetTestCase): command = ExportChartsCommand([example_chart.id]) contents = dict(command.run()) - metadata = yaml.safe_load(contents["charts/energy_sankey.yaml"]) + metadata = yaml.safe_load(contents["charts/Energy_Sankey.yaml"]) assert list(metadata.keys()) == [ "slice_name", "viz_type", diff --git a/tests/dashboards/commands_tests.py b/tests/dashboards/commands_tests.py index 075d2b825c..ce23294399 100644 --- a/tests/dashboards/commands_tests.py +++ b/tests/dashboards/commands_tests.py @@ -39,24 +39,23 @@ class TestExportDashboardsCommand(SupersetTestCase): expected_paths = { "metadata.yaml", - "dashboards/world_banks_data.yaml", - "charts/box_plot.yaml", + "dashboards/World_Banks_Data.yaml", + "charts/Region_Filter.yaml", "datasets/examples/wb_health_population.yaml", "databases/examples.yaml", - "charts/treemap.yaml", - "charts/region_filter.yaml", - "charts/_rural.yaml", - "charts/worlds_population.yaml", - "charts/most_populated_countries.yaml", - "charts/growth_rate.yaml", - "charts/life_expectancy_vs_rural_.yaml", - "charts/rural_breakdown.yaml", - "charts/worlds_pop_growth.yaml", + "charts/Worlds_Population.yaml", + "charts/Most_Populated_Countries.yaml", + "charts/Growth_Rate.yaml", + "charts/Rural.yaml", + "charts/Life_Expectancy_VS_Rural.yaml", + "charts/Rural_Breakdown.yaml", + "charts/Worlds_Pop_Growth.yaml", + "charts/Box_plot.yaml", + "charts/Treemap.yaml", } - assert expected_paths == set(contents.keys()) - metadata = yaml.safe_load(contents["dashboards/world_banks_data.yaml"]) + metadata = yaml.safe_load(contents["dashboards/World_Banks_Data.yaml"]) # remove chart UUIDs from metadata so we can compare for chart_info in metadata["position"].values(): @@ -178,7 +177,7 @@ class TestExportDashboardsCommand(SupersetTestCase): command = ExportDashboardsCommand([example_dashboard.id]) contents = dict(command.run()) - metadata = yaml.safe_load(contents["dashboards/world_banks_data.yaml"]) + metadata = yaml.safe_load(contents["dashboards/World_Banks_Data.yaml"]) assert list(metadata.keys()) == [ "dashboard_title", "description", diff --git a/tests/queries/saved_queries/commands_tests.py b/tests/queries/saved_queries/commands_tests.py index 34f4dbe8e4..1525576a27 100644 --- a/tests/queries/saved_queries/commands_tests.py +++ b/tests/queries/saved_queries/commands_tests.py @@ -54,12 +54,12 @@ class TestExportSavedQueriesCommand(SupersetTestCase): expected = [ "metadata.yaml", - "queries/examples/schema1/the_answer.yaml", + "queries/examples/schema1/The_answer.yaml", "databases/examples.yaml", ] assert expected == list(contents.keys()) - metadata = yaml.safe_load(contents["queries/examples/schema1/the_answer.yaml"]) + metadata = yaml.safe_load(contents["queries/examples/schema1/The_answer.yaml"]) assert metadata == { "schema": "schema1", "label": "The answer", @@ -98,7 +98,7 @@ class TestExportSavedQueriesCommand(SupersetTestCase): command = ExportSavedQueriesCommand([self.example_query.id]) contents = dict(command.run()) - metadata = yaml.safe_load(contents["queries/examples/schema1/the_answer.yaml"]) + metadata = yaml.safe_load(contents["queries/examples/schema1/The_answer.yaml"]) assert list(metadata.keys()) == [ "schema", "label",