This is an automated email from the ASF dual-hosted git repository.

villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new fdd28c1  Upload excel (#9825)
fdd28c1 is described below

commit fdd28c1a5ed6727b12bc30632dcf901db87f0d0a
Author: Hossein Torabi <blck...@gmail.com>
AuthorDate: Fri Jul 3 09:58:30 2020 +0430

    Upload excel (#9825)
---
 UPDATING.md                                        |   2 +
 requirements.txt                                   |   2 +-
 setup.py                                           |   1 +
 superset/app.py                                    |  45 ++++--
 superset/config.py                                 |   5 +-
 superset/db_engine_specs/base.py                   |  36 +++++
 .../form_view/excel_to_database_view/edit.html     |  64 ++++++++
 superset/views/database/forms.py                   | 174 ++++++++++++++++++++-
 superset/views/database/views.py                   | 162 +++++++++++++++++--
 9 files changed, 465 insertions(+), 26 deletions(-)

diff --git a/UPDATING.md b/UPDATING.md
index 0c04796..8eccc80 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -252,6 +252,8 @@ If you run a production system you should schedule downtime 
for this
 upgrade.
 
 The PRs bellow have more information around the breaking changes:
+* [9825](https://github.com/apache/incubator-superset/pull/9825):  Support for 
Excel sheet upload added. To enable support, install Superset with the optional 
dependency `excel`
+
 * [4587](https://github.com/apache/incubator-superset/pull/4587) : a backward
   incompatible database migration that requires downtime. Once the
   db migration succeeds, the web server needs to be restarted with the
diff --git a/requirements.txt b/requirements.txt
index 1ed3e7b..73e7be6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -100,4 +100,4 @@ yarl==1.4.2               # via aiohttp
 zipp==3.1.0               # via importlib-metadata
 
 # The following packages are considered to be unsafe in a requirements file:
-# setuptools
+# setuptools
\ No newline at end of file
diff --git a/setup.py b/setup.py
index f2a4e1b..897c305 100644
--- a/setup.py
+++ b/setup.py
@@ -123,6 +123,7 @@ setup(
         "dremio": ["sqlalchemy_dremio>=1.1.0"],
         "cockroachdb": ["cockroachdb==0.3.3"],
         "thumbnails": ["Pillow>=7.0.0, <8.0.0"],
+        "excel": ["xlrd>=1.2.0, <1.3"],
     },
     python_requires="~=3.6",
     author="Apache Software Foundation",
diff --git a/superset/app.py b/superset/app.py
index 7dc7aec..c047a28 100644
--- a/superset/app.py
+++ b/superset/app.py
@@ -159,7 +159,11 @@ class SupersetAppInitializer:
             DashboardModelViewAsync,
         )
         from superset.views.database.api import DatabaseRestApi
-        from superset.views.database.views import DatabaseView, 
CsvToDatabaseView
+        from superset.views.database.views import (
+            DatabaseView,
+            CsvToDatabaseView,
+            ExcelToDatabaseView,
+        )
         from superset.views.datasource import Datasource
         from superset.views.log.api import LogRestApi
         from superset.views.log.views import LogModelView
@@ -265,6 +269,7 @@ class SupersetAppInitializer:
         appbuilder.add_view_no_menu(Api)
         appbuilder.add_view_no_menu(CssTemplateAsyncModelView)
         appbuilder.add_view_no_menu(CsvToDatabaseView)
+        appbuilder.add_view_no_menu(ExcelToDatabaseView)
         appbuilder.add_view_no_menu(Dashboard)
         appbuilder.add_view_no_menu(DashboardModelViewAsync)
         appbuilder.add_view_no_menu(Datasource)
@@ -324,15 +329,35 @@ class SupersetAppInitializer:
             category="SQL Lab",
             category_label=__("SQL Lab"),
         )
-        appbuilder.add_link(
-            "Upload a CSV",
-            label=__("Upload a CSV"),
-            href="/csvtodatabaseview/form",
-            icon="fa-upload",
-            category="Sources",
-            category_label=__("Sources"),
-            category_icon="fa-wrench",
-        )
+        if self.config["CSV_EXTENSIONS"].intersection(
+            self.config["ALLOWED_EXTENSIONS"]
+        ):
+            appbuilder.add_link(
+                "Upload a CSV",
+                label=__("Upload a CSV"),
+                href="/csvtodatabaseview/form",
+                icon="fa-upload",
+                category="Sources",
+                category_label=__("Sources"),
+                category_icon="fa-wrench",
+            )
+        try:
+            import xlrd  # pylint: disable=unused-import
+
+            if self.config["EXCEL_EXTENSIONS"].intersection(
+                self.config["ALLOWED_EXTENSIONS"]
+            ):
+                appbuilder.add_link(
+                    "Upload Excel",
+                    label=__("Upload Excel"),
+                    href="/exceltodatabaseview/form",
+                    icon="fa-upload",
+                    category="Sources",
+                    category_label=__("Sources"),
+                    category_icon="fa-wrench",
+                )
+        except ImportError:
+            pass
 
         #
         # Conditionally setup log views
diff --git a/superset/config.py b/superset/config.py
index 5ef169c..89b813f 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -365,8 +365,9 @@ CORS_OPTIONS: Dict[Any, Any] = {}
 SUPERSET_WEBSERVER_DOMAINS = None
 
 # Allowed format types for upload on Database view
-# TODO: Add processing of other spreadsheet formats (xls, xlsx etc)
-ALLOWED_EXTENSIONS = {"csv", "tsv"}
+EXCEL_EXTENSIONS = {"xlsx", "xls"}
+CSV_EXTENSIONS = {"csv", "tsv"}
+ALLOWED_EXTENSIONS = {*EXCEL_EXTENSIONS, *CSV_EXTENSIONS}
 
 # CSV Options: key/value pairs that will be passed as argument to 
DataFrame.to_csv
 # method.
diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py
index c86ee9d..8375eb9 100644
--- a/superset/db_engine_specs/base.py
+++ b/superset/db_engine_specs/base.py
@@ -431,6 +431,20 @@ class BaseEngineSpec:  # pylint: 
disable=too-many-public-methods
         return parsed_query.set_or_update_query_limit(limit)
 
     @staticmethod
+    def excel_to_df(**kwargs: Any) -> pd.DataFrame:
+        """ Read excel into Pandas DataFrame
+           :param kwargs: params to be passed to DataFrame.read_excel
+           :return: Pandas DataFrame containing data from excel
+        """
+        kwargs["encoding"] = "utf-8"
+        kwargs["iterator"] = True
+        chunks = pd.io.excel.read_excel(
+            io=kwargs["filepath_or_buffer"], sheet_name=kwargs["sheet_name"]
+        )
+        df = pd.concat(chunk for chunk in chunks.values())
+        return df
+
+    @staticmethod
     def csv_to_df(**kwargs: Any) -> pd.DataFrame:
         """ Read csv into Pandas DataFrame
         :param kwargs: params to be passed to DataFrame.read_csv
@@ -487,6 +501,28 @@ class BaseEngineSpec:  # pylint: 
disable=too-many-public-methods
         return None
 
     @classmethod
+    def create_table_from_excel(  # pylint: disable=too-many-arguments
+        cls,
+        filename: str,
+        table: Table,
+        database: "Database",
+        excel_to_df_kwargs: Dict[str, Any],
+        df_to_sql_kwargs: Dict[str, Any],
+    ) -> None:
+        """
+        Create table from contents of a excel. Note: this method does not 
create
+        metadata for the table.
+        """
+        df = cls.excel_to_df(filepath_or_buffer=filename, 
**excel_to_df_kwargs,)
+        engine = cls.get_engine(database)
+        if table.schema:
+            # only add schema when it is preset and non empty
+            df_to_sql_kwargs["schema"] = table.schema
+        if engine.dialect.supports_multivalues_insert:
+            df_to_sql_kwargs["method"] = "multi"
+        cls.df_to_sql(df=df, con=engine, **df_to_sql_kwargs)
+
+    @classmethod
     def get_all_datasource_names(
         cls, database: "Database", datasource_type: str
     ) -> List[utils.DatasourceName]:
diff --git 
a/superset/templates/superset/form_view/excel_to_database_view/edit.html 
b/superset/templates/superset/form_view/excel_to_database_view/edit.html
new file mode 100644
index 0000000..dcfd6d2
--- /dev/null
+++ b/superset/templates/superset/form_view/excel_to_database_view/edit.html
@@ -0,0 +1,64 @@
+{#
+  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.
+#}
+{% extends 'appbuilder/general/model/edit.html' %}
+
+{% block tail_js %}
+  {{ super() }}
+  <script>
+    var db = $("#con");
+    var schema = $("#schema");
+
+    // this element is a text input
+    // copy it here so it can be reused later
+    var any_schema_is_allowed = schema.clone();
+
+    update_schemas_allowed_for_excel_upload(db.val());
+    db.change(function(){
+        update_schemas_allowed_for_excel_upload(db.val());
+    });
+
+    function update_schemas_allowed_for_excel_upload(db_id) {
+        $.ajax({
+          method: "GET",
+          url: "/superset/schemas_access_for_excel_upload",
+          data: {db_id: db_id},
+          dataType: 'json',
+          contentType: "application/json; charset=utf-8"
+        }).done(function(data) {
+          change_schema_field_in_formview(data)
+        }).fail(function(error) {
+          var errorMsg = error.responseJSON.error;
+          alert("ERROR: " + errorMsg);
+        });
+    }
+
+    function change_schema_field_in_formview(schemas_allowed){
+        if (schemas_allowed && schemas_allowed.length > 0) {
+            var dropdown_schema_lists = '<select id="schema" name="schema" 
required>';
+            schemas_allowed.forEach(function(schema_allowed) {
+              dropdown_schema_lists += ('<option value="' + schema_allowed + 
'">' + schema_allowed + '</option>');
+            });
+            dropdown_schema_lists += '</select>';
+            $("#schema").replaceWith(dropdown_schema_lists);
+        } else {
+            $("#schema").replaceWith(any_schema_is_allowed)
+        }
+    }
+  </script>
+{% endblock %}
\ No newline at end of file
diff --git a/superset/views/database/forms.py b/superset/views/database/forms.py
index b57caff..13059a4 100644
--- a/superset/views/database/forms.py
+++ b/superset/views/database/forms.py
@@ -91,11 +91,15 @@ class CsvToDatabaseForm(DynamicForm):
         validators=[
             FileRequired(),
             FileAllowed(
-                config["ALLOWED_EXTENSIONS"],
+                
config["ALLOWED_EXTENSIONS"].intersection(config["CSV_EXTENSIONS"]),
                 _(
                     "Only the following file extensions are allowed: "
                     "%(allowed_extensions)s",
-                    allowed_extensions=", ".join(config["ALLOWED_EXTENSIONS"]),
+                    allowed_extensions=", ".join(
+                        config["ALLOWED_EXTENSIONS"].intersection(
+                            config["CSV_EXTENSIONS"]
+                        )
+                    ),
                 ),
             ),
         ],
@@ -206,3 +210,169 @@ class CsvToDatabaseForm(DynamicForm):
         validators=[Optional()],
         widget=BS3TextFieldWidget(),
     )
+
+
+class ExcelToDatabaseForm(DynamicForm):
+    # pylint: disable=E0211
+    def excel_allowed_dbs():  # type: ignore
+        excel_allowed_dbs = []
+        # TODO: change allow_csv_upload to allow_file_upload
+        excel_enabled_dbs = (
+            db.session.query(Database).filter_by(allow_csv_upload=True).all()
+        )
+        for excel_enabled_db in excel_enabled_dbs:
+            if 
ExcelToDatabaseForm.at_least_one_schema_is_allowed(excel_enabled_db):
+                excel_allowed_dbs.append(excel_enabled_db)
+        return excel_allowed_dbs
+
+    @staticmethod
+    def at_least_one_schema_is_allowed(database: Database) -> bool:
+        """
+        If the user has access to the database or all datasource
+            1. if schemas_allowed_for_csv_upload is empty
+                a) if database does not support schema
+                    user is able to upload excel without specifying schema name
+                b) if database supports schema
+                    user is able to upload excel to any schema
+            2. if schemas_allowed_for_csv_upload is not empty
+                a) if database does not support schema
+                    This situation is impossible and upload will fail
+                b) if database supports schema
+                    user is able to upload to schema in 
schemas_allowed_for_csv_upload
+        elif the user does not access to the database or all datasource
+            1. if schemas_allowed_for_csv_upload is empty
+                a) if database does not support schema
+                    user is unable to upload excel
+                b) if database supports schema
+                    user is unable to upload excel
+            2. if schemas_allowed_for_csv_upload is not empty
+                a) if database does not support schema
+                    This situation is impossible and user is unable to upload 
excel
+                b) if database supports schema
+                    user is able to upload to schema in 
schemas_allowed_for_csv_upload
+        """
+        if (
+            security_manager.database_access(database)
+            or security_manager.all_datasource_access()
+        ):
+            return True
+        schemas = database.get_schema_access_for_csv_upload()
+        if schemas and security_manager.schemas_accessible_by_user(
+            database, schemas, False
+        ):
+            return True
+        return False
+
+    name = StringField(
+        _("Table Name"),
+        description=_("Name of table to be created from excel data."),
+        validators=[DataRequired()],
+        widget=BS3TextFieldWidget(),
+    )
+    excel_file = FileField(
+        _("Excel File"),
+        description=_("Select a Excel file to be uploaded to a database."),
+        validators=[
+            FileRequired(),
+            FileAllowed(
+                
config["ALLOWED_EXTENSIONS"].intersection(config["EXCEL_EXTENSIONS"]),
+                _(
+                    "Only the following file extensions are allowed: "
+                    "%(allowed_extensions)s",
+                    allowed_extensions=", ".join(
+                        config["ALLOWED_EXTENSIONS"].intersection(
+                            config["EXCEL_EXTENSIONS"]
+                        )
+                    ),
+                ),
+            ),
+        ],
+    )
+
+    sheet_name = StringField(
+        _("Sheet Name"), description="Sheet Name", validators=[Optional()]
+    )
+
+    con = QuerySelectField(
+        _("Database"),
+        query_factory=excel_allowed_dbs,
+        get_pk=lambda a: a.id,
+        get_label=lambda a: a.database_name,
+    )
+    schema = StringField(
+        _("Schema"),
+        description=_("Specify a schema (if database flavor supports this)."),
+        validators=[Optional()],
+        widget=BS3TextFieldWidget(),
+    )
+    if_exists = SelectField(
+        _("Table Exists"),
+        description=_(
+            "If table exists do one of the following: "
+            "Fail (do nothing), Replace (drop and recreate table) "
+            "or Append (insert data)."
+        ),
+        choices=[
+            ("fail", _("Fail")),
+            ("replace", _("Replace")),
+            ("append", _("Append")),
+        ],
+        validators=[DataRequired()],
+    )
+    header = IntegerField(
+        _("Header Row"),
+        description=_(
+            "Row containing the headers to use as "
+            "column names (0 is first line of data). "
+            "Leave empty if there is no header row."
+        ),
+        validators=[Optional(), NumberRange(min=0)],
+        widget=BS3TextFieldWidget(),
+    )
+    index_col = IntegerField(
+        _("Index Column"),
+        description=_(
+            "Column to use as the row labels of the "
+            "dataframe. Leave empty if no index column."
+        ),
+        validators=[Optional(), NumberRange(min=0)],
+        widget=BS3TextFieldWidget(),
+    )
+    mangle_dupe_cols = BooleanField(
+        _("Mangle Duplicate Columns"),
+        description=_('Specify duplicate columns as "X.0, X.1".'),
+    )
+    skipinitialspace = BooleanField(
+        _("Skip Initial Space"), description=_("Skip spaces after delimiter.")
+    )
+    skiprows = IntegerField(
+        _("Skip Rows"),
+        description=_("Number of rows to skip at start of file."),
+        validators=[Optional(), NumberRange(min=0)],
+        widget=BS3TextFieldWidget(),
+    )
+    nrows = IntegerField(
+        _("Rows to Read"),
+        description=_("Number of rows of file to read."),
+        validators=[Optional(), NumberRange(min=0)],
+        widget=BS3TextFieldWidget(),
+    )
+    decimal = StringField(
+        _("Decimal Character"),
+        default=".",
+        description=_("Character to interpret as decimal point."),
+        validators=[Optional(), Length(min=1, max=1)],
+        widget=BS3TextFieldWidget(),
+    )
+    index = BooleanField(
+        _("Dataframe Index"), description=_("Write dataframe index as a 
column.")
+    )
+    index_label = StringField(
+        _("Column Label(s)"),
+        description=_(
+            "Column label for index column(s). If None is given "
+            "and Dataframe Index is True, Index Names are used."
+        ),
+        validators=[Optional()],
+        widget=BS3TextFieldWidget(),
+    )
diff --git a/superset/views/database/views.py b/superset/views/database/views.py
index 8865270..47b8686 100644
--- a/superset/views/database/views.py
+++ b/superset/views/database/views.py
@@ -20,9 +20,9 @@ from typing import TYPE_CHECKING
 
 from flask import flash, g, redirect
 from flask_appbuilder import SimpleFormView
-from flask_appbuilder.forms import DynamicForm
 from flask_appbuilder.models.sqla.interface import SQLAInterface
 from flask_babel import lazy_gettext as _
+from werkzeug.wrappers import Response
 from wtforms.fields import StringField
 from wtforms.validators import ValidationError
 
@@ -32,12 +32,10 @@ from superset.connectors.sqla.models import SqlaTable
 from superset.constants import RouteMethod
 from superset.exceptions import CertificateException
 from superset.sql_parse import Table
-from superset.typing import FlaskResponse
 from superset.utils import core as utils
 from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin
-from superset.views.database.forms import CsvToDatabaseForm
 
-from .forms import CsvToDatabaseForm
+from .forms import CsvToDatabaseForm, ExcelToDatabaseForm
 from .mixins import DatabaseMixin
 from .validators import schema_allows_csv_upload, sqlalchemy_uri_validator
 
@@ -48,9 +46,7 @@ config = app.config
 stats_logger = config["STATS_LOGGER"]
 
 
-def sqlalchemy_uri_form_validator(  # pylint: disable=unused-argument
-    form: DynamicForm, field: StringField
-) -> None:
+def sqlalchemy_uri_form_validator(_: _, field: StringField) -> None:
     """
         Check if user has submitted a valid SQLAlchemy URI
     """
@@ -58,9 +54,7 @@ def sqlalchemy_uri_form_validator(  # pylint: 
disable=unused-argument
     sqlalchemy_uri_validator(field.data, exception=ValidationError)
 
 
-def certificate_form_validator(  # pylint: disable=unused-argument
-    form: DynamicForm, field: StringField
-) -> None:
+def certificate_form_validator(_: _, field: StringField) -> None:
     """
         Check if user has submitted a valid SSL certificate
     """
@@ -116,7 +110,7 @@ class CsvToDatabaseView(SimpleFormView):
         form.decimal.data = "."
         form.if_exists.data = "fail"
 
-    def form_post(self, form: CsvToDatabaseForm) -> FlaskResponse:
+    def form_post(self, form: CsvToDatabaseForm) -> Response:
         database = form.con.data
         csv_table = Table(table=form.name.data, schema=form.schema.data)
 
@@ -249,3 +243,149 @@ class CsvToDatabaseView(SimpleFormView):
         flash(message, "info")
         stats_logger.incr("successful_csv_upload")
         return redirect("/tablemodelview/list/")
+
+
+class ExcelToDatabaseView(SimpleFormView):
+    form = ExcelToDatabaseForm
+    form_template = "superset/form_view/excel_to_database_view/edit.html"
+    form_title = _("Excel to Database configuration")
+    add_columns = ["database", "schema", "table_name"]
+
+    def form_get(self, form: ExcelToDatabaseForm) -> None:
+        form.header.data = 0
+        form.mangle_dupe_cols.data = True
+        form.skipinitialspace.data = False
+        form.decimal.data = "."
+        form.if_exists.data = "fail"
+        form.sheet_name = None
+
+    def form_post(self, form: ExcelToDatabaseForm) -> Response:
+        database = form.con.data
+        excel_table = Table(table=form.name.data, schema=form.schema.data)
+
+        if not schema_allows_csv_upload(database, excel_table.schema):
+            message = _(
+                'Database "%(database_name)s" schema "%(schema_name)s" '
+                "is not allowed for excel uploads. Please contact your 
Superset Admin.",
+                database_name=database.database_name,
+                schema_name=excel_table.schema,
+            )
+            flash(message, "danger")
+            return redirect("/exceltodatabaseview/form")
+
+        if "." in excel_table.table and excel_table.schema:
+            message = _(
+                "You cannot specify a namespace both in the name of the table: 
"
+                '"%(excel_table.table)s" and in the schema field: '
+                '"%(excel_table.schema)s". Please remove one',
+                table=excel_table.table,
+                schema=excel_table.schema,
+            )
+            flash(message, "danger")
+            return redirect("/exceltodatabaseview/form")
+
+        uploaded_tmp_file_path = tempfile.NamedTemporaryFile(
+            dir=app.config["UPLOAD_FOLDER"],
+            suffix=os.path.splitext(form.excel_file.data.filename)[1].lower(),
+            delete=False,
+        ).name
+
+        try:
+            utils.ensure_path_exists(config["UPLOAD_FOLDER"])
+            upload_stream_write(form.excel_file.data, uploaded_tmp_file_path)
+
+            con = form.data.get("con")
+            database = (
+                
db.session.query(models.Database).filter_by(id=con.data.get("id")).one()
+            )
+            excel_to_df_kwargs = {
+                "header": form.header.data if form.header.data else 0,
+                "index_col": form.index_col.data,
+                "mangle_dupe_cols": form.mangle_dupe_cols.data,
+                "skipinitialspace": form.skipinitialspace.data,
+                "skiprows": form.skiprows.data,
+                "nrows": form.nrows.data,
+                "sheet_name": form.sheet_name.data,
+                "chunksize": 1000,
+            }
+            df_to_sql_kwargs = {
+                "name": excel_table.table,
+                "if_exists": form.if_exists.data,
+                "index": form.index.data,
+                "index_label": form.index_label.data,
+                "chunksize": 1000,
+            }
+            database.db_engine_spec.create_table_from_excel(
+                uploaded_tmp_file_path,
+                excel_table,
+                database,
+                excel_to_df_kwargs,
+                df_to_sql_kwargs,
+            )
+
+            # Connect table to the database that should be used for 
exploration.
+            # E.g. if hive was used to upload a excel, presto will be a better 
option
+            # to explore the table.
+            expore_database = database
+            explore_database_id = 
database.get_extra().get("explore_database_id", None)
+            if explore_database_id:
+                expore_database = (
+                    db.session.query(models.Database)
+                    .filter_by(id=explore_database_id)
+                    .one_or_none()
+                    or database
+                )
+
+            sqla_table = (
+                db.session.query(SqlaTable)
+                .filter_by(
+                    table_name=excel_table.table,
+                    schema=excel_table.schema,
+                    database_id=expore_database.id,
+                )
+                .one_or_none()
+            )
+
+            if sqla_table:
+                sqla_table.fetch_metadata()
+            if not sqla_table:
+                sqla_table = SqlaTable(table_name=excel_table.table)
+                sqla_table.database = expore_database
+                sqla_table.database_id = database.id
+                sqla_table.user_id = g.user.id
+                sqla_table.schema = excel_table.schema
+                sqla_table.fetch_metadata()
+                db.session.add(sqla_table)
+            db.session.commit()
+        except Exception as ex:  # pylint: disable=broad-except
+            db.session.rollback()
+            try:
+                os.remove(uploaded_tmp_file_path)
+            except OSError:
+                pass
+            message = _(
+                'Unable to upload Excel file "%(filename)s" to table '
+                '"%(table_name)s" in database "%(db_name)s". '
+                "Error message: %(error_msg)s",
+                filename=form.excel_file.data.filename,
+                table_name=form.name.data,
+                db_name=database.database_name,
+                error_msg=str(ex),
+            )
+
+            flash(message, "danger")
+            stats_logger.incr("failed_excel_upload")
+            return redirect("/exceltodatabaseview/form")
+
+        os.remove(uploaded_tmp_file_path)
+        # Go back to welcome page / splash screen
+        message = _(
+            'CSV file "%(excel_filename)s" uploaded to table "%(table_name)s" 
in '
+            'database "%(db_name)s"',
+            excel_filename=form.excel_file.data.filename,
+            table_name=str(excel_table),
+            db_name=sqla_table.database.database_name,
+        )
+        flash(message, "info")
+        stats_logger.incr("successful_excel_upload")
+        return redirect("/tablemodelview/list/")

Reply via email to