views.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. import os
  18. import tempfile
  19. from typing import TYPE_CHECKING
  20. from flask import flash, g, redirect
  21. from flask_appbuilder import SimpleFormView
  22. from flask_appbuilder.models.sqla.interface import SQLAInterface
  23. from flask_babel import lazy_gettext as _
  24. from wtforms.fields import StringField
  25. from wtforms.validators import ValidationError
  26. import superset.models.core as models
  27. from superset import app, db
  28. from superset.connectors.sqla.models import SqlaTable
  29. from superset.constants import RouteMethod
  30. from superset.utils import core as utils
  31. from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin
  32. from .forms import CsvToDatabaseForm
  33. from .mixins import DatabaseMixin
  34. from .validators import schema_allows_csv_upload, sqlalchemy_uri_validator
  35. if TYPE_CHECKING:
  36. from werkzeug.datastructures import FileStorage # pylint: disable=unused-import
  37. config = app.config
  38. stats_logger = config["STATS_LOGGER"]
  39. def sqlalchemy_uri_form_validator(_, field: StringField) -> None:
  40. """
  41. Check if user has submitted a valid SQLAlchemy URI
  42. """
  43. sqlalchemy_uri_validator(field.data, exception=ValidationError)
  44. def upload_stream_write(form_file_field: "FileStorage", path: str):
  45. chunk_size = app.config["UPLOAD_CHUNK_SIZE"]
  46. with open(path, "bw") as file_description:
  47. while True:
  48. chunk = form_file_field.stream.read(chunk_size)
  49. if not chunk:
  50. break
  51. file_description.write(chunk)
  52. class DatabaseView(
  53. DatabaseMixin, SupersetModelView, DeleteMixin, YamlExportMixin
  54. ): # pylint: disable=too-many-ancestors
  55. datamodel = SQLAInterface(models.Database)
  56. include_route_methods = RouteMethod.CRUD_SET
  57. add_template = "superset/models/database/add.html"
  58. edit_template = "superset/models/database/edit.html"
  59. validators_columns = {"sqlalchemy_uri": [sqlalchemy_uri_form_validator]}
  60. yaml_dict_key = "databases"
  61. def _delete(self, pk):
  62. DeleteMixin._delete(self, pk)
  63. class CsvToDatabaseView(SimpleFormView):
  64. form = CsvToDatabaseForm
  65. form_template = "superset/form_view/csv_to_database_view/edit.html"
  66. form_title = _("CSV to Database configuration")
  67. add_columns = ["database", "schema", "table_name"]
  68. def form_get(self, form):
  69. form.sep.data = ","
  70. form.header.data = 0
  71. form.mangle_dupe_cols.data = True
  72. form.skipinitialspace.data = False
  73. form.skip_blank_lines.data = True
  74. form.infer_datetime_format.data = True
  75. form.decimal.data = "."
  76. form.if_exists.data = "fail"
  77. def form_post(self, form):
  78. database = form.con.data
  79. schema_name = form.schema.data or ""
  80. if not schema_allows_csv_upload(database, schema_name):
  81. message = _(
  82. 'Database "%(database_name)s" schema "%(schema_name)s" '
  83. "is not allowed for csv uploads. Please contact your Superset Admin.",
  84. database_name=database.database_name,
  85. schema_name=schema_name,
  86. )
  87. flash(message, "danger")
  88. return redirect("/csvtodatabaseview/form")
  89. csv_filename = form.csv_file.data.filename
  90. extension = os.path.splitext(csv_filename)[1].lower()
  91. path = tempfile.NamedTemporaryFile(
  92. dir=app.config["UPLOAD_FOLDER"], suffix=extension, delete=False
  93. ).name
  94. form.csv_file.data.filename = path
  95. try:
  96. utils.ensure_path_exists(config["UPLOAD_FOLDER"])
  97. upload_stream_write(form.csv_file.data, path)
  98. table_name = form.name.data
  99. con = form.data.get("con")
  100. database = (
  101. db.session.query(models.Database).filter_by(id=con.data.get("id")).one()
  102. )
  103. database.db_engine_spec.create_table_from_csv(form, database)
  104. table = (
  105. db.session.query(SqlaTable)
  106. .filter_by(
  107. table_name=table_name,
  108. schema=form.schema.data,
  109. database_id=database.id,
  110. )
  111. .one_or_none()
  112. )
  113. if table:
  114. table.fetch_metadata()
  115. if not table:
  116. table = SqlaTable(table_name=table_name)
  117. table.database = database
  118. table.database_id = database.id
  119. table.user_id = g.user.id
  120. table.schema = form.schema.data
  121. table.fetch_metadata()
  122. db.session.add(table)
  123. db.session.commit()
  124. except Exception as e: # pylint: disable=broad-except
  125. db.session.rollback()
  126. try:
  127. os.remove(path)
  128. except OSError:
  129. pass
  130. message = _(
  131. 'Unable to upload CSV file "%(filename)s" to table '
  132. '"%(table_name)s" in database "%(db_name)s". '
  133. "Error message: %(error_msg)s",
  134. filename=csv_filename,
  135. table_name=form.name.data,
  136. db_name=database.database_name,
  137. error_msg=str(e),
  138. )
  139. flash(message, "danger")
  140. stats_logger.incr("failed_csv_upload")
  141. return redirect("/csvtodatabaseview/form")
  142. os.remove(path)
  143. # Go back to welcome page / splash screen
  144. message = _(
  145. 'CSV file "%(csv_filename)s" uploaded to table "%(table_name)s" in '
  146. 'database "%(db_name)s"',
  147. csv_filename=csv_filename,
  148. table_name=form.name.data,
  149. db_name=table.database.database_name,
  150. )
  151. flash(message, "info")
  152. stats_logger.incr("successful_csv_upload")
  153. return redirect("/tablemodelview/list/")