diff options
| author | Vidhin Mehta (OpenERP) <vme@tinyerp.com> | 2012-09-13 12:04:49 +0530 |
|---|---|---|
| committer | Vidhin Mehta (OpenERP) <vme@tinyerp.com> | 2012-09-13 12:04:49 +0530 |
| commit | dc97de0e27bb1ce5d1e1ef44f4b09b48764a01f2 (patch) | |
| tree | 6b6d7763cc79163175edbe96b455a8577c0b5c9b /addons/base_import/static/src | |
| parent | 881b1ed7cc4396ded4937975be110d1ac59b0fa0 (diff) | |
| parent | 74df8d0147b79a61fcf961231be8b82a5f86ef99 (diff) | |
[MERGE]TRUNk.
bzr revid: vme@tinyerp.com-20120913063449-t6hftj16o25onju0
Diffstat (limited to 'addons/base_import/static/src')
| -rw-r--r-- | addons/base_import/static/src/css/import.css | 57 | ||||
| -rw-r--r-- | addons/base_import/static/src/js/import.js | 291 | ||||
| -rw-r--r-- | addons/base_import/static/src/xml/import.xml | 103 |
3 files changed, 451 insertions, 0 deletions
diff --git a/addons/base_import/static/src/css/import.css b/addons/base_import/static/src/css/import.css new file mode 100644 index 00000000000..f6258f5aa50 --- /dev/null +++ b/addons/base_import/static/src/css/import.css @@ -0,0 +1,57 @@ +.openerp .oe_list_buttons .oe_alternative { + visibility: visible; +} +.openerp .oe_list_buttons.oe_editing .oe_list_button_import { + display: none; +} + +.oe_import dd, +.oe_import .oe_import_toggled, +.oe_import .oe_import_grid, +.oe_import .oe_import_error_report, +.oe_import .oe_import_with_file, +.oe_import .oe_import_noheaders { + display: none; +} + +.oe_import.oe_import_preview .oe_import_grid { + display: table; +} +.oe_import.oe_import_error .oe_import_error_report, +.oe_import.oe_import_with_file .oe_import_with_file, +.oe_import.oe_import_noheaders .oe_import_noheaders { + display: block; +} + +.oe_import .oe_import_error_report ul .oe_import_report_error { + background-color: #FFD9DB; +} +.oe_import .oe_import_error_report ul .oe_import_report_warning { + background-color: #FEFFD9; +} + +.oe_import .oe_import_noheaders { + color: #888; +} + +.oe_import a.oe_import_toggle { + display: block; +} +.oe_import a.oe_import_toggle:before { + content: '> ' +} + +.oe_import .oe_import_options p { + margin: 0; + padding: 0; +} +.oe_import .oe_import_options label { + display: inline-block; + width: 10em; + text-align: right; +} + +.oe_import_selector ul, +.oe_import_selector li { + margin: 0; padding: 0; +} diff --git a/addons/base_import/static/src/js/import.js b/addons/base_import/static/src/js/import.js new file mode 100644 index 00000000000..c2deb660ff2 --- /dev/null +++ b/addons/base_import/static/src/js/import.js @@ -0,0 +1,291 @@ +openerp.base_import = function (instance) { + var QWeb = instance.web.qweb; + var _t = instance.web._t; + var _lt = instance.web._lt; + + /** + * 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}); + } + _.extend(attributes, { + dataType: 'script', + }); + $(form).ajaxSubmit(attributes); + } + + // if true, the 'Import', 'Export', etc... buttons will be shown + instance.web.ListView.prototype.defaults.import_enabled = true; + instance.web.ListView.include({ + on_loaded: function () { + var self = this; + var add_button = false; + if (!this.$buttons) { + add_button = true; + } + this._super.apply(this, arguments); + if(add_button) { + this.$buttons.on('click', '.oe_list_button_import', function() { + new instance.web.DataImport(self, self.dataset).open(); + return false; + }); + } + } + }); + + instance.web.DataImport = instance.web.Dialog.extend({ + template: 'ImportView', + dialog_title: _lt("Import Data"), + opts: [ + {name: 'encoding', label: _lt("Encoding:"), value: 'utf-8'}, + {name: 'separator', label: _lt("Separator:"), value: ','}, + {name: 'quoting', label: _lt("Quoting:"), value: '"'} + ], + events: { + 'change .oe_import_grid input': 'import_dryrun', + 'change input.oe_import_file': 'file_update', + 'change input.oe_import_has_header, .oe_import_options input': 'settings_updated', + 'click a.oe_import_csv': function (e) { + e.preventDefault(); + }, + 'click a.oe_import_export': function (e) { + e.preventDefault(); + }, + 'click a.oe_import_toggle': function (e) { + e.preventDefault(); + var $el = $(e.target); + ($el.next().length + ? $el.next() + : $el.parent().next()) + .toggle(); + } + }, + init: function (parent, dataset) { + var self = this; + this._super(parent, { + buttons: [ + {text: _t("Import File"), click: function () { + self.do_import(); + }, 'class': 'oe_import_dialog_button'} + ] + }); + this.res_model = parent.model; + // import object id + this.id = null; + this.Import = new instance.web.Model('base_import.import'); + }, + start: function () { + var self = this; + return this.Import.call('create', [{ + 'res_model': this.res_model + }]).then(function (id) { + self.id = id; + self.$('input[name=import_id]').val(id); + }); + }, + + import_options: function () { + var self = this; + var options = { + headers: this.$('input.oe_import_has_header').prop('checked') + }; + _(this.opts).each(function (opt) { + options[opt.name] = + self.$('input.oe_import_' + opt.name).val(); + }); + return options; + }, + + //- File & settings change section + file_update: function (e) { + if (!this.$('input.oe_import_file').val()) { return; } + + this.$el.removeClass('oe_import_preview oe_import_error'); + jsonp(this.$el, { + url: '/base_import/set_file' + }, this.proxy('settings_updated')); + }, + settings_updated: function () { + this.$el.addClass('oe_import_with_file'); + // TODO: test that write // succeeded? + this.Import.call( + 'parse_preview', [this.id, this.import_options()]) + .then(this.proxy('preview')); + }, + preview: function (result) { + this.$el.toggleClass( + 'oe_import_noheaders', + !this.$('input.oe_import_has_header').prop('checked')); + if (result.error) { + this.$el.addClass('oe_import_error'); + this.$('.oe_import_error_report').html( + QWeb.render('ImportView.preview.error', result)); + return; + } + this.$el.addClass('oe_import_preview'); + this.$('table').html(QWeb.render('ImportView.preview', result)); + + var $fields = this.$('.oe_import_fields input'); + this.render_fields_matches(result, $fields); + var data = this.generate_fields_completion(result); + var item_finder = function (id, items) { + items = items || data; + for (var i=0; i < items.length; ++i) { + var item = items[i]; + if (item.id === id) { + return item; + } + var val; + if (item.children && (val = item_finder(id, item.children))) { + return val; + } + } + return ''; + }; + $fields.select2({ + allowClear: true, + minimumInputLength: 0, + data: data, + initSelection: function (element, callback) { + var default_value = element.val(); + if (!default_value) { + callback(''); + return; + } + + callback(item_finder(default_value)); + }, + + width: 'resolve', + dropdownCssClass: 'oe_import_selector' + }); + this.import_dryrun(); + }, + generate_fields_completion: function (root) { + var basic = []; + var regulars = []; + var o2m = []; + function traverse(field, ancestors, collection) { + var subfields = field.fields; + var field_path = ancestors.concat(field); + var label = _(field_path).pluck('string').join(' / '); + var id = _(field_path).pluck('name').join('/'); + + // If non-relational, m2o or m2m, collection is regulars + if (!collection) { + if (field.name === 'id') { + collection = basic + } else if (_.isEmpty(subfields) + || _.isEqual(_.pluck(subfields, 'name'), ['id', '.id'])) { + collection = regulars; + } else { + collection = o2m; + } + } + + collection.push({ + id: id, + text: label, + required: field.required + }); + + for(var i=0, end=subfields.length; i<end; ++i) { + traverse(subfields[i], field_path, collection); + } + } + _(root.fields).each(function (field) { + traverse(field, []); + }); + + var cmp = function (field1, field2) { + return field1.text.localeCompare(field2.text); + + }; + regulars.sort(cmp); + o2m.sort(cmp); + return basic.concat([ + { text: _t("Normal Fields"), children: regulars }, + { text: _t("Relation Fields"), children: o2m } + ]); + }, + render_fields_matches: function (result, $fields) { + if (_(result.matches).isEmpty()) { return; } + $fields.each(function (index, input) { + var match = result.matches[index]; + if (!match) { return; } + + var current_field = result; + input.value = _(match).chain() + .map(function (name) { + // WARNING: does both mapping and folding (over the + // ``field`` iterator variable) + return current_field = _(current_field.fields).find(function (subfield) { + return subfield.name === name; + }); + }) + .pluck('name') + .value() + .join('/'); + }); + }, + + //- import itself + call_import: function (options) { + var self = this; + var fields = this.$('.oe_import_fields input.oe_import_match_field').map(function (index, el) { + return $(el).select2('val') || false; + }).get(); + return this.Import.call( + 'do', [this.id, fields, this.import_options()], options); + }, + import_dryrun: function () { +// this.call_import({ dryrun: true }) +// .then(this.proxy('render_import_errors')); + }, + do_import: function () { + var self = this; + this.call_import({ dryrun: false }).then(function (errors) { + if (_.isEmpty(errors)) { + if (self.getParent().reload_content) { + self.getParent().reload_content(); + } + self.close(); + return; + } + self.render_import_errors(errors); + }); + }, + render_import_errors: function (errors) { + if (_.isEmpty(errors)) { + this.$el.removeClass('oe_import_error'); + return; + } + // import failed (or maybe just warnings, if we ever get + // warnings?) + this.$el.addClass('oe_import_error'); + this.$('.oe_import_error_report').html( + QWeb.render('ImportView.error', {errors: errors})); + }, + }); +}; diff --git a/addons/base_import/static/src/xml/import.xml b/addons/base_import/static/src/xml/import.xml new file mode 100644 index 00000000000..386348bea4b --- /dev/null +++ b/addons/base_import/static/src/xml/import.xml @@ -0,0 +1,103 @@ +<templates> + <t t-name="ImportView"> + <t t-set="_id" t-value="_.uniqueId('export')"/> + <form action="" method="post" enctype="multipart/form-data" class="oe_import"> + <input type="hidden" name="session_id" + t-att-value="widget.session.session_id"/> + <input type="hidden" name="import_id"/> + <h2>Upload your file</h2> + <p>Select the <a href="#" class="oe_import_csv">.CSV</a> + file to import. If you need a sample importable file, you + can use <a href="#" class="oe_import_export">the export + tool</a> to generate one.</p> + <label t-attf-for="file_#{_id}" autofocus="autofocus">CSV File:</label> + <input type="file" id-attf-id="file_#{_id}" + name="file" class="oe_import_file"/> + + <div class="oe_import_with_file"> + <h2>Map your data to OpenERP</h2> + <input type="checkbox" class="oe_import_has_header" + id="oe_import_has_header" checked="checked"/> + <label for="oe_import_has_header">The first row of the + file contains the label of the column</label> + <p class="oe_import_noheaders">If the file contains + the column names, OpenERP can try auto-detecting the + field corresponding to the column. This makes imports + simpler especially when the file has many columns.</p> + + <div class="oe_import_error_report"></div> + <table class="oe_import_grid" width="100%"/> + <a href="#" class="oe_import_toggle"> + File Format Options…</a> + <div class="oe_import_toggled oe_import_options"> + <p t-foreach="widget.opts" t-as="option"> + <!-- no @name, avoid submission when file_update called --> + <label t-attf-for="#{option.name}_#{_id}"> + <t t-esc="option.label"/></label> + <input t-attf-id="#{option.name}_#{_id}" + t-attf-class="oe_import_#{option.name}" + t-att-value="option.value"/> + </p> + </div> + + <h2>Frequently Asked Questions</h2> + <dl> + <dt><a href="#" class="oe_import_toggle"> + Need to import data from an other application?</a></dt> + <dd> + <p>In order to re-create relationships between + different records, you should use the unique + identifier from the original application and + map it to the <abbr title="External ID">ID</abbr> + column in OpenERP. When you + import an other record that links to the first + one, use <abbr title="XXX/External ID">XXX/ID</abbr> + to the original unique identifier.</p> + <p>The <abbr title="External ID">ID</abbr> + will also be used to update the original + import if you need to re-import modified data + later, it's thus good practice to specify it + whenever possible</p> + </dd> + </dl> + </div> + </form> + </t> + <t t-name="ImportView.preview"> + <tr t-if="headers" class="oe_import_grid-header"> + <td t-foreach="headers" t-as="header" class="oe_import_grid-cell" + ><t t-esc="header"/></td> + </tr> + <tr class="oe_import_fields"> + <!-- Iterate on first row to ensure we have all columns --> + <td t-foreach="preview[0]" t-as="column"> + <input placeholder="Don't Import" + class="oe_import_match_field"/> + </td> + </tr> + <tr t-foreach="preview" t-as="row" class="oe_import_grid-row"> + <td t-foreach="row" t-as="cell" class="oe_import_grid-cell" + ><t t-esc="cell"/></td> + </tr> + </t> + <t t-name="ImportView.preview.error"> + <p>Import preview failed due to: <t t-esc="error"/></p> + <p>Here is the start of the file we could not import:</p> + <pre><t t-esc="preview"/></pre> + </t> + <ul t-name="ImportView.error"> + <li t-foreach="errors" t-as="error" t-attf-class="oe_import_report_#{error.type}"> + <!-- can also have error.record, but may be *huge* if + e.g. has image fields --> + <t t-esc="error.message"/> + </li> + </ul> + <t t-extend="ListView.buttons"> + <t t-jquery="span.oe_alternative"> + this.attr('t-if', 'widget.options.import_enabled'); + </t> + <t t-jquery="span.oe_alternative" t-operation="append"> + <a href="#" class="oe_bold oe_list_button_import">Import</a> + </t> + </t> +</templates> |
