[MERGE] base_import: second phase; update to some addons
bzr revid: xmo@openerp.com-20121010134737-u6zka0yas8sgawhz
This commit is contained in:
commit
24c7e25b9b
|
@ -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':'/',
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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/)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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/)
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -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));
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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' }
|
||||
]
|
||||
})
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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"
|
||||
|
|
|
Loading…
Reference in New Issue