feat: CSV File Upload form updates (Grouping with Collapse/Expand) (#21992)
This commit is contained in:
parent
b1f8fd4f64
commit
2fd0a6146e
|
|
@ -0,0 +1,75 @@
|
|||
{#
|
||||
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.
|
||||
#}
|
||||
{% macro render_delimiter_field(field, begin_sep_label='', end_sep_label='', begin_sep_field='', end_sep_field='') %}
|
||||
{% if field.id != 'csrf_token' %}
|
||||
{% if field.type == 'HiddenField' %}
|
||||
{{ field}}
|
||||
{% else %}
|
||||
{{begin_sep_label|safe}}
|
||||
<label for="{{field.id}}" control-label>
|
||||
{{ field.label.text }}
|
||||
{% if field.flags.required %}
|
||||
<strong style="color: red">*</strong>
|
||||
{% endif %}
|
||||
</label>
|
||||
{{end_sep_label|safe}}
|
||||
{{begin_sep_field|safe}}
|
||||
{{ field(**kwargs)|safe }}
|
||||
<input class="form-control col-sm-9" style="margin: 10px 0px; display: none;" id="otherInput" name="otherInput" placeholder="Type your delimiter here" type="text" value="">
|
||||
<span class="help-block">{{ field.description }}</span>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<div class="alert alert-danger">
|
||||
{% for error in field.errors %}
|
||||
{{ _(error) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{end_sep_field|safe}}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_collapsable_form_group(id, section_title='') %}
|
||||
<div class="form-group" id="{{id}}">
|
||||
<div class="col-xs-12" style="padding: 0;">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr data-toggle="collapse" data-target="#collapsable-content-{{id}}" class="accordion-toggle">
|
||||
<td class="col-xs-12" role="button" style="border: none;">
|
||||
<i class="fa fa-chevron-down" style="color: #666666; margin-right: 8px; margin-left: 12px;"></i>
|
||||
{{section_title}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="collapse" id="collapsable-content-{{id}}">
|
||||
<td colspan="12" style="padding: 0;">
|
||||
<div>
|
||||
<table class="table table-bordered" style="margin-bottom: 0; background-color: transparent; border: none;">
|
||||
<tbody>
|
||||
{{ caller() }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{#
|
||||
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.
|
||||
#}
|
||||
<script>
|
||||
$('#delimiter').on('change', function () {
|
||||
var delimiterOptions = $('#delimiter').val();
|
||||
if (delimiterOptions?.includes("other")) {
|
||||
document.getElementById("otherInput").style.display = 'block';
|
||||
$('#otherInput').attr('required', 'required');
|
||||
} else {
|
||||
document.getElementById("otherInput").style.display = 'none';
|
||||
$('#otherInput').removeAttr('required');
|
||||
}
|
||||
}).change();
|
||||
|
||||
$(".collapse").on("hide.bs.collapse show.bs.collapse", e => {
|
||||
$(e.target)
|
||||
.prev()
|
||||
.find("i:last-child")
|
||||
.toggleClass("fa-chevron-up fa-chevron-down");
|
||||
});
|
||||
</script>
|
||||
|
|
@ -16,10 +16,122 @@
|
|||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
#}
|
||||
{% extends "appbuilder/base.html" %}
|
||||
{% import 'appbuilder/general/lib.html' as lib %}
|
||||
{% set begin_sep_label = '<td class="col-sm-2" style="border-left: 0; border-top: 0;">' %}
|
||||
{% set end_sep_label = '</td>' %}
|
||||
{% set begin_sep_field = '<td style="border-right: 0; border-top: 0;">' %}
|
||||
{% set end_sep_field = '</td>' %}
|
||||
{% import 'superset/form_view/database_schemas_selector.html' as schemas_selector %}
|
||||
{% extends 'appbuilder/general/model/edit.html' %}
|
||||
|
||||
{% import 'superset/form_view/csv_scripts.html' as csv_scripts %}
|
||||
{% import 'superset/form_view/csv_macros.html' as csv_macros %}
|
||||
{% block content %}
|
||||
{{ lib.panel_begin(title, "edit") }}
|
||||
<div id="Home" class="tab-pane active">
|
||||
<form id="model_form" action="" method="post" enctype="multipart/form-data">
|
||||
{{form.hidden_tag()}}
|
||||
<div class="form-group">
|
||||
<div class="col-md-12" style="padding: 0;">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
{{ lib.render_field(form.csv_file, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.table_name, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.database, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.schema, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ csv_macros.render_delimiter_field(form.delimiter, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field) }}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% call csv_macros.render_collapsable_form_group("accordion1", "File Settings") %}
|
||||
<tr>
|
||||
{{ lib.render_field(form.if_exists, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.skip_initial_space, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.skip_blank_lines, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.parse_dates, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.infer_datetime_format, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.decimal, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.null_values, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
{% endcall %}
|
||||
{% call csv_macros.render_collapsable_form_group("accordion2", "Columns") %}
|
||||
<tr>
|
||||
{{ lib.render_field(form.index_col, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.dataframe_index, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.index_label, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.use_cols, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.overwrite_duplicate, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
{% endcall %}
|
||||
{% call csv_macros.render_collapsable_form_group("accordion3", "Rows") %}
|
||||
<tr>
|
||||
{{ lib.render_field(form.header, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field)
|
||||
}}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.nrows, begin_sep_label, end_sep_label, begin_sep_field, end_sep_field)
|
||||
}}
|
||||
</tr>
|
||||
<tr>
|
||||
{{ lib.render_field(form.skiprows, begin_sep_label, end_sep_label, begin_sep_field,
|
||||
end_sep_field) }}
|
||||
</tr>
|
||||
{% endcall %}
|
||||
<div class="form-group">
|
||||
<div class="col-xs-12" style="padding: 0;">
|
||||
{{ lib.render_form_controls() }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block add_tail_js %}
|
||||
<script src="{{url_for('appbuilder.static',filename='js/ab_keep_tab.js')}}"></script>
|
||||
{% endblock %}
|
||||
{% block tail_js %}
|
||||
{{ super() }}
|
||||
{{ schemas_selector }}
|
||||
{{ csv_scripts }}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -146,11 +146,19 @@ class CsvToDatabaseForm(UploadToDatabaseForm):
|
|||
validators=[Optional()],
|
||||
widget=BS3TextFieldWidget(),
|
||||
)
|
||||
delimiter = StringField(
|
||||
delimiter = SelectField(
|
||||
_("Delimiter"),
|
||||
description=_("Enter a delimiter for this data"),
|
||||
choices=[
|
||||
(",", _(",")),
|
||||
(".", _(".")),
|
||||
("other", _("Other")),
|
||||
],
|
||||
validators=[DataRequired()],
|
||||
widget=BS3TextFieldWidget(),
|
||||
default=[","],
|
||||
)
|
||||
otherInput = StringField(
|
||||
_("Other"),
|
||||
)
|
||||
if_exists = SelectField(
|
||||
_("If Table Already Exists"),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import io
|
|||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
import pandas as pd
|
||||
from flask import flash, g, redirect
|
||||
|
|
@ -109,7 +109,52 @@ class DatabaseView(
|
|||
return super().render_app_template()
|
||||
|
||||
|
||||
class CsvToDatabaseView(SimpleFormView):
|
||||
class CustomFormView(SimpleFormView):
|
||||
"""
|
||||
View for presenting your own forms
|
||||
Inherit from this view to provide some base
|
||||
processing for your customized form views.
|
||||
|
||||
Notice that this class inherits from BaseView
|
||||
so all properties from the parent class can be overridden also.
|
||||
|
||||
Implement form_get and form_post to implement
|
||||
your form pre-processing and post-processing
|
||||
"""
|
||||
|
||||
@expose("/form", methods=["GET"])
|
||||
@has_access
|
||||
def this_form_get(self) -> Any:
|
||||
self._init_vars()
|
||||
form = self.form.refresh()
|
||||
self.form_get(form)
|
||||
self.update_redirect()
|
||||
return self.render_template(
|
||||
self.form_template,
|
||||
title=self.form_title,
|
||||
form=form,
|
||||
appbuilder=self.appbuilder,
|
||||
)
|
||||
|
||||
@expose("/form", methods=["POST"])
|
||||
@has_access
|
||||
def this_form_post(self) -> Any:
|
||||
self._init_vars()
|
||||
form = self.form.refresh()
|
||||
if form.validate_on_submit():
|
||||
response = self.form_post(form) # pylint: disable=assignment-from-no-return
|
||||
if not response:
|
||||
return redirect(self.get_redirect())
|
||||
return response
|
||||
return self.render_template(
|
||||
self.form_template,
|
||||
title=self.form_title,
|
||||
form=form,
|
||||
appbuilder=self.appbuilder,
|
||||
)
|
||||
|
||||
|
||||
class CsvToDatabaseView(CustomFormView):
|
||||
form = CsvToDatabaseForm
|
||||
form_template = "superset/form_view/csv_to_database_view/edit.html"
|
||||
form_title = _("CSV to Database configuration")
|
||||
|
|
@ -128,6 +173,7 @@ class CsvToDatabaseView(SimpleFormView):
|
|||
def form_post(self, form: CsvToDatabaseForm) -> Response:
|
||||
database = form.database.data
|
||||
csv_table = Table(table=form.table_name.data, schema=form.schema.data)
|
||||
delimiter_input = form.delimiter.data
|
||||
|
||||
if not schema_allows_file_upload(database, csv_table.schema):
|
||||
message = __(
|
||||
|
|
@ -139,6 +185,9 @@ class CsvToDatabaseView(SimpleFormView):
|
|||
flash(message, "danger")
|
||||
return redirect("/csvtodatabaseview/form")
|
||||
|
||||
if form.delimiter.data == "other":
|
||||
delimiter_input = form.otherInput.data
|
||||
|
||||
try:
|
||||
df = pd.concat(
|
||||
pd.read_csv(
|
||||
|
|
@ -155,7 +204,7 @@ class CsvToDatabaseView(SimpleFormView):
|
|||
na_values=form.null_values.data if form.null_values.data else None,
|
||||
nrows=form.nrows.data,
|
||||
parse_dates=form.parse_dates.data,
|
||||
sep=form.delimiter.data,
|
||||
sep=delimiter_input,
|
||||
skip_blank_lines=form.skip_blank_lines.data,
|
||||
skipinitialspace=form.skip_initial_space.data,
|
||||
skiprows=form.skiprows.data,
|
||||
|
|
|
|||
Loading…
Reference in New Issue