From 49605e763c7e931fd1cae6380f61a55b9fe29430 Mon Sep 17 00:00:00 2001 From: John Bodley <4567245+john-bodley@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:45:29 -0700 Subject: [PATCH] chore(command): Condense delete/bulk-delete operations (#24607) Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com> (cherry picked from commit a156816064c47b9ad12f34571f44e02cfb72fe75) --- superset/annotation_layers/annotations/api.py | 10 +-- .../annotations/commands/bulk_delete.py | 51 -------------- .../annotations/commands/delete.py | 14 ++-- .../annotations/commands/exceptions.py | 8 +-- superset/annotation_layers/api.py | 13 ++-- .../annotation_layers/commands/bulk_delete.py | 54 -------------- superset/annotation_layers/commands/delete.py | 16 ++--- .../annotation_layers/commands/exceptions.py | 12 +--- superset/charts/api.py | 8 +-- superset/charts/commands/bulk_delete.py | 70 ------------------- superset/charts/commands/delete.py | 32 +++++---- superset/charts/commands/exceptions.py | 10 +-- superset/css_templates/api.py | 8 +-- .../commands/{bulk_delete.py => delete.py} | 6 +- superset/css_templates/commands/exceptions.py | 4 +- superset/dashboards/api.py | 8 +-- superset/dashboards/commands/bulk_delete.py | 70 ------------------- superset/dashboards/commands/delete.py | 25 +++---- superset/dashboards/commands/exceptions.py | 10 +-- superset/datasets/api.py | 8 +-- superset/datasets/commands/bulk_delete.py | 60 ---------------- superset/datasets/commands/delete.py | 25 +++---- superset/datasets/commands/exceptions.py | 6 +- superset/queries/saved_queries/api.py | 10 ++- .../commands/{bulk_delete.py => delete.py} | 6 +- .../saved_queries/commands/exceptions.py | 2 +- superset/reports/api.py | 8 +-- superset/reports/commands/bulk_delete.py | 61 ---------------- superset/reports/commands/delete.py | 23 +++--- superset/reports/commands/exceptions.py | 5 -- superset/row_level_security/api.py | 4 +- .../commands/{bulk_delete.py => delete.py} | 6 +- .../row_level_security/commands/exceptions.py | 4 +- tests/integration_tests/datasets/api_tests.py | 2 +- .../security/row_level_security_tests.py | 2 +- 35 files changed, 123 insertions(+), 538 deletions(-) delete mode 100644 superset/annotation_layers/annotations/commands/bulk_delete.py delete mode 100644 superset/annotation_layers/commands/bulk_delete.py delete mode 100644 superset/charts/commands/bulk_delete.py rename superset/css_templates/commands/{bulk_delete.py => delete.py} (92%) delete mode 100644 superset/dashboards/commands/bulk_delete.py delete mode 100644 superset/datasets/commands/bulk_delete.py rename superset/queries/saved_queries/commands/{bulk_delete.py => delete.py} (92%) delete mode 100644 superset/reports/commands/bulk_delete.py rename superset/row_level_security/commands/{bulk_delete.py => delete.py} (92%) diff --git a/superset/annotation_layers/annotations/api.py b/superset/annotation_layers/annotations/api.py index 70e0a1ad02d3a..484335b81cc3e 100644 --- a/superset/annotation_layers/annotations/api.py +++ b/superset/annotation_layers/annotations/api.py @@ -24,9 +24,6 @@ from flask_babel import ngettext from marshmallow import ValidationError -from superset.annotation_layers.annotations.commands.bulk_delete import ( - BulkDeleteAnnotationCommand, -) from superset.annotation_layers.annotations.commands.create import ( CreateAnnotationCommand, ) @@ -34,7 +31,6 @@ DeleteAnnotationCommand, ) from superset.annotation_layers.annotations.commands.exceptions import ( - AnnotationBulkDeleteFailedError, AnnotationCreateFailedError, AnnotationDeleteFailedError, AnnotationInvalidError, @@ -438,7 +434,7 @@ def delete( # pylint: disable=arguments-differ $ref: '#/components/responses/500' """ try: - DeleteAnnotationCommand(annotation_id).run() + DeleteAnnotationCommand([annotation_id]).run() return self.response(200, message="OK") except AnnotationNotFoundError: return self.response_404() @@ -495,7 +491,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteAnnotationCommand(item_ids).run() + DeleteAnnotationCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -506,5 +502,5 @@ def bulk_delete(self, **kwargs: Any) -> Response: ) except AnnotationNotFoundError: return self.response_404() - except AnnotationBulkDeleteFailedError as ex: + except AnnotationDeleteFailedError as ex: return self.response_422(message=str(ex)) diff --git a/superset/annotation_layers/annotations/commands/bulk_delete.py b/superset/annotation_layers/annotations/commands/bulk_delete.py deleted file mode 100644 index 153f93aa6fc1e..0000000000000 --- a/superset/annotation_layers/annotations/commands/bulk_delete.py +++ /dev/null @@ -1,51 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from typing import Optional - -from superset.annotation_layers.annotations.commands.exceptions import ( - AnnotationBulkDeleteFailedError, - AnnotationNotFoundError, -) -from superset.commands.base import BaseCommand -from superset.daos.annotation import AnnotationDAO -from superset.daos.exceptions import DAODeleteFailedError -from superset.models.annotations import Annotation - -logger = logging.getLogger(__name__) - - -class BulkDeleteAnnotationCommand(BaseCommand): - def __init__(self, model_ids: list[int]): - self._model_ids = model_ids - self._models: Optional[list[Annotation]] = None - - def run(self) -> None: - self.validate() - assert self._models - - try: - AnnotationDAO.delete(self._models) - except DAODeleteFailedError as ex: - logger.exception(ex.exception) - raise AnnotationBulkDeleteFailedError() from ex - - def validate(self) -> None: - # Validate/populate model exists - self._models = AnnotationDAO.find_by_ids(self._model_ids) - if not self._models or len(self._models) != len(self._model_ids): - raise AnnotationNotFoundError() diff --git a/superset/annotation_layers/annotations/commands/delete.py b/superset/annotation_layers/annotations/commands/delete.py index bbd7f39dd6958..2850f8cb96302 100644 --- a/superset/annotation_layers/annotations/commands/delete.py +++ b/superset/annotation_layers/annotations/commands/delete.py @@ -30,22 +30,22 @@ class DeleteAnnotationCommand(BaseCommand): - def __init__(self, model_id: int): - self._model_id = model_id - self._model: Optional[Annotation] = None + def __init__(self, model_ids: list[int]): + self._model_ids = model_ids + self._models: Optional[list[Annotation]] = None def run(self) -> None: self.validate() - assert self._model + assert self._models try: - AnnotationDAO.delete(self._model) + AnnotationDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) raise AnnotationDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists - self._model = AnnotationDAO.find_by_id(self._model_id) - if not self._model: + self._models = AnnotationDAO.find_by_ids(self._model_ids) + if not self._models or len(self._models) != len(self._model_ids): raise AnnotationNotFoundError() diff --git a/superset/annotation_layers/annotations/commands/exceptions.py b/superset/annotation_layers/annotations/commands/exceptions.py index e7fc93ecffe2f..dcdf42cc85c6a 100644 --- a/superset/annotation_layers/annotations/commands/exceptions.py +++ b/superset/annotation_layers/annotations/commands/exceptions.py @@ -48,10 +48,6 @@ def __init__(self) -> None: ) -class AnnotationBulkDeleteFailedError(DeleteFailedError): - message = _("Annotations could not be deleted.") - - class AnnotationNotFoundError(CommandException): message = _("Annotation not found.") @@ -68,5 +64,5 @@ class AnnotationUpdateFailedError(CreateFailedError): message = _("Annotation could not be updated.") -class AnnotationDeleteFailedError(CommandException): - message = _("Annotation delete failed.") +class AnnotationDeleteFailedError(DeleteFailedError): + message = _("Annotations could not be deleted.") diff --git a/superset/annotation_layers/api.py b/superset/annotation_layers/api.py index 25e995b611a69..2e64fec78c95a 100644 --- a/superset/annotation_layers/api.py +++ b/superset/annotation_layers/api.py @@ -23,14 +23,9 @@ from flask_babel import ngettext from marshmallow import ValidationError -from superset.annotation_layers.commands.bulk_delete import ( - BulkDeleteAnnotationLayerCommand, -) from superset.annotation_layers.commands.create import CreateAnnotationLayerCommand from superset.annotation_layers.commands.delete import DeleteAnnotationLayerCommand from superset.annotation_layers.commands.exceptions import ( - AnnotationLayerBulkDeleteFailedError, - AnnotationLayerBulkDeleteIntegrityError, AnnotationLayerCreateFailedError, AnnotationLayerDeleteFailedError, AnnotationLayerDeleteIntegrityError, @@ -151,7 +146,7 @@ def delete(self, pk: int) -> Response: $ref: '#/components/responses/500' """ try: - DeleteAnnotationLayerCommand(pk).run() + DeleteAnnotationLayerCommand([pk]).run() return self.response(200, message="OK") except AnnotationLayerNotFoundError: return self.response_404() @@ -346,7 +341,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteAnnotationLayerCommand(item_ids).run() + DeleteAnnotationLayerCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -357,7 +352,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: ) except AnnotationLayerNotFoundError: return self.response_404() - except AnnotationLayerBulkDeleteIntegrityError as ex: + except AnnotationLayerDeleteIntegrityError as ex: return self.response_422(message=str(ex)) - except AnnotationLayerBulkDeleteFailedError as ex: + except AnnotationLayerDeleteFailedError as ex: return self.response_422(message=str(ex)) diff --git a/superset/annotation_layers/commands/bulk_delete.py b/superset/annotation_layers/commands/bulk_delete.py deleted file mode 100644 index a227fc3266f5a..0000000000000 --- a/superset/annotation_layers/commands/bulk_delete.py +++ /dev/null @@ -1,54 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from typing import Optional - -from superset.annotation_layers.commands.exceptions import ( - AnnotationLayerBulkDeleteFailedError, - AnnotationLayerBulkDeleteIntegrityError, - AnnotationLayerNotFoundError, -) -from superset.commands.base import BaseCommand -from superset.daos.annotation import AnnotationLayerDAO -from superset.daos.exceptions import DAODeleteFailedError -from superset.models.annotations import AnnotationLayer - -logger = logging.getLogger(__name__) - - -class BulkDeleteAnnotationLayerCommand(BaseCommand): - def __init__(self, model_ids: list[int]): - self._model_ids = model_ids - self._models: Optional[list[AnnotationLayer]] = None - - def run(self) -> None: - self.validate() - assert self._models - - try: - AnnotationLayerDAO.delete(self._models) - except DAODeleteFailedError as ex: - logger.exception(ex.exception) - raise AnnotationLayerBulkDeleteFailedError() from ex - - def validate(self) -> None: - # Validate/populate model exists - self._models = AnnotationLayerDAO.find_by_ids(self._model_ids) - if not self._models or len(self._models) != len(self._model_ids): - raise AnnotationLayerNotFoundError() - if AnnotationLayerDAO.has_annotations(self._model_ids): - raise AnnotationLayerBulkDeleteIntegrityError() diff --git a/superset/annotation_layers/commands/delete.py b/superset/annotation_layers/commands/delete.py index 14f53272f13f3..41c727054bd7f 100644 --- a/superset/annotation_layers/commands/delete.py +++ b/superset/annotation_layers/commands/delete.py @@ -31,24 +31,24 @@ class DeleteAnnotationLayerCommand(BaseCommand): - def __init__(self, model_id: int): - self._model_id = model_id - self._model: Optional[AnnotationLayer] = None + def __init__(self, model_ids: list[int]): + self._model_ids = model_ids + self._models: Optional[list[AnnotationLayer]] = None def run(self) -> None: self.validate() - assert self._model + assert self._models try: - AnnotationLayerDAO.delete(self._model) + AnnotationLayerDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) raise AnnotationLayerDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists - self._model = AnnotationLayerDAO.find_by_id(self._model_id) - if not self._model: + self._models = AnnotationLayerDAO.find_by_ids(self._model_ids) + if not self._models or len(self._models) != len(self._model_ids): raise AnnotationLayerNotFoundError() - if AnnotationLayerDAO.has_annotations(self._model.id): + if AnnotationLayerDAO.has_annotations(self._model_ids): raise AnnotationLayerDeleteIntegrityError() diff --git a/superset/annotation_layers/commands/exceptions.py b/superset/annotation_layers/commands/exceptions.py index 584962321bb26..a9e6e97ecf361 100644 --- a/superset/annotation_layers/commands/exceptions.py +++ b/superset/annotation_layers/commands/exceptions.py @@ -29,10 +29,6 @@ class AnnotationLayerInvalidError(CommandInvalidError): message = _("Annotation layer parameters are invalid.") -class AnnotationLayerBulkDeleteFailedError(DeleteFailedError): - message = _("Annotation layer could not be deleted.") - - class AnnotationLayerCreateFailedError(CreateFailedError): message = _("Annotation layer could not be created.") @@ -45,18 +41,14 @@ class AnnotationLayerNotFoundError(CommandException): message = _("Annotation layer not found.") -class AnnotationLayerDeleteFailedError(CommandException): - message = _("Annotation layer delete failed.") +class AnnotationLayerDeleteFailedError(DeleteFailedError): + message = _("Annotation layers could not be deleted.") class AnnotationLayerDeleteIntegrityError(CommandException): message = _("Annotation layer has associated annotations.") -class AnnotationLayerBulkDeleteIntegrityError(CommandException): - message = _("Annotation layer has associated annotations.") - - class AnnotationLayerNameUniquenessValidationError(ValidationError): """ Marshmallow validation error for annotation layer name already exists diff --git a/superset/charts/api.py b/superset/charts/api.py index 8cfe9fa4c2c43..a060b23b0efbc 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -32,11 +32,9 @@ from werkzeug.wsgi import FileWrapper from superset import app, is_feature_enabled, thumbnail_cache -from superset.charts.commands.bulk_delete import BulkDeleteChartCommand from superset.charts.commands.create import CreateChartCommand from superset.charts.commands.delete import DeleteChartCommand from superset.charts.commands.exceptions import ( - ChartBulkDeleteFailedError, ChartCreateFailedError, ChartDeleteFailedError, ChartForbiddenError, @@ -461,7 +459,7 @@ def delete(self, pk: int) -> Response: $ref: '#/components/responses/500' """ try: - DeleteChartCommand(pk).run() + DeleteChartCommand([pk]).run() return self.response(200, message="OK") except ChartNotFoundError: return self.response_404() @@ -521,7 +519,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteChartCommand(item_ids).run() + DeleteChartCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -532,7 +530,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: return self.response_404() except ChartForbiddenError: return self.response_403() - except ChartBulkDeleteFailedError as ex: + except ChartDeleteFailedError as ex: return self.response_422(message=str(ex)) @expose("//cache_screenshot/", methods=("GET",)) diff --git a/superset/charts/commands/bulk_delete.py b/superset/charts/commands/bulk_delete.py deleted file mode 100644 index 0555992e24ce1..0000000000000 --- a/superset/charts/commands/bulk_delete.py +++ /dev/null @@ -1,70 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from typing import Optional - -from flask_babel import lazy_gettext as _ - -from superset import security_manager -from superset.charts.commands.exceptions import ( - ChartBulkDeleteFailedError, - ChartBulkDeleteFailedReportsExistError, - ChartForbiddenError, - ChartNotFoundError, -) -from superset.commands.base import BaseCommand -from superset.commands.exceptions import DeleteFailedError -from superset.daos.chart import ChartDAO -from superset.daos.report import ReportScheduleDAO -from superset.exceptions import SupersetSecurityException -from superset.models.slice import Slice - -logger = logging.getLogger(__name__) - - -class BulkDeleteChartCommand(BaseCommand): - def __init__(self, model_ids: list[int]): - self._model_ids = model_ids - self._models: Optional[list[Slice]] = None - - def run(self) -> None: - self.validate() - assert self._models - - try: - ChartDAO.delete(self._models) - except DeleteFailedError as ex: - logger.exception(ex.exception) - raise ChartBulkDeleteFailedError() from ex - - def validate(self) -> None: - # Validate/populate model exists - self._models = ChartDAO.find_by_ids(self._model_ids) - if not self._models or len(self._models) != len(self._model_ids): - raise ChartNotFoundError() - # Check there are no associated ReportSchedules - if reports := ReportScheduleDAO.find_by_chart_ids(self._model_ids): - report_names = [report.name for report in reports] - raise ChartBulkDeleteFailedReportsExistError( - _("There are associated alerts or reports: %s" % ",".join(report_names)) - ) - # Check ownership - for model in self._models: - try: - security_manager.raise_for_ownership(model) - except SupersetSecurityException as ex: - raise ChartForbiddenError() from ex diff --git a/superset/charts/commands/delete.py b/superset/charts/commands/delete.py index 72d9f9a732dd1..1b3d93f063b0c 100644 --- a/superset/charts/commands/delete.py +++ b/superset/charts/commands/delete.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. import logging -from typing import cast, Optional +from typing import Optional from flask_babel import lazy_gettext as _ @@ -38,33 +38,37 @@ class DeleteChartCommand(BaseCommand): - def __init__(self, model_id: int): - self._model_id = model_id - self._model: Optional[Slice] = None + def __init__(self, model_ids: list[int]): + self._model_ids = model_ids + self._models: Optional[list[Slice]] = None def run(self) -> None: self.validate() - self._model = cast(Slice, self._model) + assert self._models + + for model_id in self._model_ids: + Dashboard.clear_cache_for_slice(slice_id=model_id) + try: - Dashboard.clear_cache_for_slice(slice_id=self._model_id) - ChartDAO.delete(self._model) + ChartDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) raise ChartDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists - self._model = ChartDAO.find_by_id(self._model_id) - if not self._model: + self._models = ChartDAO.find_by_ids(self._model_ids) + if not self._models or len(self._models) != len(self._model_ids): raise ChartNotFoundError() # Check there are no associated ReportSchedules - if reports := ReportScheduleDAO.find_by_chart_id(self._model_id): + if reports := ReportScheduleDAO.find_by_chart_ids(self._model_ids): report_names = [report.name for report in reports] raise ChartDeleteFailedReportsExistError( _("There are associated alerts or reports: %s" % ",".join(report_names)) ) # Check ownership - try: - security_manager.raise_for_ownership(self._model) - except SupersetSecurityException as ex: - raise ChartForbiddenError() from ex + for model in self._models: + try: + security_manager.raise_for_ownership(model) + except SupersetSecurityException as ex: + raise ChartForbiddenError() from ex diff --git a/superset/charts/commands/exceptions.py b/superset/charts/commands/exceptions.py index 83792ae2527fe..00877aa803070 100644 --- a/superset/charts/commands/exceptions.py +++ b/superset/charts/commands/exceptions.py @@ -120,7 +120,7 @@ class ChartUpdateFailedError(UpdateFailedError): class ChartDeleteFailedError(DeleteFailedError): - message = _("Chart could not be deleted.") + message = _("Charts could not be deleted.") class ChartDeleteFailedReportsExistError(ChartDeleteFailedError): @@ -135,10 +135,6 @@ class ChartForbiddenError(ForbiddenError): message = _("Changing this chart is forbidden") -class ChartBulkDeleteFailedError(DeleteFailedError): - message = _("Charts could not be deleted.") - - class ChartDataQueryFailedError(CommandException): pass @@ -147,10 +143,6 @@ class ChartDataCacheLoadError(CommandException): pass -class ChartBulkDeleteFailedReportsExistError(ChartBulkDeleteFailedError): - message = _("There are associated alerts or reports") - - class ChartImportError(ImportFailedError): message = _("Import chart failed for an unknown reason") diff --git a/superset/css_templates/api.py b/superset/css_templates/api.py index 3f0980f2ffb47..f7688afe03382 100644 --- a/superset/css_templates/api.py +++ b/superset/css_templates/api.py @@ -23,9 +23,9 @@ from flask_babel import ngettext from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod -from superset.css_templates.commands.bulk_delete import BulkDeleteCssTemplateCommand +from superset.css_templates.commands.delete import DeleteCssTemplateCommand from superset.css_templates.commands.exceptions import ( - CssTemplateBulkDeleteFailedError, + CssTemplateDeleteFailedError, CssTemplateNotFoundError, ) from superset.css_templates.filters import CssTemplateAllTextFilter @@ -130,7 +130,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteCssTemplateCommand(item_ids).run() + DeleteCssTemplateCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -141,5 +141,5 @@ def bulk_delete(self, **kwargs: Any) -> Response: ) except CssTemplateNotFoundError: return self.response_404() - except CssTemplateBulkDeleteFailedError as ex: + except CssTemplateDeleteFailedError as ex: return self.response_422(message=str(ex)) diff --git a/superset/css_templates/commands/bulk_delete.py b/superset/css_templates/commands/delete.py similarity index 92% rename from superset/css_templates/commands/bulk_delete.py rename to superset/css_templates/commands/delete.py index ef13a1c8e226f..123658cb45acc 100644 --- a/superset/css_templates/commands/bulk_delete.py +++ b/superset/css_templates/commands/delete.py @@ -19,7 +19,7 @@ from superset.commands.base import BaseCommand from superset.css_templates.commands.exceptions import ( - CssTemplateBulkDeleteFailedError, + CssTemplateDeleteFailedError, CssTemplateNotFoundError, ) from superset.daos.css import CssTemplateDAO @@ -29,7 +29,7 @@ logger = logging.getLogger(__name__) -class BulkDeleteCssTemplateCommand(BaseCommand): +class DeleteCssTemplateCommand(BaseCommand): def __init__(self, model_ids: list[int]): self._model_ids = model_ids self._models: Optional[list[CssTemplate]] = None @@ -42,7 +42,7 @@ def run(self) -> None: CssTemplateDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) - raise CssTemplateBulkDeleteFailedError() from ex + raise CssTemplateDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists diff --git a/superset/css_templates/commands/exceptions.py b/superset/css_templates/commands/exceptions.py index d950822cfa1d5..95517581067f5 100644 --- a/superset/css_templates/commands/exceptions.py +++ b/superset/css_templates/commands/exceptions.py @@ -19,8 +19,8 @@ from superset.commands.exceptions import CommandException, DeleteFailedError -class CssTemplateBulkDeleteFailedError(DeleteFailedError): - message = _("CSS template could not be deleted.") +class CssTemplateDeleteFailedError(DeleteFailedError): + message = _("CSS templates could not be deleted.") class CssTemplateNotFoundError(CommandException): diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index 781ad6f6c77f7..8464c87444b3b 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -39,12 +39,10 @@ from superset.commands.importers.v1.utils import get_contents_from_bundle from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.daos.dashboard import DashboardDAO, EmbeddedDashboardDAO -from superset.dashboards.commands.bulk_delete import BulkDeleteDashboardCommand from superset.dashboards.commands.create import CreateDashboardCommand from superset.dashboards.commands.delete import DeleteDashboardCommand from superset.dashboards.commands.exceptions import ( DashboardAccessDeniedError, - DashboardBulkDeleteFailedError, DashboardCreateFailedError, DashboardDeleteFailedError, DashboardForbiddenError, @@ -674,7 +672,7 @@ def delete(self, pk: int) -> Response: $ref: '#/components/responses/500' """ try: - DeleteDashboardCommand(pk).run() + DeleteDashboardCommand([pk]).run() return self.response(200, message="OK") except DashboardNotFoundError: return self.response_404() @@ -734,7 +732,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteDashboardCommand(item_ids).run() + DeleteDashboardCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -747,7 +745,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: return self.response_404() except DashboardForbiddenError: return self.response_403() - except DashboardBulkDeleteFailedError as ex: + except DashboardDeleteFailedError as ex: return self.response_422(message=str(ex)) @expose("/export/", methods=("GET",)) diff --git a/superset/dashboards/commands/bulk_delete.py b/superset/dashboards/commands/bulk_delete.py deleted file mode 100644 index 707f0d722ab11..0000000000000 --- a/superset/dashboards/commands/bulk_delete.py +++ /dev/null @@ -1,70 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from typing import Optional - -from flask_babel import lazy_gettext as _ - -from superset import security_manager -from superset.commands.base import BaseCommand -from superset.commands.exceptions import DeleteFailedError -from superset.daos.dashboard import DashboardDAO -from superset.daos.report import ReportScheduleDAO -from superset.dashboards.commands.exceptions import ( - DashboardBulkDeleteFailedError, - DashboardBulkDeleteFailedReportsExistError, - DashboardForbiddenError, - DashboardNotFoundError, -) -from superset.exceptions import SupersetSecurityException -from superset.models.dashboard import Dashboard - -logger = logging.getLogger(__name__) - - -class BulkDeleteDashboardCommand(BaseCommand): - def __init__(self, model_ids: list[int]): - self._model_ids = model_ids - self._models: Optional[list[Dashboard]] = None - - def run(self) -> None: - self.validate() - assert self._models - - try: - DashboardDAO.delete(self._models) - except DeleteFailedError as ex: - logger.exception(ex.exception) - raise DashboardBulkDeleteFailedError() from ex - - def validate(self) -> None: - # Validate/populate model exists - self._models = DashboardDAO.find_by_ids(self._model_ids) - if not self._models or len(self._models) != len(self._model_ids): - raise DashboardNotFoundError() - # Check there are no associated ReportSchedules - if reports := ReportScheduleDAO.find_by_dashboard_ids(self._model_ids): - report_names = [report.name for report in reports] - raise DashboardBulkDeleteFailedReportsExistError( - _("There are associated alerts or reports: %s" % ",".join(report_names)) - ) - # Check ownership - for model in self._models: - try: - security_manager.raise_for_ownership(model) - except SupersetSecurityException as ex: - raise DashboardForbiddenError() from ex diff --git a/superset/dashboards/commands/delete.py b/superset/dashboards/commands/delete.py index 13b92977dfc30..23e38093abe11 100644 --- a/superset/dashboards/commands/delete.py +++ b/superset/dashboards/commands/delete.py @@ -37,33 +37,34 @@ class DeleteDashboardCommand(BaseCommand): - def __init__(self, model_id: int): - self._model_id = model_id - self._model: Optional[Dashboard] = None + def __init__(self, model_ids: list[int]): + self._model_ids = model_ids + self._models: Optional[list[Dashboard]] = None def run(self) -> None: self.validate() - assert self._model + assert self._models try: - DashboardDAO.delete(self._model) + DashboardDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) raise DashboardDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists - self._model = DashboardDAO.find_by_id(self._model_id) - if not self._model: + self._models = DashboardDAO.find_by_ids(self._model_ids) + if not self._models or len(self._models) != len(self._model_ids): raise DashboardNotFoundError() # Check there are no associated ReportSchedules - if reports := ReportScheduleDAO.find_by_dashboard_id(self._model_id): + if reports := ReportScheduleDAO.find_by_dashboard_ids(self._model_ids): report_names = [report.name for report in reports] raise DashboardDeleteFailedReportsExistError( _("There are associated alerts or reports: %s" % ",".join(report_names)) ) # Check ownership - try: - security_manager.raise_for_ownership(self._model) - except SupersetSecurityException as ex: - raise DashboardForbiddenError() from ex + for model in self._models: + try: + security_manager.raise_for_ownership(model) + except SupersetSecurityException as ex: + raise DashboardForbiddenError() from ex diff --git a/superset/dashboards/commands/exceptions.py b/superset/dashboards/commands/exceptions.py index 1a5bdaf789f67..19184b894c294 100644 --- a/superset/dashboards/commands/exceptions.py +++ b/superset/dashboards/commands/exceptions.py @@ -51,15 +51,7 @@ def __init__( class DashboardCreateFailedError(CreateFailedError): - message = _("Dashboard could not be created.") - - -class DashboardBulkDeleteFailedError(CreateFailedError): - message = _("Dashboards could not be deleted.") - - -class DashboardBulkDeleteFailedReportsExistError(DashboardBulkDeleteFailedError): - message = _("There are associated alerts or reports") + message = _("Dashboards could not be created.") class DashboardUpdateFailedError(UpdateFailedError): diff --git a/superset/datasets/api.py b/superset/datasets/api.py index 87e1d9e74c842..d5a0478c5d2fa 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -37,12 +37,10 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.daos.dataset import DatasetDAO from superset.databases.filters import DatabaseFilter -from superset.datasets.commands.bulk_delete import BulkDeleteDatasetCommand from superset.datasets.commands.create import CreateDatasetCommand from superset.datasets.commands.delete import DeleteDatasetCommand from superset.datasets.commands.duplicate import DuplicateDatasetCommand from superset.datasets.commands.exceptions import ( - DatasetBulkDeleteFailedError, DatasetCreateFailedError, DatasetDeleteFailedError, DatasetForbiddenError, @@ -453,7 +451,7 @@ def delete(self, pk: int) -> Response: $ref: '#/components/responses/500' """ try: - DeleteDatasetCommand(pk).run() + DeleteDatasetCommand([pk]).run() return self.response(200, message="OK") except DatasetNotFoundError: return self.response_404() @@ -788,7 +786,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteDatasetCommand(item_ids).run() + DeleteDatasetCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -801,7 +799,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: return self.response_404() except DatasetForbiddenError: return self.response_403() - except DatasetBulkDeleteFailedError as ex: + except DatasetDeleteFailedError as ex: return self.response_422(message=str(ex)) @expose("/import/", methods=("POST",)) diff --git a/superset/datasets/commands/bulk_delete.py b/superset/datasets/commands/bulk_delete.py deleted file mode 100644 index 48515bbc6aa00..0000000000000 --- a/superset/datasets/commands/bulk_delete.py +++ /dev/null @@ -1,60 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from typing import Optional - -from superset import security_manager -from superset.commands.base import BaseCommand -from superset.commands.exceptions import DeleteFailedError -from superset.connectors.sqla.models import SqlaTable -from superset.daos.dataset import DatasetDAO -from superset.datasets.commands.exceptions import ( - DatasetBulkDeleteFailedError, - DatasetForbiddenError, - DatasetNotFoundError, -) -from superset.exceptions import SupersetSecurityException - -logger = logging.getLogger(__name__) - - -class BulkDeleteDatasetCommand(BaseCommand): - def __init__(self, model_ids: list[int]): - self._model_ids = model_ids - self._models: Optional[list[SqlaTable]] = None - - def run(self) -> None: - self.validate() - assert self._models - - try: - DatasetDAO.delete(self._models) - except DeleteFailedError as ex: - logger.exception(ex.exception) - raise DatasetBulkDeleteFailedError() from ex - - def validate(self) -> None: - # Validate/populate model exists - self._models = DatasetDAO.find_by_ids(self._model_ids) - if not self._models or len(self._models) != len(self._model_ids): - raise DatasetNotFoundError() - # Check ownership - for model in self._models: - try: - security_manager.raise_for_ownership(model) - except SupersetSecurityException as ex: - raise DatasetForbiddenError() from ex diff --git a/superset/datasets/commands/delete.py b/superset/datasets/commands/delete.py index 3e7fc7d5a3178..478267d01dd4d 100644 --- a/superset/datasets/commands/delete.py +++ b/superset/datasets/commands/delete.py @@ -33,27 +33,28 @@ class DeleteDatasetCommand(BaseCommand): - def __init__(self, model_id: int): - self._model_id = model_id - self._model: Optional[SqlaTable] = None + def __init__(self, model_ids: list[int]): + self._model_ids = model_ids + self._models: Optional[list[SqlaTable]] = None def run(self) -> None: self.validate() - assert self._model + assert self._models try: - return DatasetDAO.delete(self._model) + DatasetDAO.delete(self._models) except DAODeleteFailedError as ex: - logger.exception(ex) + logger.exception(ex.exception) raise DatasetDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists - self._model = DatasetDAO.find_by_id(self._model_id) - if not self._model: + self._models = DatasetDAO.find_by_ids(self._model_ids) + if not self._models or len(self._models) != len(self._model_ids): raise DatasetNotFoundError() # Check ownership - try: - security_manager.raise_for_ownership(self._model) - except SupersetSecurityException as ex: - raise DatasetForbiddenError() from ex + for model in self._models: + try: + security_manager.raise_for_ownership(model) + except SupersetSecurityException as ex: + raise DatasetForbiddenError() from ex diff --git a/superset/datasets/commands/exceptions.py b/superset/datasets/commands/exceptions.py index 7c6ef86634f3a..fe9fe94cc61a3 100644 --- a/superset/datasets/commands/exceptions.py +++ b/superset/datasets/commands/exceptions.py @@ -179,11 +179,7 @@ class DatasetUpdateFailedError(UpdateFailedError): class DatasetDeleteFailedError(DeleteFailedError): - message = _("Dataset could not be deleted.") - - -class DatasetBulkDeleteFailedError(DeleteFailedError): - message = _("Dataset(s) could not be bulk deleted.") + message = _("Datasets could not be deleted.") class DatasetRefreshFailedError(UpdateFailedError): diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py index c6e980c5defe8..327a2ac4cdd21 100644 --- a/superset/queries/saved_queries/api.py +++ b/superset/queries/saved_queries/api.py @@ -36,11 +36,9 @@ from superset.databases.filters import DatabaseFilter from superset.extensions import event_logger from superset.models.sql_lab import SavedQuery -from superset.queries.saved_queries.commands.bulk_delete import ( - BulkDeleteSavedQueryCommand, -) +from superset.queries.saved_queries.commands.delete import DeleteSavedQueryCommand from superset.queries.saved_queries.commands.exceptions import ( - SavedQueryBulkDeleteFailedError, + SavedQueryDeleteFailedError, SavedQueryNotFoundError, ) from superset.queries.saved_queries.commands.export import ExportSavedQueriesCommand @@ -213,7 +211,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteSavedQueryCommand(item_ids).run() + DeleteSavedQueryCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -224,7 +222,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: ) except SavedQueryNotFoundError: return self.response_404() - except SavedQueryBulkDeleteFailedError as ex: + except SavedQueryDeleteFailedError as ex: return self.response_422(message=str(ex)) @expose("/export/", methods=("GET",)) diff --git a/superset/queries/saved_queries/commands/bulk_delete.py b/superset/queries/saved_queries/commands/delete.py similarity index 92% rename from superset/queries/saved_queries/commands/bulk_delete.py rename to superset/queries/saved_queries/commands/delete.py index 4e68f6b79d08f..40b73658e0afa 100644 --- a/superset/queries/saved_queries/commands/bulk_delete.py +++ b/superset/queries/saved_queries/commands/delete.py @@ -22,14 +22,14 @@ from superset.daos.query import SavedQueryDAO from superset.models.dashboard import Dashboard from superset.queries.saved_queries.commands.exceptions import ( - SavedQueryBulkDeleteFailedError, + SavedQueryDeleteFailedError, SavedQueryNotFoundError, ) logger = logging.getLogger(__name__) -class BulkDeleteSavedQueryCommand(BaseCommand): +class DeleteSavedQueryCommand(BaseCommand): def __init__(self, model_ids: list[int]): self._model_ids = model_ids self._models: Optional[list[Dashboard]] = None @@ -42,7 +42,7 @@ def run(self) -> None: SavedQueryDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) - raise SavedQueryBulkDeleteFailedError() from ex + raise SavedQueryDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists diff --git a/superset/queries/saved_queries/commands/exceptions.py b/superset/queries/saved_queries/commands/exceptions.py index 731857352444d..20797955e3d55 100644 --- a/superset/queries/saved_queries/commands/exceptions.py +++ b/superset/queries/saved_queries/commands/exceptions.py @@ -24,7 +24,7 @@ ) -class SavedQueryBulkDeleteFailedError(DeleteFailedError): +class SavedQueryDeleteFailedError(DeleteFailedError): message = _("Saved queries could not be deleted.") diff --git a/superset/reports/api.py b/superset/reports/api.py index 3686ab74bd1f1..eb6ddcfa7fa48 100644 --- a/superset/reports/api.py +++ b/superset/reports/api.py @@ -30,11 +30,9 @@ from superset.dashboards.filters import DashboardAccessFilter from superset.databases.filters import DatabaseFilter from superset.extensions import event_logger -from superset.reports.commands.bulk_delete import BulkDeleteReportScheduleCommand from superset.reports.commands.create import CreateReportScheduleCommand from superset.reports.commands.delete import DeleteReportScheduleCommand from superset.reports.commands.exceptions import ( - ReportScheduleBulkDeleteFailedError, ReportScheduleCreateFailedError, ReportScheduleDeleteFailedError, ReportScheduleForbiddenError, @@ -278,7 +276,7 @@ def delete(self, pk: int) -> Response: $ref: '#/components/responses/500' """ try: - DeleteReportScheduleCommand(pk).run() + DeleteReportScheduleCommand([pk]).run() return self.response(200, message="OK") except ReportScheduleNotFoundError: return self.response_404() @@ -495,7 +493,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteReportScheduleCommand(item_ids).run() + DeleteReportScheduleCommand(item_ids).run() return self.response( 200, message=ngettext( @@ -508,5 +506,5 @@ def bulk_delete(self, **kwargs: Any) -> Response: return self.response_404() except ReportScheduleForbiddenError: return self.response_403() - except ReportScheduleBulkDeleteFailedError as ex: + except ReportScheduleDeleteFailedError as ex: return self.response_422(message=str(ex)) diff --git a/superset/reports/commands/bulk_delete.py b/superset/reports/commands/bulk_delete.py deleted file mode 100644 index 3e3df3d100c67..0000000000000 --- a/superset/reports/commands/bulk_delete.py +++ /dev/null @@ -1,61 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from typing import Optional - -from superset import security_manager -from superset.commands.base import BaseCommand -from superset.daos.exceptions import DAODeleteFailedError -from superset.daos.report import ReportScheduleDAO -from superset.exceptions import SupersetSecurityException -from superset.reports.commands.exceptions import ( - ReportScheduleBulkDeleteFailedError, - ReportScheduleForbiddenError, - ReportScheduleNotFoundError, -) -from superset.reports.models import ReportSchedule - -logger = logging.getLogger(__name__) - - -class BulkDeleteReportScheduleCommand(BaseCommand): - def __init__(self, model_ids: list[int]): - self._model_ids = model_ids - self._models: Optional[list[ReportSchedule]] = None - - def run(self) -> None: - self.validate() - assert self._models - - try: - ReportScheduleDAO.delete(self._models) - except DAODeleteFailedError as ex: - logger.exception(ex.exception) - raise ReportScheduleBulkDeleteFailedError() from ex - - def validate(self) -> None: - # Validate/populate model exists - self._models = ReportScheduleDAO.find_by_ids(self._model_ids) - if not self._models or len(self._models) != len(self._model_ids): - raise ReportScheduleNotFoundError() - - # Check ownership - for model in self._models: - try: - security_manager.raise_for_ownership(model) - except SupersetSecurityException as ex: - raise ReportScheduleForbiddenError() from ex diff --git a/superset/reports/commands/delete.py b/superset/reports/commands/delete.py index 4f9be93f8a22d..2cdac17c4d007 100644 --- a/superset/reports/commands/delete.py +++ b/superset/reports/commands/delete.py @@ -33,28 +33,29 @@ class DeleteReportScheduleCommand(BaseCommand): - def __init__(self, model_id: int): - self._model_id = model_id - self._model: Optional[ReportSchedule] = None + def __init__(self, model_ids: list[int]): + self._model_ids = model_ids + self._models: Optional[list[ReportSchedule]] = None def run(self) -> None: self.validate() - assert self._model + assert self._models try: - ReportScheduleDAO.delete(self._model) + ReportScheduleDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) raise ReportScheduleDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists - self._model = ReportScheduleDAO.find_by_id(self._model_id) - if not self._model: + self._models = ReportScheduleDAO.find_by_ids(self._model_ids) + if not self._models or len(self._models) != len(self._model_ids): raise ReportScheduleNotFoundError() # Check ownership - try: - security_manager.raise_for_ownership(self._model) - except SupersetSecurityException as ex: - raise ReportScheduleForbiddenError() from ex + for model in self._models: + try: + security_manager.raise_for_ownership(model) + except SupersetSecurityException as ex: + raise ReportScheduleForbiddenError() from ex diff --git a/superset/reports/commands/exceptions.py b/superset/reports/commands/exceptions.py index cba12e0786c21..2d82d5c312044 100644 --- a/superset/reports/commands/exceptions.py +++ b/superset/reports/commands/exceptions.py @@ -21,7 +21,6 @@ CommandException, CommandInvalidError, CreateFailedError, - DeleteFailedError, ForbiddenError, ValidationError, ) @@ -125,10 +124,6 @@ class ReportScheduleInvalidError(CommandInvalidError): message = _("Report Schedule parameters are invalid.") -class ReportScheduleBulkDeleteFailedError(DeleteFailedError): - message = _("Report Schedule could not be deleted.") - - class ReportScheduleCreateFailedError(CreateFailedError): message = _("Report Schedule could not be created.") diff --git a/superset/row_level_security/api.py b/superset/row_level_security/api.py index 43912689fbd02..7bf00e92f74f7 100644 --- a/superset/row_level_security/api.py +++ b/superset/row_level_security/api.py @@ -32,8 +32,8 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.daos.exceptions import DAOCreateFailedError, DAOUpdateFailedError from superset.extensions import event_logger -from superset.row_level_security.commands.bulk_delete import BulkDeleteRLSRuleCommand from superset.row_level_security.commands.create import CreateRLSRuleCommand +from superset.row_level_security.commands.delete import DeleteRLSRuleCommand from superset.row_level_security.commands.exceptions import RLSRuleNotFoundError from superset.row_level_security.commands.update import UpdateRLSRuleCommand from superset.row_level_security.schemas import ( @@ -340,7 +340,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: """ item_ids = kwargs["rison"] try: - BulkDeleteRLSRuleCommand(item_ids).run() + DeleteRLSRuleCommand(item_ids).run() return self.response( 200, message=ngettext( diff --git a/superset/row_level_security/commands/bulk_delete.py b/superset/row_level_security/commands/delete.py similarity index 92% rename from superset/row_level_security/commands/bulk_delete.py rename to superset/row_level_security/commands/delete.py index f0c8dddabcfca..d669f7d90f7d7 100644 --- a/superset/row_level_security/commands/bulk_delete.py +++ b/superset/row_level_security/commands/delete.py @@ -23,13 +23,13 @@ from superset.reports.models import ReportSchedule from superset.row_level_security.commands.exceptions import ( RLSRuleNotFoundError, - RuleBulkDeleteFailedError, + RuleDeleteFailedError, ) logger = logging.getLogger(__name__) -class BulkDeleteRLSRuleCommand(BaseCommand): +class DeleteRLSRuleCommand(BaseCommand): def __init__(self, model_ids: list[int]): self._model_ids = model_ids self._models: list[ReportSchedule] = [] @@ -40,7 +40,7 @@ def run(self) -> None: RLSDAO.delete(self._models) except DAODeleteFailedError as ex: logger.exception(ex.exception) - raise RuleBulkDeleteFailedError() from ex + raise RuleDeleteFailedError() from ex def validate(self) -> None: # Validate/populate model exists diff --git a/superset/row_level_security/commands/exceptions.py b/superset/row_level_security/commands/exceptions.py index 40f8e4af81af0..5eb8e0b103a0a 100644 --- a/superset/row_level_security/commands/exceptions.py +++ b/superset/row_level_security/commands/exceptions.py @@ -25,5 +25,5 @@ class RLSRuleNotFoundError(CommandException): message = _("RLS Rule not found.") -class RuleBulkDeleteFailedError(DeleteFailedError): - message = _("RLS Rule could not be deleted.") +class RuleDeleteFailedError(DeleteFailedError): + message = _("RLS rules could not be deleted.") diff --git a/tests/integration_tests/datasets/api_tests.py b/tests/integration_tests/datasets/api_tests.py index 97c4800478850..027002507aece 100644 --- a/tests/integration_tests/datasets/api_tests.py +++ b/tests/integration_tests/datasets/api_tests.py @@ -1530,7 +1530,7 @@ def test_delete_dataset_sqlalchemy_error(self, mock_dao_delete): rv = self.delete_assert_metric(uri, "delete") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 422 - assert data == {"message": "Dataset could not be deleted."} + assert data == {"message": "Datasets could not be deleted."} db.session.delete(dataset) db.session.commit() diff --git a/tests/integration_tests/security/row_level_security_tests.py b/tests/integration_tests/security/row_level_security_tests.py index 2a28089c3e51e..c29ebe9afef03 100644 --- a/tests/integration_tests/security/row_level_security_tests.py +++ b/tests/integration_tests/security/row_level_security_tests.py @@ -483,7 +483,7 @@ def test_put_success(self): db.session.commit() -class TestRowLevelSecurityBulkDeleteAPI(SupersetTestCase): +class TestRowLevelSecurityDeleteAPI(SupersetTestCase): def test_invalid_id_failure(self): self.login("Admin")