[MERGE] base_import: second phase; update to some addons

bzr revid: xmo@openerp.com-20121010134737-u6zka0yas8sgawhz
This commit is contained in:
Xavier Morel 2012-10-10 15:47:37 +02:00
commit 24c7e25b9b
13 changed files with 878 additions and 128 deletions

View File

@ -155,6 +155,7 @@ class account_account_type(osv.osv):
return res
def _save_report_type(self, cr, uid, account_type_id, field_name, field_value, arg, context=None):
field_value = field_value or 'none'
obj_data = self.pool.get('ir.model.data')
obj_financial_report = self.pool.get('account.financial.report')
#unlink if it exists somewhere in the financial reports related to BS or PL
@ -596,10 +597,7 @@ class account_account(osv.osv):
return res
def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
if default is None:
default = {}
else:
default = default.copy()
default = {} if default is None else default.copy()
if done_list is None:
done_list = []
account = self.browse(cr, uid, id, context=context)
@ -781,10 +779,7 @@ class account_journal(osv.osv):
]
def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
if default is None:
default = {}
else:
default = default.copy()
default = {} if default is None else default.copy()
if done_list is None:
done_list = []
journal = self.browse(cr, uid, id, context=context)
@ -1185,6 +1180,7 @@ class account_fiscalyear(osv.osv):
}
def copy(self, cr, uid, id, default=None, context=None):
default = {} if default is None else default.copy()
default.update({
'period_ids': [],
'end_journal_period_id': False
@ -1444,14 +1440,8 @@ class account_move(osv.osv):
return result
def copy(self, cr, uid, id, default=None, context=None):
if context is None:
default = {}
else:
default = default.copy()
if context is None:
context = {}
else:
context = context.copy()
default = {} if default is None else default.copy()
context = {} if context is None else context.copy()
default.update({
'state':'draft',
'name':'/',

View File

@ -26,13 +26,14 @@ Re-implement openerp's file import system:
'author': 'OpenERP SA',
'depends': ['base'],
'installable': True,
'auto_install': False, # set to true and allow uninstall?
'auto_install': True,
'css': [
'static/lib/select2/select2.css',
'static/src/css/import.css',
],
'js': [
'static/lib/select2/select2.js',
'static/lib/javascript-state-machine/state-machine.js',
'static/src/js/import.js',
],
'qweb': ['static/src/xml/import.xml'],

View File

@ -83,6 +83,10 @@ class ir_import(orm.TransientModel):
}]
fields_got = self.pool[model].fields_get(cr, uid, context=context)
for name, field in fields_got.iteritems():
# an empty string means the field is deprecated, @deprecated must
# be absent or False to mean not-deprecated
if field.get('deprecated', False) is not False:
continue
if field.get('readonly'):
states = field.get('states')
if not states:
@ -97,7 +101,7 @@ class ir_import(orm.TransientModel):
'id': name,
'name': name,
'string': field['string'],
# Y U NO ALWAYS HAVE REQUIRED
# Y U NO ALWAYS HAS REQUIRED
'required': bool(field.get('required')),
'fields': [],
}
@ -124,8 +128,8 @@ class ir_import(orm.TransientModel):
"""
csv_iterator = csv.reader(
StringIO(record.file),
quotechar=options['quoting'],
delimiter=options['separator'])
quotechar=str(options['quoting']),
delimiter=str(options['separator']))
csv_nonempty = itertools.ifilter(None, csv_iterator)
# TODO: guess encoding with chardet? Or https://github.com/aadsm/jschardet
encoding = options.get('encoding', 'utf-8')
@ -307,22 +311,14 @@ class ir_import(orm.TransientModel):
except ValueError, e:
return [{
'type': 'error',
'message': str(e),
'message': unicode(e),
'record': False,
}]
try:
_logger.info('importing %d rows...', len(data))
(code, record, message, _wat) = self.pool[record.res_model].import_data(
cr, uid, import_fields, data, context=context)
_logger.info('done')
except Exception, e:
_logger.exception("Import failed")
# TODO: remove when exceptions stop being an "expected"
# behavior of import_data on some (most) invalid
# input.
code, record, message = -1, None, str(e)
_logger.info('importing %d rows...', len(data))
import_result = self.pool[record.res_model].load(
cr, uid, import_fields, data, context=context)
_logger.info('done')
# If transaction aborted, RELEASE SAVEPOINT is going to raise
# an InternalError (ROLLBACK should work, maybe). Ignore that.
@ -339,14 +335,4 @@ class ir_import(orm.TransientModel):
except psycopg2.InternalError:
pass
if code != -1:
return []
# TODO: add key for error location?
# TODO: error not within normal preview, how to display? Re-preview
# with higher ``count``?
return [{
'type': 'error',
'message': message,
'record': record or False
}]
return import_result['messages']

View File

@ -0,0 +1,20 @@
Copyright (c) 2012 Jake Gordon and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,327 @@
Javascript Finite State Machine (v2.1.0)
========================================
This standalone javascript micro-framework provides a finite state machine for your pleasure.
* You can find the [code here](https://github.com/jakesgordon/javascript-state-machine)
* You can find a [description here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
* You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/)
Download
========
You can download [state-machine.js](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.js),
or the [minified version](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.min.js)
Alternatively:
git clone git@github.com:jakesgordon/javascript-state-machine
* All code is in state-machine.js
* Minified version provided in state-machine.min.js
* No 3rd party library is required
* Demo can be found in /index.html
* QUnit tests can be found in /test/index.html
Usage
=====
Include `state-machine.min.js` in your application.
In its simplest form, create a standalone state machine using:
var fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
]});
... will create an object with a method for each event:
* fsm.warn() - transition from 'green' to 'yellow'
* fsm.panic() - transition from 'yellow' to 'red'
* fsm.calm() - transition from 'red' to 'yellow'
* fsm.clear() - transition from 'yellow' to 'green'
along with the following members:
* fsm.current - contains the current state
* fsm.is(s) - return true if state `s` is the current state
* fsm.can(e) - return true if event `e` can be fired in the current state
* fsm.cannot(e) - return true if event `e` cannot be fired in the current state
Multiple 'from' and 'to' states for a single event
==================================================
If an event is allowed **from** multiple states, and always transitions to the same
state, then simply provide an array of states in the `from` attribute of an event. However,
if an event is allowed from multiple states, but should transition **to** a different
state depending on the current state, then provide multiple event entries with
the same name:
var fsm = StateMachine.create({
initial: 'hungry',
events: [
{ name: 'eat', from: 'hungry', to: 'satisfied' },
{ name: 'eat', from: 'satisfied', to: 'full' },
{ name: 'eat', from: 'full', to: 'sick' },
{ name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' },
]});
This example will create an object with 2 event methods:
* fsm.eat()
* fsm.rest()
The `rest` event will always transition to the `hungry` state, while the `eat` event
will transition to a state that is dependent on the current state.
>> NOTE: The `rest` event could use a wildcard '*' for the 'from' state if it should be
allowed from any current state.
>> NOTE: The `rest` event in the above example can also be specified as multiple events with
the same name if you prefer the verbose approach.
Callbacks
=========
4 callbacks are available if your state machine has methods using the following naming conventions:
* onbefore**event** - fired before the event
* onleave**state** - fired when leaving the old state
* onenter**state** - fired when entering the new state
* onafter**event** - fired after the event
You can affect the event in 3 ways:
* return `false` from an `onbeforeevent` handler to cancel the event.
* return `false` from an `onleavestate` handler to cancel the event.
* return `ASYNC` from an `onleavestate` handler to perform an asynchronous state transition (see next section)
For convenience, the 2 most useful callbacks can be shortened:
* on**event** - convenience shorthand for onafter**event**
* on**state** - convenience shorthand for onenter**state**
In addition, a generic `onchangestate()` callback can be used to call a single function for _all_ state changes:
All callbacks will be passed the same arguments:
* **event** name
* **from** state
* **to** state
* _(followed by any arguments you passed into the original event method)_
Callbacks can be specified when the state machine is first created:
var fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
],
callbacks: {
onpanic: function(event, from, to, msg) { alert('panic! ' + msg); },
onclear: function(event, from, to, msg) { alert('thanks to ' + msg); },
ongreen: function(event, from, to) { document.body.className = 'green'; },
onyellow: function(event, from, to) { document.body.className = 'yellow'; },
onred: function(event, from, to) { document.body.className = 'red'; },
}
});
fsm.panic('killer bees');
fsm.clear('sedatives in the honey pots');
...
Additionally, they can be added and removed from the state machine at any time:
fsm.ongreen = null;
fsm.onyellow = null;
fsm.onred = null;
fsm.onchangestate = function(event, from, to) { document.body.className = to; };
Asynchronous State Transitions
==============================
Sometimes, you need to execute some asynchronous code during a state transition and ensure the
new state is not entered until your code has completed.
A good example of this is when you transition out of a `menu` state, perhaps you want to gradually
fade the menu away, or slide it off the screen and don't want to transition to your `game` state
until after that animation has been performed.
You can now return `StateMachine.ASYNC` from your `onleavestate` handler and the state machine
will be _'put on hold'_ until you are ready to trigger the transition using the new `transition()`
method.
For example, using jQuery effects:
var fsm = StateMachine.create({
initial: 'menu',
events: [
{ name: 'play', from: 'menu', to: 'game' },
{ name: 'quit', from: 'game', to: 'menu' }
],
callbacks: {
onentermenu: function() { $('#menu').show(); },
onentergame: function() { $('#game').show(); },
onleavemenu: function() {
$('#menu').fadeOut('fast', function() {
fsm.transition();
});
return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in fadeOut callback above)
},
onleavegame: function() {
$('#game').slideDown('slow', function() {
fsm.transition();
};
return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in slideDown callback above)
}
}
});
State Machine Classes
=====================
You can also turn all instances of a _class_ into an FSM by applying
the state machine functionality to the prototype, including your callbacks
in your prototype, and providing a `startup` event for use when constructing
instances:
MyFSM = function() { // my constructor function
this.startup();
};
MyFSM.prototype = {
onpanic: function(event, from, to) { alert('panic'); },
onclear: function(event, from, to) { alert('all is clear'); },
// my other prototype methods
};
StateMachine.create({
target: MyFSM.prototype,
events: [
{ name: 'startup', from: 'none', to: 'green' },
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
]});
This should be easy to adjust to fit your appropriate mechanism for object construction.
Initialization Options
======================
How the state machine should initialize can depend on your application requirements, so
the library provides a number of simple options.
By default, if you dont specify any initial state, the state machine will be in the `'none'`
state and you would need to provide an event to take it out of this state:
var fsm = StateMachine.create({
events: [
{ name: 'startup', from: 'none', to: 'green' },
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "none"
fsm.startup();
alert(fsm.current); // "green"
If you specify the name of your initial event (as in all the earlier examples), then an
implicit `startup` event will be created for you and fired when the state machine is constructed.
var fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "green"
If your object already has a `startup` method you can use a different name for the initial event
var fsm = StateMachine.create({
initial: { state: 'green', event: 'init' },
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "green"
Finally, if you want to wait to call the initial state transition event until a later date you
can `defer` it:
var fsm = StateMachine.create({
initial: { state: 'green', event: 'init', defer: true },
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "none"
fsm.init();
alert(fsm.current); // "green"
Of course, we have now come full circle, this last example is pretty much functionally the
same as the first example in this section where you simply define your own startup event.
So you have a number of choices available to you when initializing your state machine.
Handling Failures
======================
By default, if you try to call an event method that is not allowed in the current state, the
state machine will throw an exception. If you prefer to handle the problem yourself, you can
define a custom `error` handler:
var fsm = StateMachine.create({
initial: 'green',
error: function(eventName, from, to, args, errorCode, errorMessage) {
return 'event ' + eventName + ' was naughty :- ' + errorMessage;
},
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.calm()); // "event calm was naughty :- event not allowed in current state green"
Release Notes
=============
See [RELEASE NOTES](https://github.com/jakesgordon/javascript-state-machine/blob/master/RELEASE_NOTES.md) file.
License
=======
See [LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE) file.
Contact
=======
If you have any ideas, feedback, requests or bug reports, you can reach me at
[jake@codeincomplete.com](mailto:jake@codeincomplete.com), or via
my website: [Code inComplete](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)

View File

@ -0,0 +1,32 @@
Version 2.1.0 (January 7th 2012)
--------------------------------
* Wrapped in self executing function to be more easily used with loaders like `require.js` or `curl.js` (issue #15)
* Allow event to be cancelled by returning `false` from `onleavestate` handler (issue #13) - WARNING: this breaks backward compatibility for async transitions (you now need to return `StateMachine.ASYNC` instead of `false`)
* Added explicit return values for event methods (issue #12)
* Added support for wildcard events that can be fired 'from' any state (issue #11)
* Added support for no-op events that transition 'to' the same state (issue #5)
* extended custom error callback to handle any exceptions caused by caller provided callbacks
* added custom error callback to override exception when an illegal state transition is attempted (thanks to cboone)
* fixed typos (thanks to cboone)
* fixed issue #4 - ensure before/after event hooks are called even if the event doesn't result in a state change
Version 2.0.0 (August 19th 2011)
--------------------------------
* adding support for asynchronous state transitions (see README) - with lots of qunit tests (see test/async.js).
* consistent arguments for ALL callbacks, first 3 args are ALWAYS event name, from state and to state, followed by whatever arguments the user passed to the original event method.
* added a generic `onchangestate(event,from,to)` callback to detect all state changes with a single function.
* allow callbacks to be declared at creation time (instead of having to attach them afterwards)
* renamed 'hooks' => 'callbacks'
* [read more...](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
Version 1.2.0 (June 21st 2011)
------------------------------
* allows the same event to transition to different states, depending on the current state (see 'Multiple...' section in README.md)
* [read more...](http://codeincomplete.com/posts/2011/6/21/javascript_state_machine_v1_2_0/)
Version 1.0.0 (June 1st 2011)
-----------------------------
* initial version
* [read more...](http://codeincomplete.com/posts/2011/6/1/javascript_state_machine/)

View File

@ -0,0 +1,8 @@
desc "create minified version of state-machine.js"
task :minify do
require File.expand_path(File.join(File.dirname(__FILE__), 'minifier/minifier'))
Minifier.enabled = true
Minifier.minify('state-machine.js')
end

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Finite State Machine</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="demo/demo.css" media="screen, print" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="demo" class='green'>
<h1> Finite State Machine </h1>
<div id="controls">
<button id="clear" onclick="Demo.clear();">clear</button>
<button id="calm" onclick="Demo.calm();">calm</button>
<button id="warn" onclick="Demo.warn();">warn</button>
<button id="panic" onclick="Demo.panic();">panic!</button>
</div>
<div id="diagram">
</div>
<div id="notes">
<i>dashed lines are asynchronous state transitions (3 seconds)</i>
</div>
<textarea id="output">
</textarea>
</div>
<script src="state-machine.js"></script>
<script src="demo/demo.js"></script>
</body>
</html>

View File

@ -0,0 +1,155 @@
(function (window) {
StateMachine = {
//---------------------------------------------------------------------------
VERSION: "2.1.0",
//---------------------------------------------------------------------------
Result: {
SUCCEEDED: 1, // the event transitioned successfully from one state to another
NOTRANSITION: 2, // the event was successfull but no state transition was necessary
CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback
ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs
},
Error: {
INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
INVALID_CALLBACK: 300, // caller provided callback function threw an exception
},
WILDCARD: '*',
ASYNC: 'async',
//---------------------------------------------------------------------------
create: function(cfg, target) {
var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false }
var fsm = target || cfg.target || {};
var events = cfg.events || [];
var callbacks = cfg.callbacks || {};
var map = {};
var add = function(e) {
var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
map[e.name] = map[e.name] || {};
for (var n = 0 ; n < from.length ; n++)
map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
};
if (initial) {
initial.event = initial.event || 'startup';
add({ name: initial.event, from: 'none', to: initial.state });
}
for(var n = 0 ; n < events.length ; n++)
add(events[n]);
for(var name in map) {
if (map.hasOwnProperty(name))
fsm[name] = StateMachine.buildEvent(name, map[name]);
}
for(var name in callbacks) {
if (callbacks.hasOwnProperty(name))
fsm[name] = callbacks[name]
}
fsm.current = 'none';
fsm.is = function(state) { return this.current == state; };
fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
fsm.cannot = function(event) { return !this.can(event); };
fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3)
if (initial && !initial.defer)
fsm[initial.event]();
return fsm;
},
//===========================================================================
doCallback: function(fsm, func, name, from, to, args) {
if (func) {
try {
return func.apply(fsm, [name, from, to].concat(args));
}
catch(e) {
return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
}
}
},
beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
buildEvent: function(name, map) {
return function() {
var from = this.current;
var to = map[from] || map[StateMachine.WILDCARD] || from;
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
if (this.transition)
return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");
if (this.cannot(name))
return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
if (false === StateMachine.beforeEvent(this, name, from, to, args))
return StateMachine.CANCELLED;
if (from === to) {
StateMachine.afterEvent(this, name, from, to, args);
return StateMachine.NOTRANSITION;
}
// prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState)
var fsm = this;
this.transition = function() {
fsm.transition = null; // this method should only ever be called once
fsm.current = to;
StateMachine.enterState( fsm, name, from, to, args);
StateMachine.changeState(fsm, name, from, to, args);
StateMachine.afterEvent( fsm, name, from, to, args);
};
var leave = StateMachine.leaveState(this, name, from, to, args);
if (false === leave) {
this.transition = null;
return StateMachine.CANCELLED;
}
else if ("async" === leave) {
return StateMachine.ASYNC;
}
else {
if (this.transition)
this.transition(); // in case user manually called transition() but forgot to return ASYNC
return StateMachine.SUCCEEDED;
}
};
}
}; // StateMachine
//===========================================================================
if ("function" === typeof define) {
define("statemachine", [], function() { return StateMachine; });
}
else {
window.StateMachine = StateMachine;
}
}(this));

View File

@ -10,7 +10,8 @@
.oe_import .oe_import_grid,
.oe_import .oe_import_error_report,
.oe_import .oe_import_with_file,
.oe_import .oe_import_noheaders {
.oe_import .oe_import_noheaders,
.oe_import .oe_import_report_more {
display: none;
}
@ -19,7 +20,8 @@
}
.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 {
.oe_import.oe_import_noheaders .oe_import_noheaders,
.oe_import .oe_import_report_showmore .oe_import_report_more {
display: block;
}
@ -29,6 +31,9 @@
.oe_import .oe_import_error_report ul .oe_import_report_warning {
background-color: #FEFFD9;
}
.oe_import .oe_import_error_report ul .oe_import_report_info {
background-color: #d3ffd3;
}
.oe_import .oe_import_noheaders {
color: #888;
@ -47,7 +52,7 @@
}
.oe_import .oe_import_options label {
display: inline-block;
width: 10em;
width: 8em;
text-align: right;
}

View File

@ -47,31 +47,35 @@ openerp.base_import = function (instance) {
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();
self.do_action({
type: 'ir.actions.client',
tag: 'import',
params: {
model: self.dataset.model
}
}, void 0, void 0, function () {
self.reload();
});
return false;
});
}
}
});
instance.web.DataImport = instance.web.Dialog.extend({
instance.web.client_actions.add(
'import', 'instance.web.DataImport');
instance.web.DataImport = instance.web.Widget.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();
},
// 'change .oe_import_grid input': 'import_dryrun',
'change .oe_import_file': 'loaded_file',
'click .oe_import_file_reload': 'loaded_file',
'change input.oe_import_has_header, .oe_import_options input': 'settings_changed',
'click a.oe_import_toggle': function (e) {
e.preventDefault();
var $el = $(e.target);
@ -79,30 +83,84 @@ openerp.base_import = function (instance) {
? $el.next()
: $el.parent().next())
.toggle();
},
'click .oe_import_report a.oe_import_report_count': function (e) {
e.preventDefault();
$(e.target).parent().toggleClass('oe_import_report_showmore');
},
'click .oe_import_moreinfo_action a': function (e) {
e.preventDefault();
// #data will parse the attribute on its own, we don't like
// that sort of things
var action = JSON.parse($(e.target).attr('data-action'));
// FIXME: when JS-side clean_action
action.views = _(action.views).map(function (view) {
var id = view[0], type = view[1];
return [
id,
type !== 'tree' ? type
: action.view_type === 'form' ? 'list'
: 'tree'
];
});
this.do_action(_.extend(action, {
target: 'new',
flags: {
search_view: true,
display_title: true,
pager: true,
list: {selectable: false}
}
}));
},
// buttons
'click .oe_import_validate': 'validate',
'click .oe_import_import': 'import',
'click .oe_import_cancel': function (e) {
e.preventDefault();
this.exit();
}
},
init: function (parent, dataset) {
init: function (parent, params) {
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;
this._super.apply(this, arguments);
this.res_model = params.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);
});
this.setup_encoding_picker();
return $.when(
this._super(),
this.Import.call('create', [{
'res_model': this.res_model
}]).then(function (id) {
self.id = id;
self.$('input[name=import_id]').val(id);
})
)
},
setup_encoding_picker: function () {
this.$('input.oe_import_encoding').select2({
width: '160px',
query: function (q) {
var make = function (term) { return {id: term, text: term}; };
var suggestions = _.map(
('utf-8 utf-16 windows-1252 latin1 latin2 big5 ' +
'gb18030 shift_jis windows-1251 koir8_r').split(/\s+/),
make);
if (q.term) {
suggestions.unshift(make(q.term));
}
q.callback({results: suggestions});
},
initSelection: function (e, c) {
return c({id: 'utf-8', text: 'utf-8'});
}
}).select2('val', 'utf-8');
},
import_options: function () {
@ -118,34 +176,52 @@ openerp.base_import = function (instance) {
},
//- File & settings change section
file_update: function (e) {
onfile_loaded: function () {
this.$('.oe_import_button').prop('disabled', true);
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'));
}, this.proxy('settings_changed'));
},
settings_updated: function () {
onpreviewing: function () {
var self = this;
this.$('.oe_import_button').prop('disabled', true);
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.removeClass('oe_import_preview_error oe_import_error');
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(
this.Import.call(
'parse_preview', [this.id, this.import_options()])
.then(function (result) {
var signal = result.error ? 'preview_failed' : 'preview_succeeded';
self[signal](result);
});
},
onpreview_error: function (event, from, to, result) {
this.$('.oe_import_options').show();
this.$el.addClass('oe_import_preview_error oe_import_error');
this.$('.oe_import_error_report').html(
QWeb.render('ImportView.preview.error', result));
return;
}
},
onpreview_success: function (event, from, to, result) {
this.$('.oe_import_import').removeClass('oe_highlight');
this.$('.oe_import_validate').addClass('oe_highlight');
this.$('.oe_import_button').prop('disabled', false);
this.$el.addClass('oe_import_preview');
this.$('table').html(QWeb.render('ImportView.preview', result));
if (result.headers.length === 1) {
this.$('.oe_import_options').show();
this.onresults(null, null, null, [{
type: 'warning',
message: _t("A single column was found in the file, this often means the file separator is incorrect")
}]);
}
var $fields = this.$('.oe_import_fields input');
this.render_fields_matches(result, $fields);
var data = this.generate_fields_completion(result);
@ -180,7 +256,6 @@ openerp.base_import = function (instance) {
width: 'resolve',
dropdownCssClass: 'oe_import_selector'
});
this.import_dryrun();
},
generate_fields_completion: function (root) {
var basic = [];
@ -252,40 +327,116 @@ openerp.base_import = function (instance) {
//- 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'));
onvalidate: function () {
return this.call_import({ dryrun: true })
.then(this.proxy('validated'));
},
do_import: function () {
onimport: 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 this.call_import({ dryrun: false }).then(function (message) {
if (!_.any(message, function (message) {
return message.type === 'error' })) {
self['import_succeeded']();
return;
}
self.render_import_errors(errors);
self['import_failed'](message);
});
},
render_import_errors: function (errors) {
if (_.isEmpty(errors)) {
this.$el.removeClass('oe_import_error');
return;
onimported: function () {
this.exit();
},
exit: function () {
this.do_action({
type: 'ir.actions.client',
tag: 'history_back'
});
},
onresults: function (event, from, to, message) {
var no_messages = _.isEmpty(message);
this.$('.oe_import_import').toggleClass('oe_highlight', no_messages);
this.$('.oe_import_validate').toggleClass('oe_highlight', !no_messages);
if (no_messages) {
message.push({
type: 'info',
message: _t("Everything seems valid.")
});
}
// import failed (or maybe just warnings, if we ever get
// warnings?)
// row indexes come back 0-indexed, spreadsheets
// display 1-indexed.
var offset = 1;
// offset more if header
if (this.import_options().headers) { offset += 1; }
this.$el.addClass('oe_import_error');
this.$('.oe_import_error_report').html(
QWeb.render('ImportView.error', {errors: errors}));
QWeb.render('ImportView.error', {
errors: _(message).groupBy('message'),
at: function (rows) {
var from = rows.from + offset;
var to = rows.to + offset;
if (from === to) {
return _.str.sprintf(_t("at row %d"), from);
}
return _.str.sprintf(_t("between rows %d and %d"),
from, to);
},
more: function (n) {
return _.str.sprintf(_t("(%d more)"), n);
},
info: function (msg) {
if (typeof msg === 'string') {
return _.str.sprintf(
'<div class="oe_import_moreinfo oe_import_moreinfo_message">%s</div>',
_.str.escapeHTML(msg));
}
if (msg instanceof Array) {
return _.str.sprintf(
'<div class="oe_import_moreinfo oe_import_moreinfo_choices">%s <ul>%s</ul></div>',
_.str.escapeHTML(_t("Here are the possible values:")),
_(msg).map(function (msg) {
return '<li>'
+ _.str.escapeHTML(msg)
+ '</li>';
}).join(''));
}
// Final should be object, action descriptor
return [
'<div class="oe_import_moreinfo oe_import_moreinfo_action">',
_.str.sprintf('<a href="#" data-action="%s">',
_.str.escapeHTML(JSON.stringify(msg))),
_.str.escapeHTML(
_t("Get all possible values")),
'</a>',
'</div>'
].join('')
},
})).get(0).scrollIntoView();
},
});
// FSM-ize DataImport
StateMachine.create({
target: instance.web.DataImport.prototype,
events: [
{ name: 'loaded_file',
from: ['none', 'file_loaded', 'preview_error', 'preview_success', 'results'],
to: 'file_loaded' },
{ name: 'settings_changed',
from: ['file_loaded', 'preview_error', 'preview_success', 'results'],
to: 'previewing' },
{ name: 'preview_failed', from: 'previewing', to: 'preview_error' },
{ name: 'preview_succeeded', from: 'previewing', to: 'preview_success' },
{ name: 'validate', from: 'preview_success', to: 'validating' },
{ name: 'validate', from: 'results', to: 'validating' },
{ name: 'validated', from: 'validating', to: 'results' },
{ name: 'import', from: ['preview_success', 'results'], to: 'importing' },
{ name: 'import_succeeded', from: 'importing', to: 'imported'},
{ name: 'import_failed', from: 'importing', to: 'results' }
]
})
};

View File

@ -2,17 +2,45 @@
<t t-name="ImportView">
<t t-set="_id" t-value="_.uniqueId('export')"/>
<form action="" method="post" enctype="multipart/form-data" class="oe_import">
<header>
<button type="button" disabled="disabled"
class="oe_button oe_import_button oe_import_validate oe_highlight"
>Validate</button>
<button type="button" disabled="disabled"
class="oe_button oe_import_button oe_import_import"
>Import</button>
<span class="oe_fade">or</span>
<a class="oe_import_cancel" href="#">Cancel</a>
</header>
<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>
<p>Select the <a
href="http://en.wikipedia.org/wiki/Comma-separated_values"
class="oe_import_csv" target="_blank">.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>
can use the export tool 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"/>
<button type="button" class="oe_import_file_reload">
<img src="/web/static/src/img/icons/gtk-refresh.png"/>
</button>
<div class="oe_import_with_file">
<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>
</div>
<div class="oe_import_with_file">
<h2>Map your data to OpenERP</h2>
@ -27,18 +55,6 @@
<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>
@ -81,17 +97,37 @@
</tr>
</t>
<t t-name="ImportView.preview.error">
<p>Import preview failed due to: <t t-esc="error"/></p>
<p>Import preview failed due to: <t t-esc="error"/>. The issue is
usually an incorrect file encoding.</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 t-foreach="errors" t-as="error"
t-attf-class="oe_import_report oe_import_report_#{error_value[0].type}">
<t t-call="ImportView.error.each">
<t t-set="error" t-value="error_value[0]"/>
</t>
<a href="#" class="oe_import_report_count" t-if="error_value.length gt 1">
<t t-esc="more(error_value.length - 1)"/>
</a>
<ul class="oe_import_report_more" t-if="error_value.length gt 1">
<li t-foreach="error_value.length - 1" t-as="index">
<t t-call="ImportView.error.each">
<t t-set="error" t-value="error_value[index + 1]"/>
</t>
</li>
</ul>
</li>
</ul>
<t t-name="ImportView.error.each">
<span class="oe_import_report_message">
<t t-esc="error.message"/>
</span>
<t t-if="error.rows" t-esc="at(error.rows)"/>
<t t-if="error.moreinfo" t-raw="info(error.moreinfo)"/>
</t>
<t t-extend="ListView.buttons">
<t t-jquery="span.oe_alternative">
this.attr('t-if', 'widget.options.import_enabled');

View File

@ -10,4 +10,4 @@
"account_type_output_tax","Output Tax","Output Tax","liability","Unreconciled"
"account_type_input_tax","Input Tax","Input Tax","asset","Unreconciled"
"account_type_profit_and_loss","Profit and Loss","Profit and Loss","none","None"
"account_type_view","View","view",,"None"
"account_type_view","View","view","none","None"

1 id name code report_type close_method
10 account_type_output_tax Output Tax Output Tax liability Unreconciled
11 account_type_input_tax Input Tax Input Tax asset Unreconciled
12 account_type_profit_and_loss Profit and Loss Profit and Loss none None
13 account_type_view View view none None