chore(command): Condense delete/bulk-delete operations (#24607)

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
This commit is contained in:
John Bodley
2023-07-12 15:45:29 -07:00
committed by GitHub
co-authored by Michael S. Molina
parent 6ae6fc60f8
commit a156816064
35 changed files with 123 additions and 538 deletions
@@ -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))
@@ -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()
@@ -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()
@@ -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.")
+4 -9
View File
@@ -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))
@@ -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()
@@ -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()
@@ -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
+3 -5
View File
@@ -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",))
-70
View File
@@ -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
+18 -14
View File
@@ -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
+1 -9
View File
@@ -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")
+4 -4
View File
@@ -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))
@@ -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
@@ -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):
+3 -5
View File
@@ -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",))
@@ -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
+13 -12
View File
@@ -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
+1 -9
View File
@@ -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):
+3 -5
View File
@@ -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",))
-60
View File
@@ -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
+13 -12
View File
@@ -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
+1 -5
View File
@@ -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):
+4 -6
View File
@@ -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",))
@@ -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
@@ -24,7 +24,7 @@ from superset.commands.exceptions import (
)
class SavedQueryBulkDeleteFailedError(DeleteFailedError):
class SavedQueryDeleteFailedError(DeleteFailedError):
message = _("Saved queries could not be deleted.")
+3 -5
View File
@@ -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))
-61
View File
@@ -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
+12 -11
View File
@@ -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
-5
View File
@@ -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.")
+2 -2
View File
@@ -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(
@@ -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
@@ -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.")
@@ -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()
@@ -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")