diff --git a/.gitignore b/.gitignore index b228b9840..5dc500e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ *.pyc *.sqllite *.swp +.bento* .cache-loader .coverage .DS_Store diff --git a/.rat-excludes b/.rat-excludes index 61d68b54d..79ed787d2 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -29,7 +29,9 @@ apache_superset.egg-info .*json .*csv # Generated doc files +env/* docs/_build/* +docs/_modules/* _build/* _static/* .buildinfo diff --git a/requirements.txt b/requirements.txt index a89d85904..fb8414671 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ croniter==0.3.31 cryptography==2.8 decorator==4.4.1 # via retry defusedxml==0.6.0 # via python3-openid -flask-appbuilder==2.2.1 +flask-appbuilder==2.2.2rc3 flask-babel==0.12.2 # via flask-appbuilder flask-caching==1.8.0 flask-compress==1.4.0 diff --git a/superset/app.py b/superset/app.py index 98381977a..4a904eea1 100644 --- a/superset/app.py +++ b/superset/app.py @@ -149,21 +149,15 @@ class SupersetAppInitializer: CssTemplateAsyncModelView, ) from superset.views.chart.api import ChartRestApi - from superset.views.chart.views import SliceModelView, SliceAsync, SliceAddView + from superset.views.chart.views import SliceModelView, SliceAsync from superset.views.dashboard.api import DashboardRestApi from superset.views.dashboard.views import ( DashboardModelView, Dashboard, - DashboardAddView, DashboardModelViewAsync, ) from superset.views.database.api import DatabaseRestApi - from superset.views.database.views import ( - DatabaseView, - DatabaseTablesAsync, - CsvToDatabaseView, - DatabaseAsync, - ) + from superset.views.database.views import DatabaseView, CsvToDatabaseView from superset.views.datasource import Datasource from superset.views.log.api import LogRestApi from superset.views.log.views import LogModelView @@ -218,6 +212,16 @@ class SupersetAppInitializer: category_label=__("Sources"), category_icon="fa-database", ) + appbuilder.add_link( + "Tables", + label=__("Tables"), + href="/tablemodelview/list/?_flt_1_is_sqllab_view=y", + icon="fa-table", + category="Sources", + category_label=__("Sources"), + category_icon="fa-table", + ) + appbuilder.add_separator("Sources") appbuilder.add_view( SliceModelView, "Charts", @@ -259,16 +263,12 @@ class SupersetAppInitializer: appbuilder.add_view_no_menu(CssTemplateAsyncModelView) appbuilder.add_view_no_menu(CsvToDatabaseView) appbuilder.add_view_no_menu(Dashboard) - appbuilder.add_view_no_menu(DashboardAddView) appbuilder.add_view_no_menu(DashboardModelViewAsync) - appbuilder.add_view_no_menu(DatabaseAsync) - appbuilder.add_view_no_menu(DatabaseTablesAsync) appbuilder.add_view_no_menu(Datasource) appbuilder.add_view_no_menu(KV) appbuilder.add_view_no_menu(R) appbuilder.add_view_no_menu(SavedQueryView) appbuilder.add_view_no_menu(SavedQueryViewApi) - appbuilder.add_view_no_menu(SliceAddView) appbuilder.add_view_no_menu(SliceAsync) appbuilder.add_view_no_menu(SqlLab) appbuilder.add_view_no_menu(SqlMetricInlineView) @@ -282,12 +282,6 @@ class SupersetAppInitializer: # # Add links # - appbuilder.add_link( - __("Saved Queries"), - href="/sqllab/my_queries/", - icon="fa-save", - category="SQL Lab", - ) appbuilder.add_link( "Import Dashboards", label=__("Import Dashboards"), @@ -306,6 +300,12 @@ class SupersetAppInitializer: category="SQL Lab", category_label=__("SQL Lab"), ) + appbuilder.add_link( + __("Saved Queries"), + href="/sqllab/my_queries/", + icon="fa-save", + category="SQL Lab", + ) appbuilder.add_link( "Query Search", label=_("Query Search"), @@ -324,23 +324,11 @@ class SupersetAppInitializer: category_label=__("Sources"), category_icon="fa-wrench", ) - appbuilder.add_link( - "Tables", - label=__("Tables"), - href="/tablemodelview/list/?_flt_1_is_sqllab_view=y", - icon="fa-table", - category="Sources", - category_label=__("Sources"), - category_icon="fa-table", - ) # # Conditionally setup log views # - if ( - not self.config["FAB_ADD_SECURITY_VIEWS"] is False - or self.config["SUPERSET_LOG_VIEW"] is False - ): + if self.config["FAB_ADD_SECURITY_VIEWS"] and self.config["SUPERSET_LOG_VIEW"]: appbuilder.add_api(LogRestApi) appbuilder.add_view( LogModelView, diff --git a/superset/assets/src/dashboard/actions/sliceEntities.js b/superset/assets/src/dashboard/actions/sliceEntities.js index efee23eec..b0db62614 100644 --- a/superset/assets/src/dashboard/actions/sliceEntities.js +++ b/superset/assets/src/dashboard/actions/sliceEntities.js @@ -46,7 +46,7 @@ export function fetchAllSlices(userId) { dispatch(fetchAllSlicesStarted()); return SupersetClient.get({ - endpoint: `/sliceaddview/api/read?_flt_0_created_by=${userId}`, + endpoint: `/sliceasync/api/read?_flt_0_created_by=${userId}`, }) .then(({ json }) => { const slices = {}; diff --git a/superset/connectors/druid/views.py b/superset/connectors/druid/views.py index 523bfcd08..5a2cea051 100644 --- a/superset/connectors/druid/views.py +++ b/superset/connectors/druid/views.py @@ -30,6 +30,7 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectField from superset import app, appbuilder, db, security_manager from superset.connectors.base.views import DatasourceModelView from superset.connectors.connector_registry import ConnectorRegistry +from superset.constants import RouteMethod from superset.utils import core as utils from superset.views.base import ( BaseSupersetView, @@ -47,6 +48,7 @@ from . import models class DruidColumnInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.DruidColumn) + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Columns") show_title = _("Show Druid Column") @@ -133,6 +135,7 @@ class DruidColumnInlineView(CompactCRUDMixin, SupersetModelView): class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.DruidMetric) + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Metrics") show_title = _("Show Druid Metric") @@ -185,7 +188,7 @@ class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): datamodel = SQLAInterface(models.DruidCluster) - + include_route_methods = RouteMethod.CRUD_SET list_title = _("Druid Clusters") show_title = _("Show Druid Cluster") add_title = _("Add Druid Cluster") @@ -247,7 +250,7 @@ class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): class DruidDatasourceModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): datamodel = SQLAInterface(models.DruidDatasource) - + include_route_methods = RouteMethod.CRUD_SET list_title = _("Druid Datasources") show_title = _("Show Druid Datasource") add_title = _("Add Druid Datasource") diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py index c51057a46..c63c4f8df 100644 --- a/superset/connectors/sqla/views.py +++ b/superset/connectors/sqla/views.py @@ -31,6 +31,7 @@ from wtforms.validators import Regexp from superset import appbuilder, db, security_manager from superset.connectors.base.views import DatasourceModelView +from superset.constants import RouteMethod from superset.utils import core as utils from superset.views.base import ( DatasourceFilter, @@ -48,6 +49,8 @@ logger = logging.getLogger(__name__) class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.TableColumn) + # TODO TODO, review need for this on related_views + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Columns") show_title = _("Show Column") @@ -164,6 +167,7 @@ class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.SqlMetric) + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Metrics") show_title = _("Show Metric") @@ -223,6 +227,7 @@ class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): datamodel = SQLAInterface(models.SqlaTable) + include_route_methods = RouteMethod.CRUD_SET list_title = _("Tables") show_title = _("Show Table") diff --git a/superset/constants.py b/superset/constants.py index f8472c54f..78b47e53f 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -19,3 +19,43 @@ # string to use when None values *need* to be converted to/from strings NULL_STRING = "" + + +class RouteMethod: # pylint: disable=too-few-public-methods + """ + Route methods are a FAB concept around ModelView and RestModelView + classes in FAB. Derivatives can define `include_route_method` and + `exclude_route_methods` class attribute as a set of methods that + will or won't get exposed. + + This class is a collection of static constants to reference common + route methods, namely the ones defined in the base classes in FAB + """ + + # ModelView specific + ACTION = "action" + ACTION_POST = "action_post" + ADD = "add" + API_CREATE = "api_create" + API_DELETE = "api_delete" + API_GET = "api_get" + API_READ = "api_read" + API_UPDATE = "api_update" + DELETE = "delete" + DOWNLOAD = "download" + EDIT = "edit" + LIST = "list" + SHOW = "show" + + # RestModelView specific + EXPORT = "export" + GET = "get" + GET_LIST = "get_list" + POST = "post" + PUT = "put" + RELATED = "related" + + # Commonly used sets + CRUD_SET = {ADD, LIST, EDIT, DELETE, ACTION_POST} + RELATED_VIEW_SET = {ADD, LIST, EDIT, DELETE} + REST_MODEL_VIEW_CRUD_SET = {DELETE, GET, GET_LIST, POST, PUT} diff --git a/superset/security/manager.py b/superset/security/manager.py index 8b436209e..f1b69d138 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -32,6 +32,7 @@ from flask_appbuilder.security.views import ( PermissionViewModelView, RoleModelView, UserModelView, + ViewMenuModelView, ) from flask_appbuilder.widgets import ListWidget from sqlalchemy import or_ @@ -40,6 +41,7 @@ from sqlalchemy.orm.mapper import Mapper from superset import sql_parse from superset.connectors.connector_registry import ConnectorRegistry +from superset.constants import RouteMethod from superset.exceptions import SupersetSecurityException from superset.utils.core import DatasourceName @@ -76,8 +78,16 @@ RoleModelView.list_widget = SupersetRoleListWidget PermissionViewModelView.list_widget = SupersetSecurityListWidget PermissionModelView.list_widget = SupersetSecurityListWidget +# Limiting routes on FAB model views +UserModelView.include_route_methods = RouteMethod.CRUD_SET | {"userinfo"} +RoleModelView.include_route_methods = RouteMethod.CRUD_SET +PermissionViewModelView.include_route_methods = {RouteMethod.LIST} +PermissionModelView.include_route_methods = {RouteMethod.LIST} +ViewMenuModelView.include_route_methods = {RouteMethod.LIST} + class SupersetSecurityManager(SecurityManager): + userstatschartview = None READ_ONLY_MODEL_VIEWS = {"DatabaseAsync", "DatabaseView", "DruidClusterModelView"} USER_MODEL_VIEWS = { diff --git a/superset/views/annotations.py b/superset/views/annotations.py index 33fdb3825..5ba975050 100644 --- a/superset/views/annotations.py +++ b/superset/views/annotations.py @@ -18,6 +18,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_babel import lazy_gettext as _ from wtforms.validators import StopValidation +from superset.constants import RouteMethod from superset.models.annotations import Annotation, AnnotationLayer from .base import DeleteMixin, SupersetModelView @@ -45,6 +46,7 @@ class AnnotationModelView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(Annotation) + include_route_methods = RouteMethod.CRUD_SET list_title = _("List Annotation") show_title = _("Show Annotation") @@ -93,6 +95,7 @@ class AnnotationLayerModelView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(AnnotationLayer) + include_route_methods = RouteMethod.CRUD_SET list_title = _("List Annotation Layer") show_title = _("Show Annotation Layer") diff --git a/superset/views/base_api.py b/superset/views/base_api.py index a2ee1d408..976f5b499 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -63,6 +63,17 @@ class BaseSupersetModelRestApi(ModelRestApi): """ logger = logging.getLogger(__name__) + method_permission_name = { + "get_list": "list", + "get": "show", + "export": "mulexport", + "post": "add", + "put": "edit", + "delete": "delete", + "bulk_delete": "delete", + "info": "list", + "related": "list", + } order_rel_fields: Dict[str, Tuple[str, str]] = {} """ diff --git a/superset/views/chart/api.py b/superset/views/chart/api.py index 511b62ae9..47c7d7fb3 100644 --- a/superset/views/chart/api.py +++ b/superset/views/chart/api.py @@ -134,15 +134,6 @@ class ChartRestApi(SliceMixin, BaseOwnedModelRestApi): allow_browser_login = True class_permission_name = "SliceModelView" - method_permission_name = { - "get_list": "list", - "get": "show", - "post": "add", - "put": "edit", - "delete": "delete", - "info": "list", - "related": "list", - } show_columns = [ "slice_name", "description", diff --git a/superset/views/chart/views.py b/superset/views/chart/views.py index d6a0c7ed8..a493600b9 100644 --- a/superset/views/chart/views.py +++ b/superset/views/chart/views.py @@ -22,6 +22,7 @@ from flask_babel import lazy_gettext as _ from superset import db from superset.connectors.connector_registry import ConnectorRegistry +from superset.constants import RouteMethod from superset.models.slice import Slice from superset.utils import core as utils from superset.views.base import check_ownership, DeleteMixin, SupersetModelView @@ -33,6 +34,11 @@ class SliceModelView( ): # pylint: disable=too-many-ancestors route_base = "/chart" datamodel = SQLAInterface(Slice) + include_route_methods = RouteMethod.CRUD_SET | { + RouteMethod.DOWNLOAD, + RouteMethod.API_READ, + RouteMethod.API_DELETE, + } def pre_add(self, item): utils.validate_json(item.params) @@ -61,36 +67,27 @@ class SliceModelView( class SliceAsync(SliceModelView): # pylint: disable=too-many-ancestors route_base = "/sliceasync" - list_columns = [ - "id", - "slice_link", - "viz_type", - "slice_name", - "creator", - "modified", - "icons", - "changed_on_humanized", - ] - label_columns = {"icons": " ", "slice_link": _("Chart")} + include_route_methods = {RouteMethod.API_READ} - -class SliceAddView(SliceModelView): # pylint: disable=too-many-ancestors - route_base = "/sliceaddview" list_columns = [ - "id", - "slice_name", - "slice_url", - "edit_url", - "viz_type", - "params", - "description", - "description_markeddown", - "datasource_id", - "datasource_type", - "datasource_name_text", - "datasource_link", - "owners", - "modified", "changed_on", "changed_on_humanized", + "creator", + "datasource_id", + "datasource_link", + "datasource_name_text", + "datasource_type", + "description", + "description_markeddown", + "edit_url", + "icons", + "id", + "modified", + "owners", + "params", + "slice_link", + "slice_name", + "slice_url", + "viz_type", ] + label_columns = {"icons": " ", "slice_link": _("Chart")} diff --git a/superset/views/core.py b/superset/views/core.py index d96d67c79..7957572d5 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -70,6 +70,7 @@ from superset import ( ) from superset.connectors.connector_registry import ConnectorRegistry from superset.connectors.sqla.models import AnnotationDatasource +from superset.constants import RouteMethod from superset.exceptions import ( DatabaseNotFound, SupersetException, @@ -250,6 +251,7 @@ def _deserialize_results_payload( class AccessRequestsModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(DAR) + include_route_methods = RouteMethod.CRUD_SET list_columns = [ "username", "user_roles", @@ -2816,6 +2818,7 @@ class Superset(BaseSupersetView): class CssTemplateModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(models.CssTemplate) + include_route_methods = RouteMethod.CRUD_SET list_title = _("CSS Templates") show_title = _("Show CSS Template") @@ -2829,6 +2832,7 @@ class CssTemplateModelView(SupersetModelView, DeleteMixin): class CssTemplateAsyncModelView(CssTemplateModelView): + include_route_methods = {RouteMethod.API_READ} list_columns = ["template_name", "css"] @@ -2845,16 +2849,3 @@ def apply_http_headers(response: Response): if k not in response.headers: response.headers[k] = v return response - - -@app.route('/') -def panoramix(url): - return redirect(request.full_path.replace("panoramix", "superset")) - - -@app.route('/') -def caravel(url): - return redirect(request.full_path.replace("caravel", "superset")) - - -# --------------------------------------------------------------------- diff --git a/superset/views/dashboard/api.py b/superset/views/dashboard/api.py index 857f99ebd..80cac5569 100644 --- a/superset/views/dashboard/api.py +++ b/superset/views/dashboard/api.py @@ -27,6 +27,7 @@ from marshmallow import fields, post_load, pre_load, Schema, ValidationError from marshmallow.validate import Length from sqlalchemy.exc import SQLAlchemyError +from superset.constants import RouteMethod from superset.exceptions import SupersetException, SupersetSecurityException from superset.models.dashboard import Dashboard from superset.utils import core as utils @@ -130,22 +131,15 @@ get_export_ids_schema = {"type": "array", "items": {"type": "integer"}} class DashboardRestApi(DashboardMixin, BaseOwnedModelRestApi): datamodel = SQLAInterface(Dashboard) - + include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | { + RouteMethod.EXPORT, + RouteMethod.RELATED, + "bulk_delete", # not using RouteMethod since locally defined + } resource_name = "dashboard" allow_browser_login = True class_permission_name = "DashboardModelView" - method_permission_name = { - "get_list": "list", - "get": "show", - "export": "mulexport", - "post": "add", - "put": "edit", - "delete": "delete", - "bulk_delete": "delete", - "info": "list", - "related": "list", - } show_columns = [ "dashboard_title", "slug", diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py index 236f426a4..69c37e5e8 100644 --- a/superset/views/dashboard/views.py +++ b/superset/views/dashboard/views.py @@ -26,6 +26,7 @@ from flask_babel import gettext as __, lazy_gettext as _ import superset.models.core as models from superset import db, event_logger +from superset.constants import RouteMethod from superset.utils import core as utils from ..base import ( @@ -45,6 +46,13 @@ class DashboardModelView( ): # pylint: disable=too-many-ancestors route_base = "/dashboard" datamodel = SQLAInterface(models.Dashboard) + # TODO disable api_read and api_delete (used by cypress) + # once we move to ChartRestModelApi + include_route_methods = RouteMethod.CRUD_SET | { + RouteMethod.API_READ, + RouteMethod.API_DELETE, + "download_dashboards", + } @has_access @expose("/list/") @@ -119,6 +127,8 @@ class Dashboard(BaseSupersetView): class DashboardModelViewAsync(DashboardModelView): # pylint: disable=too-many-ancestors route_base = "/dashboardasync" + include_route_methods = {RouteMethod.API_READ} + list_columns = [ "id", "dashboard_link", @@ -135,18 +145,3 @@ class DashboardModelViewAsync(DashboardModelView): # pylint: disable=too-many-a "creator": _("Creator"), "modified": _("Modified"), } - - -class DashboardAddView(DashboardModelView): # pylint: disable=too-many-ancestors - route_base = "/dashboardaddview" - list_columns = [ - "id", - "dashboard_link", - "creator", - "modified", - "dashboard_title", - "changed_on", - "url", - "changed_by_name", - ] - show_columns = list(set(DashboardModelView.edit_columns + list_columns)) diff --git a/superset/views/database/api.py b/superset/views/database/api.py index 0c1b688a9..72d2d093a 100644 --- a/superset/views/database/api.py +++ b/superset/views/database/api.py @@ -14,27 +14,20 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from flask_appbuilder import ModelRestApi from flask_appbuilder.models.sqla.interface import SQLAInterface import superset.models.core as models +from superset.views.base_api import BaseSupersetModelRestApi from .mixins import DatabaseFilter, DatabaseMixin from .validators import sqlalchemy_uri_validator -class DatabaseRestApi(DatabaseMixin, ModelRestApi): +class DatabaseRestApi(DatabaseMixin, BaseSupersetModelRestApi): datamodel = SQLAInterface(models.Database) + include_route_methods = {"get_list"} - class_permission_name = "DatabaseAsync" - method_permission_name = { - "get_list": "list", - "get": "show", - "post": "add", - "put": "edit", - "delete": "delete", - "info": "list", - } + class_permission_name = "DatabaseView" resource_name = "database" allow_browser_login = True base_filters = [["id", DatabaseFilter, lambda: []]] diff --git a/superset/views/database/views.py b/superset/views/database/views.py index 19620e1d1..9a28715f6 100644 --- a/superset/views/database/views.py +++ b/superset/views/database/views.py @@ -27,6 +27,7 @@ from wtforms.validators import ValidationError import superset.models.core as models from superset import app, db from superset.connectors.sqla.models import SqlaTable +from superset.constants import RouteMethod from superset.utils import core as utils from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin @@ -49,6 +50,7 @@ class DatabaseView( DatabaseMixin, SupersetModelView, DeleteMixin, YamlExportMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(models.Database) + include_route_methods = RouteMethod.CRUD_SET add_template = "superset/models/database/add.html" edit_template = "superset/models/database/edit.html" @@ -157,23 +159,3 @@ class CsvToDatabaseView(SimpleFormView): flash(message, "info") stats_logger.incr("successful_csv_upload") return redirect("/tablemodelview/list/") - - -class DatabaseTablesAsync(DatabaseView): # pylint: disable=too-many-ancestors - list_columns = ["id", "all_table_names_in_database", "all_schema_names"] - - -class DatabaseAsync(DatabaseView): # pylint: disable=too-many-ancestors - list_columns = [ - "id", - "database_name", - "expose_in_sqllab", - "allow_ctas", - "force_ctas_schema", - "allow_run_async", - "allow_dml", - "allow_multi_schema_metadata_fetch", - "allow_csv_upload", - "allows_subquery", - "backend", - ] diff --git a/superset/views/log/api.py b/superset/views/log/api.py index c26e0917d..cc9424ef7 100644 --- a/superset/views/log/api.py +++ b/superset/views/log/api.py @@ -14,26 +14,18 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from flask_appbuilder import ModelRestApi from flask_appbuilder.models.sqla.interface import SQLAInterface import superset.models.core as models +from superset.views.base_api import BaseSupersetModelRestApi from . import LogMixin -class LogRestApi(LogMixin, ModelRestApi): +class LogRestApi(LogMixin, BaseSupersetModelRestApi): datamodel = SQLAInterface(models.Log) class_permission_name = "LogModelView" - method_permission_name = { - "get_list": "list", - "get": "show", - "post": "add", - "put": "edit", - "delete": "delete", - "info": "list", - } resource_name = "log" allow_browser_login = True list_columns = ("user.username", "action", "dttm") diff --git a/superset/views/log/views.py b/superset/views/log/views.py index a9f3bd016..7139d3485 100644 --- a/superset/views/log/views.py +++ b/superset/views/log/views.py @@ -17,6 +17,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface import superset.models.core as models +from superset.constants import RouteMethod from superset.views.base import SupersetModelView from . import LogMixin @@ -24,3 +25,4 @@ from . import LogMixin class LogModelView(LogMixin, SupersetModelView): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(models.Log) + include_route_methods = {RouteMethod.LIST, RouteMethod.SHOW} diff --git a/superset/views/schedules.py b/superset/views/schedules.py index c1f690d81..ad7861e75 100644 --- a/superset/views/schedules.py +++ b/superset/views/schedules.py @@ -27,6 +27,7 @@ from flask_babel import lazy_gettext as _ from wtforms import BooleanField, StringField from superset import db, security_manager +from superset.constants import RouteMethod from superset.exceptions import SupersetException from superset.models.dashboard import Dashboard from superset.models.schedules import ( @@ -45,6 +46,7 @@ from .base import DeleteMixin, SupersetModelView class EmailScheduleView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors + include_route_methods = RouteMethod.CRUD_SET _extra_data = {"test_email": False, "test_email_recipients": None} schedule_type: Optional[Type] = None schedule_type_model: Optional[Type] = None diff --git a/superset/views/sql_lab.py b/superset/views/sql_lab.py index e53141868..5414c1595 100644 --- a/superset/views/sql_lab.py +++ b/superset/views/sql_lab.py @@ -25,6 +25,7 @@ from flask_babel import lazy_gettext as _ from flask_sqlalchemy import BaseQuery from superset import db, get_feature_flags, security_manager +from superset.constants import RouteMethod from superset.models.sql_lab import Query, SavedQuery, TableSchema, TabState from superset.utils import core as utils @@ -52,6 +53,7 @@ class QueryFilter(BaseFilter): # pylint: disable=too-few-public-methods class QueryView(SupersetModelView): datamodel = SQLAInterface(Query) + include_route_methods = {RouteMethod.SHOW, RouteMethod.LIST, RouteMethod.API_READ} list_title = _("List Query") show_title = _("Show Query") @@ -75,6 +77,7 @@ class SavedQueryView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(SavedQuery) + include_route_methods = RouteMethod.CRUD_SET list_title = _("List Saved Query") show_title = _("Show Saved Query") @@ -143,6 +146,12 @@ class SavedQueryView( class SavedQueryViewApi(SavedQueryView): # pylint: disable=too-many-ancestors + include_route_methods = { + RouteMethod.API_READ, + RouteMethod.API_CREATE, + RouteMethod.API_UPDATE, + RouteMethod.API_GET, + } list_columns = [ "id", "label", diff --git a/tests/core_tests.py b/tests/core_tests.py index 060b29255..cc53141b7 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -345,7 +345,7 @@ class CoreTests(SupersetTestCase): def test_get_user_slices(self): self.login(username="admin") userid = security_manager.find_user("admin").id - url = "/sliceaddview/api/read?_flt_0_created_by={}".format(userid) + url = f"/sliceasync/api/read?_flt_0_created_by={userid}" resp = self.client.get(url) self.assertEqual(resp.status_code, 200) diff --git a/tests/import_export_tests.py b/tests/import_export_tests.py index b932bf031..bf5f6dd55 100644 --- a/tests/import_export_tests.py +++ b/tests/import_export_tests.py @@ -235,9 +235,8 @@ class ImportExportTests(SupersetTestCase): def test_export_1_dashboard(self): self.login("admin") birth_dash = self.get_dash_by_slug("births") - export_dash_url = "/dashboard/export_dashboards_form?id={}&action=go".format( - birth_dash.id - ) + id_ = birth_dash.id + export_dash_url = f"/dashboard/export_dashboards_form?id={id_}&action=go" resp = self.client.get(export_dash_url) exported_dashboards = json.loads( resp.data.decode("utf-8"), object_hook=decode_dashboards @@ -247,7 +246,7 @@ class ImportExportTests(SupersetTestCase): self.assert_only_exported_slc_fields(birth_dash, exported_dashboards[0]) self.assert_dash_equals(birth_dash, exported_dashboards[0]) self.assertEqual( - birth_dash.id, + id_, json.loads( exported_dashboards[0].json_metadata, object_hook=decode_dashboards )["remote_id"], diff --git a/tests/security_tests.py b/tests/security_tests.py index afaad3866..f16f41103 100644 --- a/tests/security_tests.py +++ b/tests/security_tests.py @@ -486,14 +486,6 @@ class RolePermissionTests(SupersetTestCase): example_db.expose_in_sqllab = True session.commit() - OLD_FLASK_GET_SQL_DBS_REQUEST = ( - "databaseasync/api/read?_flt_0_expose_in_sqllab=1&" - "_oc_DatabaseAsync=database_name&_od_DatabaseAsync=asc" - ) - self.login(username="gamma") - databases_json = self.client.get(OLD_FLASK_GET_SQL_DBS_REQUEST).json - self.assertEquals(databases_json["count"], 1) - arguments = { "keys": ["none"], "filters": [{"col": "expose_in_sqllab", "opr": "eq", "value": True}], @@ -509,18 +501,15 @@ class RolePermissionTests(SupersetTestCase): self.logout() def assert_can_read(self, view_menu, permissions_set): - self.assertIn(("can_show", view_menu), permissions_set) self.assertIn(("can_list", view_menu), permissions_set) def assert_can_write(self, view_menu, permissions_set): self.assertIn(("can_add", view_menu), permissions_set) - self.assertIn(("can_download", view_menu), permissions_set) self.assertIn(("can_delete", view_menu), permissions_set) self.assertIn(("can_edit", view_menu), permissions_set) def assert_cannot_write(self, view_menu, permissions_set): self.assertNotIn(("can_add", view_menu), permissions_set) - self.assertNotIn(("can_download", view_menu), permissions_set) self.assertNotIn(("can_delete", view_menu), permissions_set) self.assertNotIn(("can_edit", view_menu), permissions_set) self.assertNotIn(("can_save", view_menu), permissions_set) @@ -530,7 +519,6 @@ class RolePermissionTests(SupersetTestCase): self.assert_can_write(view_menu, permissions_set) def assert_can_gamma(self, perm_set): - self.assert_can_read("DatabaseAsync", perm_set) self.assert_can_read("TableModelView", perm_set) # make sure that user can create slices and dashboards @@ -554,8 +542,6 @@ class RolePermissionTests(SupersetTestCase): self.assertIn(("can_userinfo", "UserDBModelView"), perm_set) def assert_can_alpha(self, perm_set): - self.assert_can_all("SqlMetricInlineView", perm_set) - self.assert_can_all("TableColumnInlineView", perm_set) self.assert_can_all("TableModelView", perm_set) self.assertIn(("all_datasource_access", "all_datasource_access"), perm_set) @@ -569,7 +555,6 @@ class RolePermissionTests(SupersetTestCase): self.assert_cannot_write("UserDBModelView", perm_set) def assert_can_admin(self, perm_set): - self.assert_can_read("DatabaseAsync", perm_set) self.assert_can_all("DatabaseView", perm_set) self.assert_can_all("RoleModelView", perm_set) self.assert_can_all("UserDBModelView", perm_set) @@ -583,7 +568,7 @@ class RolePermissionTests(SupersetTestCase): def test_is_admin_only(self): self.assertFalse( security_manager._is_admin_only( - security_manager.find_permission_view_menu("can_show", "TableModelView") + security_manager.find_permission_view_menu("can_list", "TableModelView") ) ) self.assertFalse( @@ -603,7 +588,7 @@ class RolePermissionTests(SupersetTestCase): self.assertTrue( security_manager._is_admin_only( security_manager.find_permission_view_menu( - "can_show", "AccessRequestsModelView" + "can_list", "AccessRequestsModelView" ) ) ) @@ -626,7 +611,7 @@ class RolePermissionTests(SupersetTestCase): def test_is_alpha_only(self): self.assertFalse( security_manager._is_alpha_only( - security_manager.find_permission_view_menu("can_show", "TableModelView") + security_manager.find_permission_view_menu("can_list", "TableModelView") ) ) @@ -644,13 +629,6 @@ class RolePermissionTests(SupersetTestCase): ) ) ) - self.assertTrue( - security_manager._is_alpha_only( - security_manager.find_permission_view_menu( - "can_edit", "SqlMetricInlineView" - ) - ) - ) self.assertTrue( security_manager._is_alpha_only( security_manager.find_permission_view_menu( @@ -662,7 +640,7 @@ class RolePermissionTests(SupersetTestCase): def test_is_gamma_pvm(self): self.assertTrue( security_manager._is_gamma_pvm( - security_manager.find_permission_view_menu("can_show", "TableModelView") + security_manager.find_permission_view_menu("can_list", "TableModelView") ) ) @@ -674,9 +652,10 @@ class RolePermissionTests(SupersetTestCase): SupersetTestCase.is_module_installed("pydruid"), "pydruid not installed" ) def test_alpha_permissions(self): - self.assert_can_gamma(get_perm_tuples("Alpha")) - self.assert_can_alpha(get_perm_tuples("Alpha")) - self.assert_cannot_alpha(get_perm_tuples("Alpha")) + alpha_perm_tuples = get_perm_tuples("Alpha") + self.assert_can_gamma(alpha_perm_tuples) + self.assert_can_alpha(alpha_perm_tuples) + self.assert_cannot_alpha(alpha_perm_tuples) @unittest.skipUnless( SupersetTestCase.is_module_installed("pydruid"), "pydruid not installed" @@ -703,18 +682,15 @@ class RolePermissionTests(SupersetTestCase): def test_gamma_permissions(self): def assert_can_read(view_menu): - self.assertIn(("can_show", view_menu), gamma_perm_set) self.assertIn(("can_list", view_menu), gamma_perm_set) def assert_can_write(view_menu): self.assertIn(("can_add", view_menu), gamma_perm_set) - self.assertIn(("can_download", view_menu), gamma_perm_set) self.assertIn(("can_delete", view_menu), gamma_perm_set) self.assertIn(("can_edit", view_menu), gamma_perm_set) def assert_cannot_write(view_menu): self.assertNotIn(("can_add", view_menu), gamma_perm_set) - self.assertNotIn(("can_download", view_menu), gamma_perm_set) self.assertNotIn(("can_delete", view_menu), gamma_perm_set) self.assertNotIn(("can_edit", view_menu), gamma_perm_set) self.assertNotIn(("can_save", view_menu), gamma_perm_set)