[feature] select columns in data import tool

This commit is contained in:
Rushabh Mehta 2016-08-11 13:26:51 +05:30
parent 91a3a9ea9e
commit de7e288875
12 changed files with 157 additions and 44 deletions

View file

@ -1 +0,0 @@
- Ability to add multiple sessions to users. Edit user record and edit "Simultaneous Sessions"

View file

@ -0,0 +1,2 @@
- New Feature: Ability to add multiple sessions to users. Edit **User** record and edit "Simultaneous Sessions"
- New Feature: Select columns to export and import in **Data Import Tool**

View file

@ -14,19 +14,32 @@
<br>
</div>
</div>
<div class="export-import-section hide">
<div class="row" style="max-width: 700px;">
<div class="export-import-section hide" style="max-width: 700px;">
<h4>{{ __("1. Select Columns") }}</h4>
<p>
<a class="btn btn-default btn-xs btn-select-all" style="margin-right: 7px;">
{%= __("Select All") %}</a>
<a class="btn btn-default btn-xs btn-select-mandatory" style="margin-right: 7px;">
{%= __("Select Mandatory") %}</a>
<a class="btn btn-default btn-xs btn-unselect-all">
{%= __("Unselect All") %}</a>
</p>
<div class="select-columns">
</div>
<br>
<h4>{{ __("2. Download") }}</h4>
<div class="row">
<div class="col-sm-4">
<p><a class="btn btn-default btn-sm btn-download-template">
<p><a class="btn btn-primary btn-xs btn-download-template">
{%= __("Download Blank Template") %}</a></p>
</div>
<div class="col-sm-8">
<h6 class="text-muted">{%= __("Recommended for inserting new records.") %}</h6>
</div>
</div>
<div class="row" style="max-width: 700px;">
<div class="row">
<div class="col-sm-4">
<p><a class="btn btn-default btn-sm btn-download-data">
<p><a class="btn btn-primary btn-xs btn-download-data">
{%= __("Download with Data") %}</a></p>
</div>
<div class="col-sm-8">
@ -35,12 +48,17 @@
</div>
</div>
<div>
<hr>
<hr style="margin-top: 50px;">
<h3>{%= __("Import") %}</h3>
<p class="text-muted">{%= __("Update the template and save in CSV (Comma Separate Values) format before attaching.") %}</p>
<div class="row">
<div class="col-md-6">
<br>
<h4>{{ __("1. Select File") }}</h4>
<div class="upload-area"></div>
<br>
<h4>{{ __("2. Upload") }}</h4>
<div class="checkbox">
<label>
<input type="checkbox" name="always_insert">

View file

@ -34,28 +34,65 @@ frappe.DataImportTool = Class.extend({
$(frappe.render_template("data_import_main", this)).appendTo(this.page.main);
this.select = this.page.main.find("select.doctype");
this.select_columns = this.page.main.find('.select-columns');
this.select.on("change", function() {
me.doctype = $(this).val();
me.page.main.find(".export-import-section").toggleClass(!!me.doctype);
if(me.doctype) {
me.set_btn_links();
// set button links
}
});
},
set_btn_links: function() {
var doctype = encodeURIComponent(this.doctype);
this.page.main.find(".btn-download-template").attr("href",
"/api/method/frappe.core.page.data_import_tool.exporter.get_template?"
+ "doctype=" + doctype
+ "&parent_doctype=" + doctype
+ "&with_data=No&all_doctypes=Yes");
frappe.model.with_doctype(me.doctype, function() {
me.page.main.find(".export-import-section").toggleClass(!!me.doctype);
if(me.doctype) {
this.page.main.find(".btn-download-data").attr("href",
"/api/method/frappe.core.page.data_import_tool.exporter.get_template?"
// render select columns
var doctype_list = [frappe.get_doc('DocType', me.doctype)];
frappe.meta.get_table_fields(me.doctype).forEach(function(df) {
doctype_list.push(frappe.get_doc('DocType', df.options));
});
$(frappe.render_template("data_import_tool_columns", {doctype_list: doctype_list}))
.appendTo(me.select_columns.empty());
}
});
});
this.page.main.find('.btn-select-all').on('click', function() {
me.select_columns.find('.select-column-check').prop('checked', true);
});
this.page.main.find('.btn-unselect-all').on('click', function() {
me.select_columns.find('.select-column-check').prop('checked', false);
});
this.page.main.find('.btn-select-mandatory').on('click', function() {
me.select_columns.find('.select-column-check').prop('checked', false);
me.select_columns.find('.select-column-check[data-reqd="1"]').prop('checked', true);
});
this.page.main.find(".btn-download-template").on('click', function() {
window.open(me.get_export_url(false));
});
this.page.main.find(".btn-download-data").on('click', function() {
window.open(me.get_export_url(true));
});
},
get_export_url: function(with_data) {
var doctype = this.select.val();
var columns = {};
this.select_columns.find('.select-column-check:checked').each(function() {
var _doctype = $(this).attr('data-doctype');
var _fieldname = $(this).attr('data-fieldname');
if(!columns[_doctype]) {
columns[_doctype] = [];
}
columns[_doctype].push(_fieldname);
});
return "/api/method/frappe.core.page.data_import_tool.exporter.get_template?"
+ "doctype=" + doctype
+ "&parent_doctype=" + doctype
+ "&with_data=Yes&all_doctypes=Yes");
+ "&select_columns=" + JSON.stringify(columns)
+ "&with_data="+ (with_data ? 'Yes' : 'No')+"&all_doctypes=Yes";
},
make_upload: function() {
var me = this;

View file

@ -0,0 +1,21 @@
<div style="margin: 15px 0px;">
{% for doctype in doctype_list %}
<h5 style="margin-top: 25px; margin-bottom: 5px;">{{ doctype.name }}</h5>
<div class="row">
{% for f in doctype.fields %}
{% if (frappe.model.no_value_type.indexOf(f.fieldtype)===-1) %}
<div class="col-sm-4">
<div class="checkbox" style="margin: 5px 0px;">
<label>
<input type="checkbox" class="select-column-check"
data-fieldname="{{ f.fieldname }}" data-reqd="{{ f.reqd }}"
data-doctype="{{ doctype.name }}" checked>
<small>{{ __(f.label) }}</small>
</label>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
</div>

View file

@ -3,12 +3,12 @@
from __future__ import unicode_literals
import frappe, json, os
import frappe, json
from frappe import _
import frappe.permissions
import re
from frappe.utils.csvutils import UnicodeWriter
from frappe.utils import cstr, cint, flt, formatdate, format_datetime
from frappe.utils import cstr, formatdate, format_datetime
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys
reflags = {
@ -22,8 +22,10 @@ reflags = {
}
@frappe.whitelist()
def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No"):
def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No", select_columns=None):
all_doctypes = all_doctypes=="Yes"
if select_columns:
select_columns = json.loads(select_columns);
docs_to_export = {}
if doctype:
if isinstance(doctype, basestring):
@ -84,6 +86,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
append_field_column(frappe._dict({
"fieldname": "name",
"parent": dt,
"label": "ID",
"fieldtype": "Data",
"reqd": 1,
@ -105,16 +108,27 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
column_start_end[dt].end = len(columns) + 1
def append_field_column(docfield, mandatory):
if docfield and ((mandatory and docfield.reqd) or not (mandatory or docfield.reqd)) \
and (docfield.fieldname not in ('parenttype', 'trash_reason')) and not docfield.hidden:
tablerow.append("")
fieldrow.append(docfield.fieldname)
labelrow.append(_(docfield.label))
mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
typerow.append(docfield.fieldtype)
inforow.append(getinforow(docfield))
columns.append(docfield.fieldname)
def append_field_column(docfield, for_mandatory):
if not docfield:
return
if for_mandatory and not docfield.reqd:
return
if not for_mandatory and docfield.reqd:
return
if docfield.fieldname in ('parenttype', 'trash_reason'):
return
if docfield.hidden:
return
if select_columns and docfield.fieldname not in select_columns.get(docfield.parent, []):
return
tablerow.append("")
fieldrow.append(docfield.fieldname)
labelrow.append(_(docfield.label))
mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
typerow.append(docfield.fieldtype)
inforow.append(getinforow(docfield))
columns.append(docfield.fieldname)
def append_empty_field_column():
tablerow.append("~")
@ -238,6 +252,8 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
inforow = [_('Info:'), '']
columns = [key]
build_field_columns(doctype)
if all_doctypes:
for d in child_doctypes:

View file

@ -842,6 +842,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
me.frm.attachments.remove_attachment_by_filename(me.value, function() {
me.parse_validate_and_set_in_model(null);
me.refresh();
me.frm.save();
});
} else {
this.dataurl = null;
@ -918,6 +919,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
if(selected) {
me.parse_validate_and_set_in_model(selected);
me.dialog.hide();
me.frm.save();
} else {
msgprint(__("Please attach a file or set a URL"));
}
@ -988,6 +990,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
this.parse_validate_and_set_in_model(attachment.file_url);
this.refresh();
this.frm.attachments.update_attachment(attachment);
this.frm.save();
} else {
this.value = this.get_value();
this.refresh();

View file

@ -64,6 +64,7 @@ frappe.form.formatters = {
},
Link: function(value, docfield, options, doc) {
var doctype = docfield._options || docfield.options;
var original_value = value;
if(value && value.match(/^['"].*['"]$/)) {
value.replace(/^.(.*).$/, "$1");
}
@ -85,7 +86,7 @@ frappe.form.formatters = {
} else if(docfield && doctype) {
return repl('<a class="grey" href="#Form/%(doctype)s/%(name)s" data-doctype="%(doctype)s">%(label)s</a>', {
doctype: encodeURIComponent(doctype),
name: encodeURIComponent(value),
name: encodeURIComponent(original_value),
label: __(options && options.label || value)
});
} else {

View file

@ -4,6 +4,15 @@
frappe.provide('frappe.utils');
frappe.utils = {
get_random: function(len) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for( var i=0; i < len; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
},
get_file_link: function(filename) {
filename = cstr(filename);
if(frappe.utils.is_url(filename)) {

View file

@ -170,7 +170,7 @@ frappe.upload = {
return;
}
var attachment = r.message;
opts.callback(attachment, r);
opts.callback && opts.callback(attachment, r);
$(document).trigger("upload_complete", attachment);
},
error: function(r) {

View file

@ -13,7 +13,7 @@ frappe.template.compile = function(str, name) {
// replace jinja style tags
str = str.replace(/{{/g, "{%=").replace(/}}/g, "%}");
// {% if not test %} --> {% if (!test) { %}
str = str.replace(/{%\s?if\s?\s?not\s?([^\(][^%{]+)\s?%}/g, "{% if (! $1) { %}")
@ -22,7 +22,13 @@ frappe.template.compile = function(str, name) {
// {% for item in list %}
// --> {% for (var i=0, len=list.length; i<len; i++) { var item = list[i]; %}
str = str.replace(/{%\s?for\s([a-z]+)\sin\s([a-z]+)\s?%}/g, "{% for (var i=0, len=$2.length; i<len; i++) { var $1 = $2[i]; %}");
function replacer(match, p1, p2, offset, string) {
var i = frappe.utils.get_random(3);
var len = frappe.utils.get_random(3);
return "{% for (var "+i+"=0, "+len+"="+p2+".length; "+i+"<"+len+"; "+i+"++) { var "
+p1+" = "+p2+"["+i+"]; %}";
}
str = str.replace(/{%\s?for\s([a-z]+)\sin\s([a-z._]+)\s?%}/g, replacer);
// {% endfor %} --> {% } %}
str = str.replace(/{%\s?endif\s?%}/g, "{% }; %}");
@ -46,7 +52,7 @@ frappe.template.compile = function(str, name) {
.split("\r").join("\\'")
+ "');}return _p.join('');";
frappe.template.debug[str] = fn_str;
frappe.template.debug[name] = fn_str;
try {
frappe.template.compiled[key] = new Function("obj", fn_str);
} catch (e) {

View file

@ -76,12 +76,13 @@ class TestDataImport(unittest.TestCase):
def test_import_with_children(self):
exporter.get_template("Event", all_doctypes="Yes", with_data="No")
content = read_csv_content(frappe.response.result)
content.append([None] * len(content[-2]))
content[-1][2] = "__Test Event"
content[-1][2] = "__Test Event with children"
content[-1][3] = "Private"
content[-1][4] = "2014-01-01 10:00:00.000000"
content[-1][content[15].index("role")] = "System Manager"
importer.upload(content)
ev = frappe.get_doc("Event", {"subject":"__Test Event"})
ev = frappe.get_doc("Event", {"subject":"__Test Event with children"})
self.assertTrue("System Manager" in [d.role for d in ev.roles])