summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/data_import.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/data_import.js')
-rw-r--r--addons/web/static/src/js/data_import.js289
1 files changed, 289 insertions, 0 deletions
diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js
new file mode 100644
index 00000000000..9c686846751
--- /dev/null
+++ b/addons/web/static/src/js/data_import.js
@@ -0,0 +1,289 @@
+openerp.web.data_import = function(openerp) {
+var QWeb = openerp.web.qweb;
+/**
+ * Safari does not deal well at all with raw JSON data being returned. As a
+ * result, we're going to cheat by using a pseudo-jsonp: instead of getting
+ * JSON data in the iframe, we're getting a ``script`` tag which consists of a
+ * function call and the returned data (the json dump).
+ *
+ * The function is an auto-generated name bound to ``window``, which calls
+ * back into the callback provided here.
+ *
+ * @param {Object} form the form element (DOM or jQuery) to use in the call
+ * @param {Object} attributes jquery.form attributes object
+ * @param {Function} callback function to call with the returned data
+ */
+function jsonp(form, attributes, callback) {
+ attributes = attributes || {};
+ var options = {jsonp: _.uniqueId('import_callback_')};
+ window[options.jsonp] = function () {
+ delete window[options.jsonp];
+ callback.apply(null, arguments);
+ };
+ if ('data' in attributes) {
+ _.extend(attributes.data, options);
+ } else {
+ _.extend(attributes, {data: options});
+ }
+ $(form).ajaxSubmit(attributes);
+}
+
+openerp.web.DataImport = openerp.web.Dialog.extend({
+ template: 'ImportDataView',
+ dialog_title: "Import Data",
+ init: function(parent, dataset){
+ var self = this;
+ this._super(parent, {});
+ this.model = parent.model;
+ this.fields = [];
+ this.all_fields = [];
+ this.required_fields = null;
+
+ var convert_fields = function (root, prefix) {
+ prefix = prefix || '';
+ _(root.fields).each(function (f) {
+ self.all_fields.push(prefix + f.name);
+ if (f.fields) {
+ convert_fields(f, prefix + f.id + '/');
+ }
+ });
+ };
+ this.ready = $.Deferred.queue().then(function () {
+ self.required_fields = _(self.fields).chain()
+ .filter(function (field) { return field.required; })
+ .pluck('name')
+ .value();
+ convert_fields(self);
+ self.all_fields.sort();
+ });
+ },
+ start: function() {
+ var self = this;
+ this._super();
+ this.open({
+ modal: true,
+ width: '70%',
+ height: 'auto',
+ position: 'top',
+ buttons: [
+ {text: "Close", click: function() { self.stop(); }},
+ {text: "Import File", click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
+ ],
+ close: function(event, ui) {
+ self.stop();
+ }
+ });
+ this.toggle_import_button(false);
+ this.$element.find('#csvfile').change(this.on_autodetect_data);
+ this.$element.find('fieldset').change(this.on_autodetect_data);
+ this.$element.find('fieldset legend').click(function() {
+ $(this).next().toggle();
+ });
+ this.ready.push(new openerp.web.DataSet(this, this.model).call(
+ 'fields_get', [], function (fields) {
+ self.graft_fields(fields);
+ }));
+ },
+ graft_fields: function (fields, parent, level) {
+ parent = parent || this;
+ level = level || 0;
+
+ var self = this;
+ _(fields).each(function (field, field_name) {
+ var f = {
+ id: field_name,
+ name: field_name,
+ string: field.string,
+ required: field.required
+ };
+
+ switch (field.type) {
+ case 'many2many':
+ case 'many2one':
+ f.name += '/id';
+ break;
+ case 'one2many':
+ f.name += '/id';
+ f.fields = [];
+ // only fetch sub-fields to a depth of 2 levels
+ if (level < 2) {
+ self.ready.push(new openerp.web.DataSet(self, field.relation).call(
+ 'fields_get', [], function (fields) {
+ self.graft_fields(fields, f, level+1);
+ }));
+ }
+ break;
+ }
+ parent.fields.push(f);
+ });
+ },
+ toggle_import_button: function (newstate) {
+ this.$dialog.dialog('widget')
+ .find('.oe-dialog-import-button')
+ .button('option', 'disabled', !newstate);
+ },
+ do_import: function() {
+ if(!this.$element.find('#csvfile').val()) { return; }
+ var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10);
+ var with_headers = this.$element.find('#file_has_headers').prop('checked');
+ if (!lines_to_skip && with_headers) {
+ lines_to_skip = 1;
+ }
+ var indices = [], fields = [];
+ this.$element.find(".sel_fields").each(function(index, element) {
+ var val = element.value;
+ if (!val) {
+ return;
+ }
+ indices.push(index);
+ fields.push(val);
+ });
+
+ jsonp(this.$element.find('#import_data'), {
+ url: '/web/import/import_data',
+ data: {
+ model: this.model,
+ meta: JSON.stringify({
+ skip: lines_to_skip,
+ indices: indices,
+ fields: fields
+ })
+ }
+ }, this.on_import_results);
+ },
+ on_autodetect_data: function() {
+ if(!this.$element.find('#csvfile').val()) { return; }
+ jsonp(this.$element.find('#import_data'), {
+ url: '/web/import/detect_data'
+ }, this.on_import_results);
+ },
+ on_import_results: function(results) {
+ this.$element.find('#result').empty();
+ var headers, result_node = this.$element.find("#result");
+
+ if (results['records']) {
+ var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10),
+ with_headers = this.$element.find('#file_has_headers').prop('checked');
+ headers = with_headers ? results.records[0] : null;
+
+ result_node.append(QWeb.render('ImportView.result', {
+ 'headers': headers,
+ 'records': lines_to_skip ? results.records.slice(lines_to_skip)
+ : with_headers ? results.records.slice(1)
+ : results.records
+ }));
+ } else if (results['error']) {
+ result_node.append(QWeb.render('ImportView.error', {
+ 'error': results['error']}));
+ } else if (results['success']) {
+ if (this.widget_parent.widget_parent.active_view == "list") {
+ this.widget_parent.reload_content();
+ }
+ this.stop();
+ return;
+ }
+
+ var self = this;
+ this.ready.then(function () {
+ var $fields = self.$element.find('.sel_fields').bind('blur', function () {
+ if (this.value && !_(self.all_fields).contains(this.value)) {
+ this.value = '';
+ }
+ }).autocomplete({
+ minLength: 0,
+ source: self.all_fields,
+ change: self.on_check_field_values
+ }).focus(function () {
+ $(this).autocomplete('search');
+ });
+ // Column auto-detection
+ _(headers).each(function (header, index) {
+ var f =_(self.fields).detect(function (field) {
+ // TODO: levenshtein between header and field.string
+ return field.name === header || field.string.toLowerCase() === header;
+ });
+ if (f) {
+ $fields.eq(index).val(f.name);
+ }
+ });
+ self.on_check_field_values();
+ });
+ },
+ /**
+ * Looks through all the field selections, and tries to find if two
+ * (or more) columns were matched to the same model field.
+ *
+ * Returns a map of the multiply-mapped fields to an array of offending
+ * columns (not actually columns, but the inputs containing the same field
+ * names).
+ *
+ * Also has the side-effect of marking the discovered inputs with the class
+ * ``duplicate_fld``.
+ *
+ * @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs
+ */
+ find_duplicate_fields: function() {
+ // Maps values to DOM nodes, in order to discover duplicates
+ var values = {}, duplicates = {};
+ this.$element.find(".sel_fields").each(function(index, element) {
+ var value = element.value;
+ var $element = $(element).removeClass('duplicate_fld');
+ if (!value) { return; }
+
+ if (!(value in values)) {
+ values[value] = element;
+ } else {
+ var same_valued_field = values[value];
+ if (value in duplicates) {
+ duplicates[value].push(element);
+ } else {
+ duplicates[value] = [same_valued_field, element];
+ }
+ $element.add(same_valued_field).addClass('duplicate_fld');
+ }
+ });
+ return duplicates;
+ },
+ on_check_field_values: function () {
+ this.$element.find("#message, #msg").remove();
+
+ var required_valid = this.check_required();
+
+ var duplicates = this.find_duplicate_fields();
+ if (_.isEmpty(duplicates)) {
+ this.toggle_import_button(required_valid);
+ } else {
+ var $err = $('<div id="msg" style="color: red;">Destination fields should only be selected once, some fields are selected more than once:</div>').insertBefore(this.$element.find('#result'));
+ var $dupes = $('<dl>').appendTo($err);
+ _(duplicates).each(function(elements, value) {
+ $('<dt>').text(value).appendTo($dupes);
+ _(elements).each(function(element) {
+ var cell = $(element).closest('td');
+ $('<dd>').text(cell.parent().children().index(cell)).appendTo($dupes);
+ });
+ });
+ this.toggle_import_button(false);
+ }
+
+ },
+ check_required: function() {
+ if (!this.required_fields.length) { return true; }
+
+ var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
+ .pluck('value')
+ .compact()
+ .value();
+
+ var missing_fields = _.difference(this.required_fields, selected_fields);
+ if (missing_fields.length) {
+ this.$element.find("#result").before('<div id="message" style="color:red">*Required Fields are not selected : ' + missing_fields + '.</div>');
+ return false;
+ }
+ return true;
+ },
+ stop: function() {
+ $(this.$dialog).remove();
+ this._super();
+ }
+});
+};