You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jo...@apache.org on 2023/07/12 22:45:35 UTC
[superset] branch master updated: chore(command): Condense delete/bulk-delete operations (#24607)
This is an automated email from the ASF dual-hosted git repository.
johnbodley pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new a156816064 chore(command): Condense delete/bulk-delete operations (#24607)
a156816064 is described below
commit a156816064c47b9ad12f34571f44e02cfb72fe75
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Wed Jul 12 15:45:29 2023 -0700
chore(command): Condense delete/bulk-delete operations (#24607)
Co-authored-by: Michael S. Molina <70...@users.noreply.github.com>
---
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 ++--
superset/annotation_layers/commands/bulk_delete.py | 54 -----------------
superset/annotation_layers/commands/delete.py | 16 ++---
superset/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 +-
.../queries/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 +-
superset/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(-)
diff --git a/superset/annotation_layers/annotations/api.py b/superset/annotation_layers/annotations/api.py
index 70e0a1ad02..484335b81c 100644
--- a/superset/annotation_layers/annotations/api.py
+++ b/superset/annotation_layers/annotations/api.py
@@ -24,9 +24,6 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
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 @@ from superset.annotation_layers.annotations.commands.delete import (
DeleteAnnotationCommand,
)
from superset.annotation_layers.annotations.commands.exceptions import (
- AnnotationBulkDeleteFailedError,
AnnotationCreateFailedError,
AnnotationDeleteFailedError,
AnnotationInvalidError,
@@ -438,7 +434,7 @@ class AnnotationRestApi(BaseSupersetModelRestApi):
$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 @@ class AnnotationRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteAnnotationCommand(item_ids).run()
+ DeleteAnnotationCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -506,5 +502,5 @@ class AnnotationRestApi(BaseSupersetModelRestApi):
)
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 153f93aa6f..0000000000
--- 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 bbd7f39dd6..2850f8cb96 100644
--- a/superset/annotation_layers/annotations/commands/delete.py
+++ b/superset/annotation_layers/annotations/commands/delete.py
@@ -30,22 +30,22 @@ logger = logging.getLogger(__name__)
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 e7fc93ecff..dcdf42cc85 100644
--- a/superset/annotation_layers/annotations/commands/exceptions.py
+++ b/superset/annotation_layers/annotations/commands/exceptions.py
@@ -48,10 +48,6 @@ class AnnotationUniquenessValidationError(ValidationError):
)
-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 25e995b611..2e64fec78c 100644
--- a/superset/annotation_layers/api.py
+++ b/superset/annotation_layers/api.py
@@ -23,14 +23,9 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
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 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
$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 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteAnnotationLayerCommand(item_ids).run()
+ DeleteAnnotationLayerCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -357,7 +352,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
)
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 a227fc3266..0000000000
--- 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 14f53272f1..41c727054b 100644
--- a/superset/annotation_layers/commands/delete.py
+++ b/superset/annotation_layers/commands/delete.py
@@ -31,24 +31,24 @@ logger = logging.getLogger(__name__)
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 584962321b..a9e6e97ecf 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 8cfe9fa4c2..a060b23b0e 100644
--- a/superset/charts/api.py
+++ b/superset/charts/api.py
@@ -32,11 +32,9 @@ from werkzeug.wrappers import Response as WerkzeugResponse
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 @@ class ChartRestApi(BaseSupersetModelRestApi):
$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 @@ class ChartRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteChartCommand(item_ids).run()
+ DeleteChartCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -532,7 +530,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
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("/<pk>/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 0555992e24..0000000000
--- 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 72d9f9a732..1b3d93f063 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 @@ logger = logging.getLogger(__name__)
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 83792ae252..00877aa803 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 3f0980f2ff..f7688afe03 100644
--- a/superset/css_templates/api.py
+++ b/superset/css_templates/api.py
@@ -23,9 +23,9 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
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 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteCssTemplateCommand(item_ids).run()
+ DeleteCssTemplateCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -141,5 +141,5 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
)
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 ef13a1c8e2..123658cb45 100644
--- a/superset/css_templates/commands/bulk_delete.py
+++ b/superset/css_templates/commands/delete.py
@@ -19,7 +19,7 @@ from typing import Optional
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 @@ from superset.models.core import CssTemplate
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 @@ class BulkDeleteCssTemplateCommand(BaseCommand):
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 d950822cfa..9551758106 100644
--- a/superset/css_templates/commands/exceptions.py
+++ b/superset/css_templates/commands/exceptions.py
@@ -19,8 +19,8 @@ from flask_babel import lazy_gettext as _
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 781ad6f6c7..8464c87444 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -39,12 +39,10 @@ from superset.commands.importers.exceptions import NoValidFilesFoundError
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 @@ class DashboardRestApi(BaseSupersetModelRestApi):
$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 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteDashboardCommand(item_ids).run()
+ DeleteDashboardCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -747,7 +745,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
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 707f0d722a..0000000000
--- 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 13b92977df..23e38093ab 100644
--- a/superset/dashboards/commands/delete.py
+++ b/superset/dashboards/commands/delete.py
@@ -37,33 +37,34 @@ logger = logging.getLogger(__name__)
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 1a5bdaf789..19184b894c 100644
--- a/superset/dashboards/commands/exceptions.py
+++ b/superset/dashboards/commands/exceptions.py
@@ -51,15 +51,7 @@ class DashboardNotFoundError(ObjectNotFoundError):
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 87e1d9e74c..d5a0478c5d 100644
--- a/superset/datasets/api.py
+++ b/superset/datasets/api.py
@@ -37,12 +37,10 @@ from superset.connectors.sqla.models import SqlaTable
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 @@ class DatasetRestApi(BaseSupersetModelRestApi):
$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 @@ class DatasetRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteDatasetCommand(item_ids).run()
+ DeleteDatasetCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -801,7 +799,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
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 48515bbc6a..0000000000
--- 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 3e7fc7d5a3..478267d01d 100644
--- a/superset/datasets/commands/delete.py
+++ b/superset/datasets/commands/delete.py
@@ -33,27 +33,28 @@ logger = logging.getLogger(__name__)
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 7c6ef86634..fe9fe94cc6 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 c6e980c5de..327a2ac4cd 100644
--- a/superset/queries/saved_queries/api.py
+++ b/superset/queries/saved_queries/api.py
@@ -36,11 +36,9 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
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 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteSavedQueryCommand(item_ids).run()
+ DeleteSavedQueryCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -224,7 +222,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
)
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 4e68f6b79d..40b73658e0 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.exceptions import DAODeleteFailedError
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 @@ class BulkDeleteSavedQueryCommand(BaseCommand):
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 7318573524..20797955e3 100644
--- a/superset/queries/saved_queries/commands/exceptions.py
+++ b/superset/queries/saved_queries/commands/exceptions.py
@@ -24,7 +24,7 @@ from superset.commands.exceptions import (
)
-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 3686ab74bd..eb6ddcfa7f 100644
--- a/superset/reports/api.py
+++ b/superset/reports/api.py
@@ -30,11 +30,9 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
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 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
$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 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
"""
item_ids = kwargs["rison"]
try:
- BulkDeleteReportScheduleCommand(item_ids).run()
+ DeleteReportScheduleCommand(item_ids).run()
return self.response(
200,
message=ngettext(
@@ -508,5 +506,5 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
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 3e3df3d100..0000000000
--- 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 4f9be93f8a..2cdac17c4d 100644
--- a/superset/reports/commands/delete.py
+++ b/superset/reports/commands/delete.py
@@ -33,28 +33,29 @@ logger = logging.getLogger(__name__)
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 cba12e0786..2d82d5c312 100644
--- a/superset/reports/commands/exceptions.py
+++ b/superset/reports/commands/exceptions.py
@@ -21,7 +21,6 @@ from superset.commands.exceptions import (
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 43912689fb..7bf00e92f7 100644
--- a/superset/row_level_security/api.py
+++ b/superset/row_level_security/api.py
@@ -32,8 +32,8 @@ from superset.connectors.sqla.models import RowLevelSecurityFilter
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 @@ class RLSRestApi(BaseSupersetModelRestApi):
"""
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 f0c8dddabc..d669f7d90f 100644
--- a/superset/row_level_security/commands/bulk_delete.py
+++ b/superset/row_level_security/commands/delete.py
@@ -23,13 +23,13 @@ from superset.daos.security import RLSDAO
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 @@ class BulkDeleteRLSRuleCommand(BaseCommand):
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 40f8e4af81..5eb8e0b103 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 97c4800478..027002507a 100644
--- a/tests/integration_tests/datasets/api_tests.py
+++ b/tests/integration_tests/datasets/api_tests.py
@@ -1530,7 +1530,7 @@ class TestDatasetApi(SupersetTestCase):
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 2a28089c3e..c29ebe9afe 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 @@ class TestRowLevelSecurityUpdateAPI(SupersetTestCase):
db.session.commit()
-class TestRowLevelSecurityBulkDeleteAPI(SupersetTestCase):
+class TestRowLevelSecurityDeleteAPI(SupersetTestCase):
def test_invalid_id_failure(self):
self.login("Admin")