fix: Refactor Data Import

- Break Importer into classes ImportFile, Row, Column, Header
- Show warnings section before import preview
This commit is contained in:
Faris Ansari 2020-05-28 23:16:46 +05:30
parent 806c2ac0b6
commit 429ff2ef87
10 changed files with 1078 additions and 939 deletions

View file

@ -24,7 +24,7 @@ user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
"has_role:Page", "has_role:Report")
doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified",
"linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map')
"linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map', 'data_import_column_header_map')
def clear_user_cache(user=None):

View file

@ -0,0 +1,3 @@
.warnings .warning {
margin-bottom: 40px;
}

View file

@ -57,7 +57,7 @@ frappe.ui.form.on('Data Import Beta', {
frm.set_query('reference_doctype', () => {
return {
filters: {
allow_import: 1
name: ['in', frappe.boot.user.can_import]
}
};
});
@ -236,7 +236,7 @@ frappe.ui.form.on('Data Import Beta', {
frm
.call({
method: 'get_preview_from_template',
args: { data_import: frm.doc.name },
args: { data_import: frm.doc.name, import_file: frm.doc.import_file },
error_handlers: {
TimestampMismatchError() {
// ignore this error
@ -331,8 +331,8 @@ frappe.ui.form.on('Data Import Beta', {
})
.join('');
return `
<div class="alert border" data-row="${row_number}">
<div class="uppercase">${__('Row {0}', [row_number])}</div>
<div class="warning" data-row="${row_number}">
<h5 class="text-uppercase">${__('Row {0}', [row_number])}</h5>
<div class="body"><ul>${message}</ul></div>
</div>
`;
@ -346,8 +346,8 @@ frappe.ui.form.on('Data Import Beta', {
header = __('Column {0}', [warning.col]);
}
return `
<div class="alert border" data-col="${warning.col}">
<div class="uppercase">${header}</div>
<div class="warning" data-col="${warning.col}">
<h5 class="text-uppercase">${header}</h5>
<div class="body">${warning.message}</div>
</div>
`;
@ -355,7 +355,7 @@ frappe.ui.form.on('Data Import Beta', {
.join('');
frm.get_field('import_warnings').$wrapper.html(`
<div class="row">
<div class="col-sm-6 warnings text-muted">${html}</div>
<div class="col-sm-10 warnings">${html}</div>
</div>
`);
},

View file

@ -16,11 +16,11 @@
"submit_after_import",
"mute_emails",
"template_options",
"section_import_preview",
"import_preview",
"import_warnings_section",
"template_warnings",
"import_warnings",
"section_import_preview",
"import_preview",
"import_log_section",
"import_log",
"show_failed_logs",
@ -34,7 +34,9 @@
"label": "Document Type",
"options": "DocType",
"reqd": 1,
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_type",
@ -43,28 +45,38 @@
"label": "Import Type",
"options": "\nInsert New Records\nUpdate Existing Records",
"reqd": 1,
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "import_file",
"fieldtype": "Attach",
"in_list_view": 1,
"label": "Import File"
"label": "Import File",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_preview",
"fieldtype": "HTML",
"label": "Import Preview"
"label": "Import Preview",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_import_preview",
"fieldtype": "Section Break",
"label": "Preview"
"label": "Preview",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "template_options",
@ -72,23 +84,31 @@
"hidden": 1,
"label": "Template Options",
"options": "JSON",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_log",
"fieldtype": "Code",
"label": "Import Log",
"options": "JSON"
"options": "JSON",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_log_section",
"fieldtype": "Section Break",
"label": "Import Log"
"label": "Import Log",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_log_preview",
"fieldtype": "HTML",
"label": "Import Log Preview"
"label": "Import Log Preview",
"show_days": 1,
"show_seconds": 1
},
{
"default": "Pending",
@ -97,56 +117,72 @@
"hidden": 1,
"label": "Status",
"options": "Pending\nSuccess\nPartial Success\nError",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "template_warnings",
"fieldtype": "Code",
"hidden": 1,
"label": "Template Warnings",
"options": "JSON"
"options": "JSON",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "submit_after_import",
"fieldtype": "Check",
"label": "Submit After Import",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_warnings_section",
"fieldtype": "Section Break",
"label": "Warnings"
"label": "Warnings",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "import_warnings",
"fieldtype": "HTML",
"label": "Import Warnings"
"label": "Import Warnings",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "reference_doctype",
"fieldname": "download_template",
"fieldtype": "Button",
"label": "Download Template"
"label": "Download Template",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"fieldname": "mute_emails",
"fieldtype": "Check",
"label": "Don't Send Emails",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "show_failed_logs",
"fieldtype": "Check",
"label": "Show Failed Logs"
"label": "Show Failed Logs",
"show_days": 1,
"show_seconds": 1
}
],
"hide_toolbar": 1,
"links": [],
"modified": "2020-02-17 15:35:04.386098",
"modified_by": "faris@erpnext.com",
"modified": "2020-05-28 22:11:38.266208",
"modified_by": "Administrator",
"module": "Core",
"name": "Data Import Beta",
"owner": "Administrator",

View file

@ -5,8 +5,9 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.core.doctype.data_import.importer_new import Importer
from frappe.core.doctype.data_import.exporter_new import Exporter
from frappe.core.doctype.data_import_beta.importer import Importer
from frappe.core.doctype.data_import_beta.exporter import Exporter
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.background_jobs import enqueue
from frappe import _
@ -25,7 +26,10 @@ class DataImportBeta(Document):
# validate template
self.get_importer()
def get_preview_from_template(self):
def get_preview_from_template(self, import_file=None):
if import_file:
self.import_file = import_file
if not self.import_file:
return
@ -62,8 +66,8 @@ class DataImportBeta(Document):
@frappe.whitelist()
def get_preview_from_template(data_import):
return frappe.get_doc("Data Import Beta", data_import).get_preview_from_template()
def get_preview_from_template(data_import, import_file):
return frappe.get_doc("Data Import Beta", data_import).get_preview_from_template(import_file)
@frappe.whitelist()
@ -81,8 +85,8 @@ def start_import(data_import):
frappe.db.rollback()
data_import.db_set("status", "Error")
frappe.log_error(title=data_import.name)
frappe.db.commit()
frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name})
frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name})
@frappe.whitelist()

File diff suppressed because it is too large Load diff

View file

@ -202,16 +202,16 @@ frappe.data_import.DataExporter = class DataExporter {
}
select_mandatory() {
let mandatory_table_doctypes = frappe.meta
let mandatory_table_fields = frappe.meta
.get_table_fields(this.doctype)
.filter(df => df.reqd)
.map(df => df.options);
mandatory_table_doctypes.push(this.doctype);
.map(df => df.fieldname);
mandatory_table_fields.push(this.doctype);
let multicheck_fields = this.dialog.fields
.filter(df => df.fieldtype === 'MultiCheck')
.map(df => df.fieldname)
.filter(doctype => mandatory_table_doctypes.includes(doctype));
.filter(doctype => mandatory_table_fields.includes(doctype));
let checkboxes = [].concat(
...multicheck_fields.map(fieldname => {
@ -333,16 +333,24 @@ frappe.data_import.DataExporter = class DataExporter {
}
};
function get_columns_for_picker(doctype) {
export function get_columns_for_picker(doctype) {
let out = {};
const standard_fields_filter = df =>
!in_list(frappe.model.no_value_type, df.fieldtype);
const exportable_fields = df => {
let keep = true;
if (frappe.model.no_value_type.includes(df.fieldtype)) {
keep = false;
}
if (['lft', 'rgt'].includes(df.fieldname)) {
keep = false;
}
return keep;
};
// parent
let doctype_fields = frappe.meta
.get_docfields(doctype)
.filter(standard_fields_filter);
.filter(exportable_fields);
out[doctype] = [
{
@ -359,7 +367,7 @@ function get_columns_for_picker(doctype) {
const cdt = df.options;
const child_table_fields = frappe.meta
.get_docfields(cdt)
.filter(standard_fields_filter);
.filter(exportable_fields);
out[df.fieldname] = [
{

View file

@ -1,5 +1,5 @@
import DataTable from 'frappe-datatable';
import ColumnPickerFields from './column_picker_fields';
import { get_columns_for_picker } from './data_exporter';
frappe.provide('frappe.data_import');
@ -236,9 +236,7 @@ frappe.data_import.ImportPreview = class ImportPreview {
}
show_column_mapper() {
let column_picker_fields = new ColumnPickerFields({
doctype: this.doctype
});
let column_picker_fields = get_columns_for_picker(this.doctype);
let changed = [];
let fields = this.preview_data.columns.map((col, i) => {
let df = col.df;

View file

@ -249,6 +249,7 @@
}
.progress-message {
font-feature-settings: "tnum" 1;
margin-top: 0px;
}
}
@ -1011,7 +1012,7 @@ body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
.map-columns .form-section {
padding: 0 7px 7px;
border-bottom: none;
border-top: none;
.clearfix {
display: none;
@ -1021,3 +1022,7 @@ body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
.map-columns .form-section:first-child {
padding-top: 7px;
}
.table-preview {
margin-top: 12px;
}

View file

@ -1185,3 +1185,75 @@ def is_subset(list_a, list_b):
def generate_hash(*args, **kwargs):
return frappe.generate_hash(*args, **kwargs)
def guess_date_format(date_string):
DATE_FORMATS = [
r"%d-%m-%Y",
r"%m-%d-%Y",
r"%Y-%m-%d",
r"%d-%m-%y",
r"%m-%d-%y",
r"%y-%m-%d",
r"%d/%m/%Y",
r"%m/%d/%Y",
r"%Y/%m/%d",
r"%d/%m/%y",
r"%m/%d/%y",
r"%y/%m/%d",
r"%d.%m.%Y",
r"%m.%d.%Y",
r"%Y.%m.%d",
r"%d.%m.%y",
r"%m.%d.%y",
r"%y.%m.%d",
]
TIME_FORMATS = [
r"%H:%M:%S.%f",
r"%H:%M:%S",
r"%H:%M",
r"%I:%M:%S.%f %p",
r"%I:%M:%S %p",
r"%I:%M %p",
]
date_string = date_string.strip()
_date = None
_time = None
if " " in date_string:
_date, _time = date_string.split(" ", 1)
else:
_date = date_string
date_format = None
time_format = None
for f in DATE_FORMATS:
try:
# if date is parsed without any exception
# capture the date format
datetime.datetime.strptime(_date, f)
date_format = f
break
except ValueError:
pass
if _time:
for f in TIME_FORMATS:
try:
# if time is parsed without any exception
# capture the time format
datetime.datetime.strptime(_time, f)
time_format = f
break
except ValueError:
pass
full_format = date_format
if time_format:
full_format += " " + time_format
return full_format