diff --git a/.bzrignore b/.bzrignore index 7ae985ce4df..b83a43bf1d4 100644 --- a/.bzrignore +++ b/.bzrignore @@ -9,4 +9,3 @@ RE:^include/ RE:^share/ RE:^man/ RE:^lib/ -logging.cfg diff --git a/addons/web/__init__.py b/addons/web/__init__.py index dba47c9f45e..8802cb548c3 100644 --- a/addons/web/__init__.py +++ b/addons/web/__init__.py @@ -16,6 +16,7 @@ def wsgi_postload(): _logger.info("embedded mode") o = Options() o.dbfilter = openerp.tools.config['dbfilter'] + o.server_wide_modules = openerp.conf.server_wide_modules or ['web'] o.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions") o.addons_path = openerp.modules.module.ad_paths o.serve_static = True diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 567642d3429..b611eb91eb5 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -3,7 +3,6 @@ "depends" : [], 'active': True, 'post_load' : 'wsgi_postload', - 'web_preload' : True, 'js' : [ "static/lib/datejs/globalization/en-US.js", "static/lib/datejs/core.js", @@ -27,6 +26,7 @@ "static/lib/underscore/underscore.js", "static/lib/underscore/underscore.string.js", "static/lib/labjs/LAB.src.js", + "static/lib/py.parse/lib/py.js", "static/src/js/boot.js", "static/src/js/core.js", "static/src/js/dates.js", diff --git a/addons/web/common/dispatch.py b/addons/web/common/dispatch.py index 7a293c8228b..245624f2299 100644 --- a/addons/web/common/dispatch.py +++ b/addons/web/common/dispatch.py @@ -13,7 +13,6 @@ import xmlrpclib import simplejson import werkzeug.datastructures import werkzeug.exceptions -import werkzeug.urls import werkzeug.utils import werkzeug.wrappers import werkzeug.wsgi @@ -98,7 +97,8 @@ class WebRequest(object): self.params = dict(params) # OpenERP session setup self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex - self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession(self.config)) + self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession()) + self.session.config = self.config self.context = self.params.pop('context', None) self.debug = self.params.pop('debug', False) != False @@ -309,9 +309,16 @@ class Root(object): by the server, will be filtered by this pattern """ def __init__(self, options): - self.root = werkzeug.urls.Href('/web/webclient/home') + self.root = '/web/webclient/home?debug=1' self.config = options + if self.config.backend == 'local': + conn = openerplib.get_connector(protocol='local') + else: + conn = openerplib.get_connector(hostname=self.config.server_host, + port=self.config.server_port) + self.config.connector = conn + self.session_cookie = 'sessionid' self.addons = {} @@ -341,13 +348,10 @@ class Root(object): request.parameter_storage_class = werkzeug.datastructures.ImmutableDict if request.path == '/': - return werkzeug.utils.redirect( - self.root(dict(request.args, debug='')), 301)( - environ, start_response) + return werkzeug.utils.redirect(self.root, 301)(environ, start_response) elif request.path == '/mobile': return werkzeug.utils.redirect( - '/web_mobile/static/src/web_mobile.html', 301)( - environ, start_response) + '/web_mobile/static/src/web_mobile.html', 301)(environ, start_response) handler = self.find_handler(*(request.path.split('/')[1:])) diff --git a/addons/web/common/nonliterals.py b/addons/web/common/nonliterals.py index 937da06ad9e..4db333a5769 100644 --- a/addons/web/common/nonliterals.py +++ b/addons/web/common/nonliterals.py @@ -41,6 +41,9 @@ class NonLiteralEncoder(simplejson.encoder.JSONEncoder): '__eval_context': object.get_eval_context() } raise TypeError('Could not encode unknown non-literal %s' % object) + +_ALLOWED_KEYS = frozenset(['__ref', "__id", '__domains', + '__contexts', '__eval_context', 'own_values']) def non_literal_decoder(dct): """ Decodes JSON dicts into :class:`Domain` and :class:`Context` based on @@ -50,6 +53,9 @@ def non_literal_decoder(dct): ``own_values`` dict key. """ if '__ref' in dct: + for x in dct.keys(): + if not x in _ALLOWED_KEYS: + raise ValueError("'%s' key not allowed in non literal domain/context" % x) if dct['__ref'] == 'domain': domain = Domain(None, key=dct['__id']) if 'own_values' in dct: @@ -231,7 +237,10 @@ class CompoundContext(BaseContext): def evaluate(self, context=None): ctx = dict(context or {}) - ctx.update(self.get_eval_context() or {}) + eval_context = self.get_eval_context() + if eval_context: + eval_context = self.session.eval_context(eval_context) + ctx.update(eval_context) final_context = {} for context_to_eval in self.contexts: if not isinstance(context_to_eval, (dict, BaseContext)): diff --git a/addons/web/common/openerplib/main.py b/addons/web/common/openerplib/main.py index dfc310b567b..b09e9f9d72f 100644 --- a/addons/web/common/openerplib/main.py +++ b/addons/web/common/openerplib/main.py @@ -38,6 +38,7 @@ Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trun import xmlrpclib import logging import socket +import sys try: import cPickle as pickle @@ -70,6 +71,14 @@ class Connector(object): self.hostname = hostname self.port = port + def get_service(self, service_name): + """ + Returns a Service instance to allow easy manipulation of one of the services offered by the remote server. + + :param service_name: The name of the service. + """ + return Service(self, service_name) + class XmlRPCConnector(Connector): """ A type of connector that uses the XMLRPC protocol. @@ -208,10 +217,7 @@ class LocalConnector(Connector): # OpenERPWarning code 1 # OpenERPException code 2 try: - result = openerp.netsvc.dispatch_rpc(service_name, method, args, None) - except openerp.netsvc.OpenERPDispatcherException, e: - fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e.exception), e.traceback) - raise fault + result = openerp.netsvc.dispatch_rpc(service_name, method, args) except: exc_type, exc_value, exc_tb = sys.exc_info() fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)) @@ -323,7 +329,7 @@ class Connection(object): :param service_name: The name of the service. """ - return Service(self.connector, service_name) + return self.connector.get_service(service_name) class AuthenticationError(Exception): """ @@ -370,7 +376,7 @@ class Model(object): index = {} for r in result: index[r['id']] = r - result = [index[x] for x in args[0]] + result = [index[x] for x in args[0] if x in index] self.__logger.debug('result: %r', result) return result return proxy diff --git a/addons/web/common/session.py b/addons/web/common/session.py index c06763bebe2..2ed50bd6131 100644 --- a/addons/web/common/session.py +++ b/addons/web/common/session.py @@ -5,6 +5,9 @@ import time import openerplib import nonliterals + +import logging +_logger = logging.getLogger(__name__) #---------------------------------------------------------- # OpenERPSession RPC openerp backend access #---------------------------------------------------------- @@ -26,27 +29,24 @@ class OpenERPSession(object): Used to store references to non-literal domains which need to be round-tripped to the client browser. """ - def __init__(self, config): - self.config = config + def __init__(self): + self.config = None self._db = False self._uid = False self._login = False self._password = False - self._locale = 'en_US' self.context = {} self.contexts_store = {} self.domains_store = {} - self._lang = {} - self.remote_timezone = 'utc' - self.client_timezone = False + + def __getstate__(self): + state = dict(self.__dict__) + if "config" in state: + del state['config'] + return state def build_connection(self): - if self.config.backend == 'local': - conn = openerplib.get_connection(protocol='local', database=self._db, - login=self._login, user_id=self._uid, password=self._password) - else: - conn = openerplib.get_connection(hostname=self.config.server_host, - port=self.config.server_port, database=self._db, login=self._login, + conn = openerplib.Connection(self.config.connector, database=self._db, login=self._login, user_id=self._uid, password=self._password) return conn @@ -103,15 +103,6 @@ class OpenERPSession(object): self.context = self.model('res.users').context_get(self.context) self.context = self.context or {} - self.client_timezone = self.context.get("tz", False) - # invalid code, anyway we decided the server will be in UTC - #if self.client_timezone: - # self.remote_timezone = self.execute('common', 'timezone_get') - - self._locale = self.context.get('lang','en_US') - lang_ids = self.execute('res.lang','search', [('code', '=', self._locale)]) - if lang_ids: - self._lang = self.execute('res.lang', 'read',lang_ids[0], []) return self.context @property diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index ead27b2941a..9be0794a8bb 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -68,29 +68,6 @@ class Xml2Json: # OpenERP Web web Controllers #---------------------------------------------------------- -def manifest_preload(): - modules = [k for k,v in openerpweb.addons_manifest.items() if v.get('web_preload')] - return modules - -def manifest_addons(addons): - if addons==None: - addons = manifest_preload() - else: - addons = addons.split(',') - return addons - -def manifest_glob(addons, key): - addons = manifest_addons(addons) - files = [] - for addon in addons: - manifest = openerpweb.addons_manifest.get(addon, {}) - addons_path = manifest['addons_path'] - globlist = manifest.get(key, []) - for pattern in globlist: - for path in glob.glob(os.path.join(addons_path, addon, pattern)): - files.append(path[len(addons_path):]) - return files - # TODO change into concat_file(addons,key) taking care of addons_path def concat_files(addons_path, file_list): """ Concatenate file content @@ -131,24 +108,45 @@ home_template = textwrap.dedent(""" class WebClient(openerpweb.Controller): _cp_path = "/web/webclient" + def server_wide_modules(self, req): + addons = [i for i in req.config.server_wide_modules if i in openerpweb.addons_manifest] + return addons + + def manifest_glob(self, req, addons, key): + if addons==None: + addons = self.server_wide_modules(req) + else: + addons = addons.split(',') + files = [] + for addon in addons: + manifest = openerpweb.addons_manifest.get(addon, None) + if not manifest: + continue + addons_path = manifest['addons_path'] + globlist = manifest.get(key, []) + for pattern in globlist: + for path in glob.glob(os.path.join(addons_path, addon, pattern)): + files.append(path[len(addons_path):]) + return files + @openerpweb.jsonrequest def csslist(self, req, mods=None): - return manifest_glob(mods, 'css') + return self.manifest_glob(req, mods, 'css') @openerpweb.jsonrequest def jslist(self, req, mods=None): - return manifest_glob(mods, 'js') + return self.manifest_glob(req, mods, 'js') @openerpweb.httprequest def css(self, req, mods=None): - files = manifest_glob(mods, 'css') + files = self.manifest_glob(req, mods, 'css') content,timestamp = concat_files(req.config.addons_path, files) # TODO request set the Date of last modif and Etag return req.make_response(content, [('Content-Type', 'text/css')]) @openerpweb.httprequest def js(self, req, mods=None): - files = manifest_glob(mods, 'js') + files = self.manifest_glob(req, mods, 'js') content,timestamp = concat_files(req.config.addons_path, files) # TODO request set the Date of last modif and Etag return req.make_response(content, [('Content-Type', 'application/javascript')]) @@ -158,19 +156,19 @@ class WebClient(openerpweb.Controller): # script tags jslist = ['/web/webclient/js'] if req.debug: - jslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(None, 'js')] + jslist = [i + '?debug=' + str(time.time()) for i in self.manifest_glob(req, None, 'js')] js = "\n ".join([''%i for i in jslist]) # css tags csslist = ['/web/webclient/css'] if req.debug: - csslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(None, 'css')] + csslist = [i + '?debug=' + str(time.time()) for i in self.manifest_glob(req, None, 'css')] css = "\n ".join([''%i for i in csslist]) r = home_template % { 'javascript': js, 'css': css, - 'modules': simplejson.dumps(manifest_preload()), + 'modules': simplejson.dumps(self.server_wide_modules(req)), } return r @@ -364,7 +362,7 @@ class Session(openerpweb.Controller): mods = [] for name, manifest in openerpweb.addons_manifest.items(): # TODO replace by ir.module.module installed web - if not manifest.get('web_preload') and manifest.get('active', True): + if name not in req.config.server_wide_modules and manifest.get('active', True): mods.append(name) return mods @@ -494,6 +492,9 @@ def clean_action(req, action): if isinstance(action.get('domain'), basestring): action['domain'] = eval( action['domain'], eval_ctx ) or [] + if 'type' not in action: + action['type'] = 'ir.actions.act_window_close' + if action['type'] == 'ir.actions.act_window': return fix_view_modes(action) return action diff --git a/addons/web/static/lib/py.parse/.hg_archival.txt b/addons/web/static/lib/py.parse/.hg_archival.txt new file mode 100644 index 00000000000..43d5d80a9e4 --- /dev/null +++ b/addons/web/static/lib/py.parse/.hg_archival.txt @@ -0,0 +1,5 @@ +repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea +node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec +branch: default +latesttag: 0.2 +latesttagdistance: 1 diff --git a/addons/web/static/lib/py.parse/README b/addons/web/static/lib/py.parse/README new file mode 100644 index 00000000000..453f001ee15 --- /dev/null +++ b/addons/web/static/lib/py.parse/README @@ -0,0 +1 @@ +Parser and evaluator of Python expressions diff --git a/addons/web/static/lib/py.parse/TODO.rst b/addons/web/static/lib/py.parse/TODO.rst new file mode 100644 index 00000000000..3638b681ddc --- /dev/null +++ b/addons/web/static/lib/py.parse/TODO.rst @@ -0,0 +1,14 @@ +* Parser + since parsing expressions, try with a pratt parser + http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ + http://effbot.org/zone/simple-top-down-parsing.htm + +Evaluator +--------- + +* Stop busyworking trivial binary operator +* Make it *trivial* to build Python type-wrappers +* Implement Python's `data model + protocols`_ + for *all* supported operations, optimizations can come later +* Automatically type-wrap everything (for now anyway) diff --git a/addons/web/static/lib/py.parse/lib/py.js b/addons/web/static/lib/py.parse/lib/py.js new file mode 100644 index 00000000000..0f2415f1252 --- /dev/null +++ b/addons/web/static/lib/py.parse/lib/py.js @@ -0,0 +1,546 @@ +var py = {}; +(function (exports) { + var NUMBER = /^\d$/, + NAME_FIRST = /^[a-zA-Z_]$/, + NAME = /^[a-zA-Z0-9_]$/; + + var create = function (o, props) { + function F() {} + F.prototype = o; + var inst = new F; + for(var name in props) { + if(!props.hasOwnProperty(name)) { continue; } + inst[name] = props[name]; + } + return inst; + }; + + var symbols = {}; + var comparators = {}; + var Base = { + nud: function () { throw new Error(this.id + " undefined as prefix"); }, + led: function (led) { throw new Error(this.id + " undefined as infix"); }, + toString: function () { + if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') { + return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join(''); + } else if (this.id === '(end)') { + return '(end)'; + } else if (this.id === '(comparator)' ) { + var repr = ['(comparator', this.expressions[0]]; + for (var i=0;i s.lbp) { + s.lbp = bp; + } + return s; + } + return symbols[id] = create(Base, { + id: id, + lbp: bp + }); + } + function constant(id) { + symbol(id).nud = function () { + this.id = "(constant)"; + this.value = id; + return this; + }; + } + function prefix(id, bp, nud) { + symbol(id).nud = nud || function () { + this.first = expression(bp); + return this + } + } + function infix(id, bp, led) { + symbol(id, bp).led = led || function (left) { + this.first = left; + this.second = expression(bp); + return this; + } + } + function infixr(id, bp) { + symbol(id, bp).led = function (left) { + this.first = left; + this.second = expression(bp - 1); + return this; + } + } + function comparator(id) { + comparators[id] = true; + var bp = 60; + infix(id, bp, function (left) { + this.id = '(comparator)'; + this.operators = [id]; + this.expressions = [left, expression(bp)]; + while (token.id in comparators) { + this.operators.push(token.id); + advance(); + this.expressions.push( + expression(bp)); + } + return this; + }); + } + + constant('None'); constant('False'); constant('True'); + + symbol('(number)').nud = function () { return this; }; + symbol('(name)').nud = function () { return this; }; + symbol('(string)').nud = function () { return this; }; + symbol('(end)'); + + symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(','); + symbol('else'); + + symbol('lambda', 20).nud = function () { + this.first = []; + if (token.id !== ':') { + for(;;) { + if (token.id !== '(name)') { + throw new Error('Excepted an argument name'); + } + this.first.push(token); + advance(); + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance(':'); + this.second = expression(); + return this; + }; + infix('if', 20, function (left) { + this.first = left; + this.second = expression(); + advance('else'); + this.third = expression(); + return this; + }); + + infixr('or', 30); infixr('and', 40); prefix('not', 50); + + comparator('in'); comparator('not in'); + comparator('is'); comparator('is not'); + comparator('<'); comparator('<='); + comparator('>'); comparator('>='); + comparator('<>'); comparator('!='); comparator('=='); + + infix('|', 70); infix('^', 80), infix('&', 90); + + infix('<<', 100); infix('>>', 100); + + infix('+', 110); infix('-', 110); + + infix('*', 120); infix('/', 120); + infix('//', 120), infix('%', 120); + + prefix('-', 130); prefix('+', 130); prefix('~', 130); + + infixr('**', 140); + + infix('.', 150, function (left) { + if (token.id !== '(name)') { + throw new Error('Expected attribute name, got ', token.id); + } + this.first = left; + this.second = token; + advance(); + return this; + }); + symbol('(', 150).nud = function () { + this.first = []; + var comma = false; + if (token.id !== ')') { + while (true) { + if (token.id === ')') { + break; + } + this.first.push(expression()); + if (token.id !== ',') { + break; + } + comma = true; + advance(','); + } + } + advance(')'); + if (!this.first.length || comma) { + return this; + } else { + return this.first[0]; + } + }; + symbol('(').led = function (left) { + this.first = left; + this.second = []; + if (token.id !== ")") { + for(;;) { + this.second.push(expression()); + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance(")"); + return this; + + }; + infix('[', 150, function (left) { + this.first = left; + this.second = expression(); + advance("]"); + return this; + }); + symbol('[').nud = function () { + this.first = []; + if (token.id !== ']') { + for (;;) { + if (token.id === ']') { + break; + } + this.first.push(expression()); + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + return this; + }; + + symbol('{').nud = function () { + this.first = []; + if (token.id !== '}') { + for(;;) { + if (token.id === '}') { + break; + } + var key = expression(); + advance(':'); + var value = expression(); + this.first.push([key, value]); + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + return this; + }; + + var longops = { + '*': ['*'], + '<': ['<', '=', '>'], + '>': ['=', '>'], + '!': ['='], + '=': ['='], + '/': ['/'] + }; + function Tokenizer() { + this.states = ['initial']; + this.tokens = []; + } + Tokenizer.prototype = { + builder: function (empty) { + var key = this.states[0] + '_builder'; + if (empty) { + var value = this[key]; + delete this[key]; + return value; + } else { + return this[key] = this[key] || []; + } + }, + simple: function (type) { + this.tokens.push({type: type}); + }, + push: function (new_state) { + this.states.push(new_state); + }, + pop: function () { + this.states.pop(); + }, + + feed: function (str, index) { + var s = this.states; + return this[s[s.length - 1]](str, index); + }, + + initial: function (str, index) { + var character = str[index]; + + if (character in longops) { + var follow = longops[character]; + for(var i=0, len=follow.length; i> at index " + index + + ", character [[" + character + "]]" + + "; parsed so far: " + this.tokens); + }, + string: function (str, index) { + var character = str[index]; + if (character === '"' || character === "'") { + this.tokens.push(create(symbols['(string)'], { + value: this.builder(true).join('') + })); + this.pop(); + return index + 1; + } + this.builder().push(character); + return index + 1; + }, + number: function (str, index) { + var character = str[index]; + if (!NUMBER.test(character)) { + this.tokens.push(create(symbols['(number)'], { + value: parseFloat(this.builder(true).join('')) + })); + this.pop(); + return index; + } + this.builder().push(character); + return index + 1; + }, + name: function (str, index) { + var character = str[index]; + if (!NAME.test(character)) { + var name = this.builder(true).join(''); + var symbol = symbols[name]; + if (symbol) { + if (name === 'in' && this.tokens[this.tokens.length-1].id === 'not') { + symbol = symbols['not in']; + this.tokens.pop(); + } else if (name === 'not' && this.tokens[this.tokens.length-1].id === 'is') { + symbol = symbols['is not']; + this.tokens.pop(); + } + this.tokens.push(create(symbol)); + } else { + this.tokens.push(create(symbols['(name)'], { + value: name + })); + } + this.pop(); + return index; + } + this.builder().push(character); + return index + 1; + } + }; + + exports.tokenize = function tokenize(str) { + var index = 0, + tokenizer = new Tokenizer(str); + str += '\0'; + + do { + index = tokenizer.feed(str, index); + } while (index !== str.length); + return tokenizer.tokens; + }; + + var token, next; + function expression(rbp) { + rbp = rbp || 0; + var t = token; + token = next(); + var left = t.nud(); + while (rbp < token.lbp) { + t = token; + token = next(); + left = t.led(left); + } + return left; + } + function advance(id) { + if (id && token.id !== id) { + throw new Error( + 'Expected "' + id + '", got "' + token.id + '"'); + } + token = next(); + } + + exports.object = create({}, {}); + exports.bool = function (arg) { return !!arg; }; + exports.tuple = create(exports.object, { + __contains__: function (value) { + for(var i=0, len=this.values.length; i': return a > b; + case '>=': return a >= b; + case 'in': + if (typeof b === 'string') { + return b.indexOf(a) !== -1; + } + return b.__contains__(a); + case 'not in': + if (typeof b === 'string') { + return b.indexOf(a) === -1; + } + return !b.__contains__(a); + } + throw new Error('SyntaxError: unknown comparator [[' + operator + ']]'); + }; + exports.evaluate = function (expr, context) { + switch (expr.id) { + case '(name)': + var val = context[expr.value]; + if (val === undefined) { + throw new Error("NameError: name '" + expr.value + "' is not defined"); + } + return val; + case '(string)': + case '(number)': + return expr.value; + case '(constant)': + if (expr.value === 'None') + return null; + else if (expr.value === 'False') + return false; + else if (expr.value === 'True') + return true; + throw new Error("SyntaxError: unknown constant '" + expr.value + "'"); + case '(comparator)': + var result, left = exports.evaluate(expr.expressions[0], context); + for(var i=0; i= 3')); +assert.ok(py.eval('3 >= 3')); +assert.ok(!py.eval('5 < 3')); +assert.ok(py.eval('1 < 3 < 5')); +assert.ok(py.eval('5 > 3 > 1')); +assert.ok(py.eval('1 < 3 > 2 == 2 > -2 not in (0, 1, 2)')); +// string rich comparisons +assert.ok(py.eval( + 'date >= current', {date: '2010-06-08', current: '2010-06-05'})); + +// Boolean operators +assert.ok(py.eval( + "foo == 'foo' or foo == 'bar'", {foo: 'bar'})); +assert.ok(py.eval( + "foo == 'foo' and bar == 'bar'", {foo: 'foo', bar: 'bar'})); +// - lazyness, second clauses NameError if not short-circuited +assert.ok(py.eval( + "foo == 'foo' or bar == 'bar'", {foo: 'foo'})); +assert.ok(!py.eval( + "foo == 'foo' and bar == 'bar'", {foo: 'bar'})); + +// contains (in) +assert.ok(py.eval( + "foo in ('foo', 'bar')", {foo: 'bar'})); +assert.ok(py.eval('1 in (1, 2, 3, 4)')); +assert.ok(!py.eval('1 in (2, 3, 4)')); +assert.ok(py.eval('type in ("url",)', {type: 'url'})); +assert.ok(!py.eval('type in ("url",)', {type: 'ur'})); +assert.ok(py.eval('1 not in (2, 3, 4)')); +assert.ok(py.eval('type not in ("url",)', {type: 'ur'})); + +assert.ok(py.eval( + "foo in ['foo', 'bar']", {foo: 'bar'})); +// string contains +assert.ok(py.eval('type in "view"', {type: 'view'})); +assert.ok(!py.eval('type in "view"', {type: 'bob'})); +assert.ok(py.eval('type in "url"', {type: 'ur'})); + +// Literals +assert.strictEqual(py.eval('False'), false); +assert.strictEqual(py.eval('True'), true); +assert.strictEqual(py.eval('None'), null); +assert.ok(py.eval('foo == False', {foo: false})); +assert.ok(!py.eval('foo == False', {foo: true})); + +// conversions +assert.strictEqual( + py.eval('bool(date_deadline)', {bool: py.bool, date_deadline: '2008'}), + true); + +// getattr +assert.ok(py.eval('foo.bar', {foo: {bar: true}})); +assert.ok(!py.eval('foo.bar', {foo: {bar: false}})); + +// complex expressions +assert.ok(py.eval( + "state=='pending' and not(date_deadline and (date_deadline < current_date))", + {state: 'pending', date_deadline: false})); +assert.ok(py.eval( + "state=='pending' and not(date_deadline and (date_deadline < current_date))", + {state: 'pending', date_deadline: '2010-05-08', current_date: '2010-05-08'}));; diff --git a/addons/web/static/lib/underscore/underscore.string.js b/addons/web/static/lib/underscore/underscore.string.js index 4da2b26ec1c..d15c1f1f7ce 100644 --- a/addons/web/static/lib/underscore/underscore.string.js +++ b/addons/web/static/lib/underscore/underscore.string.js @@ -4,301 +4,436 @@ // Documentation: https://github.com/edtsech/underscore.string // Some code is borrowed from MooTools and Alexandru Marasteanu. -// Version 1.1.4 +// Version 1.1.6 -(function(){ - // ------------------------- Baseline setup --------------------------------- - // Establish the root object, "window" in the browser, or "global" on the server. - var root = this; +(function(root){ + 'use strict'; - var nativeTrim = String.prototype.trim; + if (typeof _ != 'undefined') { + var _reverse = _().reverse, + _include = _.include; + } - function str_repeat(i, m) { - for (var o = []; m > 0; o[--m] = i); - return o.join(''); + // Defining helper functions. + + var nativeTrim = String.prototype.trim; + + var parseNumber = function(source) { return source * 1 || 0; }; + + var strRepeat = function(i, m) { + for (var o = []; m > 0; o[--m] = i); + return o.join(''); + }; + + var slice = function(a){ + return Array.prototype.slice.call(a); + }; + + var defaultToWhiteSpace = function(characters){ + if (characters) { + return _s.escapeRegExp(characters); + } + return '\\s'; + }; + + var sArgs = function(method){ + return function(){ + var args = slice(arguments); + for(var i=0; i + // All rights reserved. + + var sprintf = (function() { + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); } - function defaultToWhiteSpace(characters){ - if (characters) { - return _s.escapeRegExp(characters); + var str_repeat = strRepeat; + + var str_format = function() { + if (!str_format.cache.hasOwnProperty(arguments[0])) { + str_format.cache[arguments[0]] = str_format.parse(arguments[0]); + } + return str_format.format.call(null, str_format.cache[arguments[0]], arguments); + }; + + str_format.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); } - return '\\s'; - } - - var _s = { - - isBlank: function(str){ - return !!str.match(/^\s*$/); - }, - - capitalize : function(str) { - return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); - }, - - chop: function(str, step){ - step = step || str.length; - var arr = []; - for (var i = 0; i < str.length;) { - arr.push(str.slice(i,i + step)); - i = i + step; + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[_.sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; } - return arr; - }, + } else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } - clean: function(str){ - return _s.strip(str.replace(/\s+/g, ' ')); - }, + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[_.sprintf] expecting number but found %s', get_type(arg))); + } + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = Math.abs(arg); break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; - count: function(str, substr){ - var count = 0, index; - for (var i=0; i < str.length;) { - index = str.indexOf(substr, i); - index >= 0 && count++; - i = i + (index >= 0 ? index : 0) + substr.length; - } - return count; - }, + str_format.cache = {}; - chars: function(str) { - return str.split(''); - }, - - escapeHTML: function(str) { - return String(str||'').replace(/&/g,'&').replace(//g,'>') - .replace(/"/g, '"').replace(/'/g, "'"); - }, - - unescapeHTML: function(str) { - return String(str||'').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') - .replace(/"/g, '"').replace(/'/g, "'"); - }, - - escapeRegExp: function(str){ - // From MooTools core 1.2.4 - return String(str||'').replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); - }, - - insert: function(str, i, substr){ - var arr = str.split(''); - arr.splice(i, 0, substr); - return arr.join(''); - }, - - includes: function(str, needle){ - return str.indexOf(needle) !== -1; - }, - - join: function(sep) { - // TODO: Could this be faster by converting - // arguments to Array and using array.join(sep)? - sep = String(sep); - var str = ""; - for (var i=1; i < arguments.length; i += 1) { - str += String(arguments[i]); - if ( i !== arguments.length-1 ) { - str += sep; + str_format.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); } - } - return str; - }, - - lines: function(str) { - return str.split("\n"); - }, - -// reverse: function(str){ -// return Array.prototype.reverse.apply(str.split('')).join(''); -// }, - - splice: function(str, i, howmany, substr){ - var arr = str.split(''); - arr.splice(i, howmany, substr); - return arr.join(''); - }, - - startsWith: function(str, starts){ - return str.length >= starts.length && str.substring(0, starts.length) === starts; - }, - - endsWith: function(str, ends){ - return str.length >= ends.length && str.substring(str.length - ends.length) === ends; - }, - - succ: function(str){ - var arr = str.split(''); - arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1)); - return arr.join(''); - }, - - titleize: function(str){ - var arr = str.split(' '), - word; - for (var i=0; i < arr.length; i++) { - word = arr[i].split(''); - if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase(); - i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' '; - } - return arr.join(''); - }, - - camelize: function(str){ - return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) { - return chr ? chr.toUpperCase() : ''; - }); - }, - - underscored: function(str){ - return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase(); - }, - - dasherize: function(str){ - return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase(); - }, - - trim: function(str, characters){ - if (!characters && nativeTrim) { - return nativeTrim.call(str); - } - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); - }, - - ltrim: function(str, characters){ - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('\^[' + characters + ']+', 'g'), ''); - }, - - rtrim: function(str, characters){ - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('[' + characters + ']+$', 'g'), ''); - }, - - truncate: function(str, length, truncateStr){ - truncateStr = truncateStr || '...'; - return str.slice(0,length) + truncateStr; - }, - - words: function(str, delimiter) { - delimiter = delimiter || " "; - return str.split(delimiter); - }, - - - pad: function(str, length, padStr, type) { - - var padding = ''; - var padlen = 0; - - if (!padStr) { padStr = ' '; } - else if (padStr.length > 1) { padStr = padStr[0]; } - switch(type) { - case "right": - padlen = (length - str.length); - padding = str_repeat(padStr, padlen); - str = str+padding; - break; - case "both": - padlen = (length - str.length); - padding = { - 'left' : str_repeat(padStr, Math.ceil(padlen/2)), - 'right': str_repeat(padStr, Math.floor(padlen/2)) - }; - str = padding.left+str+padding.right; - break; - default: // "left" - padlen = (length - str.length); - padding = str_repeat(padStr, padlen);; - str = padding+str; - } - return str; - }, - - lpad: function(str, length, padStr) { - return _s.pad(str, length, padStr); - }, - - rpad: function(str, length, padStr) { - return _s.pad(str, length, padStr, 'right'); - }, - - lrpad: function(str, length, padStr) { - return _s.pad(str, length, padStr, 'both'); - }, - - - /** - * Credits for this function goes to - * http://www.diveintojavascript.com/projects/sprintf-for-javascript - * - * Copyright (c) Alexandru Marasteanu - * All rights reserved. - * */ - sprintf: function(){ - - var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; - while (f) { - if (m = /^[^\x25]+/.exec(f)) { - o.push(m[0]); - } - else if (m = /^\x25{2}/.exec(f)) { - o.push('%'); - } - else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { - if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { - throw('Too few arguments.'); - } - if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { - throw('Expecting number but found ' + typeof(a)); - } - switch (m[7]) { - case 'b': a = a.toString(2); break; - case 'c': a = String.fromCharCode(a); break; - case 'd': a = parseInt(a); break; - case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; - case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; - case 'o': a = a.toString(8); break; - case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; - case 'u': a = Math.abs(a); break; - case 'x': a = a.toString(16); break; - case 'X': a = a.toString(16).toUpperCase(); break; - } - a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); - c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; - x = m[5] - String(a).length - s.length; - p = m[5] ? str_repeat(c, x) : ''; - o.push(s + (m[4] ? a + p : p + a)); + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); } else { - throw('Huh ?!'); + throw('[_.sprintf] huh?'); } - f = f.substring(m[0].length); + } } - return o.join(''); + else { + throw('[_.sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[_.sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); } - } + else { + throw('[_.sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; - // Aliases + return str_format; + })(); - _s.strip = _s.trim; - _s.lstrip = _s.ltrim; - _s.rstrip = _s.rtrim; - _s.center = _s.lrpad - _s.ljust = _s.lpad - _s.rjust = _s.rpad - // CommonJS module is defined - if (typeof window === 'undefined' && typeof module !== 'undefined') { - // Export module - module.exports = _s; - // Integrate with Underscore.js - } else if (typeof root._ !== 'undefined') { - root._.mixin(_s); + // Defining underscore.string - // Or define it - } else { - root._ = _s; - } + var _s = { -}()); + isBlank: sArgs(function(str){ + return (/^\s*$/).test(str); + }), + + stripTags: sArgs(function(str){ + return str.replace(/<\/?[^>]+>/ig, ''); + }), + + capitalize : sArgs(function(str) { + return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); + }), + + chop: sArgs(function(str, step){ + step = parseNumber(step) || str.length; + var arr = []; + for (var i = 0; i < str.length;) { + arr.push(str.slice(i,i + step)); + i = i + step; + } + return arr; + }), + + clean: sArgs(function(str){ + return _s.strip(str.replace(/\s+/g, ' ')); + }), + + count: sArgs(function(str, substr){ + var count = 0, index; + for (var i=0; i < str.length;) { + index = str.indexOf(substr, i); + index >= 0 && count++; + i = i + (index >= 0 ? index : 0) + substr.length; + } + return count; + }), + + chars: sArgs(function(str) { + return str.split(''); + }), + + escapeHTML: sArgs(function(str) { + return str.replace(/&/g,'&').replace(//g,'>') + .replace(/"/g, '"').replace(/'/g, "'"); + }), + + unescapeHTML: sArgs(function(str) { + return str.replace(/</g, '<').replace(/>/g, '>') + .replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&'); + }), + + escapeRegExp: sArgs(function(str){ + // From MooTools core 1.2.4 + return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }), + + insert: sArgs(function(str, i, substr){ + var arr = str.split(''); + arr.splice(parseNumber(i), 0, substr); + return arr.join(''); + }), + + includes: sArgs(function(str, needle){ + return str.indexOf(needle) !== -1; + }), + + include: function(obj, needle) { + if (!_include || (/string|number/).test(typeof obj)) { + return this.includes(obj, needle); + } else { + return _include(obj, needle); + } + }, + + join: sArgs(function(sep) { + var args = slice(arguments); + return args.join(args.shift()); + }), + + lines: sArgs(function(str) { + return str.split("\n"); + }), + + reverse: function(obj){ + if (!_reverse || (/string|number/).test(typeof obj)) { + return Array.prototype.reverse.apply(String(obj).split('')).join(''); + } else { + return _reverse.call(_(obj)); + } + }, + + splice: sArgs(function(str, i, howmany, substr){ + var arr = str.split(''); + arr.splice(parseNumber(i), parseNumber(howmany), substr); + return arr.join(''); + }), + + startsWith: sArgs(function(str, starts){ + return str.length >= starts.length && str.substring(0, starts.length) === starts; + }), + + endsWith: sArgs(function(str, ends){ + return str.length >= ends.length && str.substring(str.length - ends.length) === ends; + }), + + succ: sArgs(function(str){ + var arr = str.split(''); + arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1)); + return arr.join(''); + }), + + titleize: sArgs(function(str){ + var arr = str.split(' '), + word; + for (var i=0; i < arr.length; i++) { + word = arr[i].split(''); + if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase(); + i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' '; + } + return arr.join(''); + }), + + camelize: sArgs(function(str){ + return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }); + }), + + underscored: function(str){ + return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase(); + }, + + dasherize: function(str){ + return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase(); + }, + + trim: sArgs(function(str, characters){ + if (!characters && nativeTrim) { + return nativeTrim.call(str); + } + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); + }), + + ltrim: sArgs(function(str, characters){ + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('\^[' + characters + ']+', 'g'), ''); + }), + + rtrim: sArgs(function(str, characters){ + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('[' + characters + ']+$', 'g'), ''); + }), + + truncate: sArgs(function(str, length, truncateStr){ + truncateStr = truncateStr || '...'; + length = parseNumber(length); + return str.length > length ? str.slice(0,length) + truncateStr : str; + }), + + words: function(str, delimiter) { + return String(str).split(delimiter || " "); + }, + + pad: sArgs(function(str, length, padStr, type) { + var padding = '', + padlen = 0; + + length = parseNumber(length); + + if (!padStr) { padStr = ' '; } + else if (padStr.length > 1) { padStr = padStr.charAt(0); } + switch(type) { + case 'right': + padlen = (length - str.length); + padding = strRepeat(padStr, padlen); + str = str+padding; + break; + case 'both': + padlen = (length - str.length); + padding = { + 'left' : strRepeat(padStr, Math.ceil(padlen/2)), + 'right': strRepeat(padStr, Math.floor(padlen/2)) + }; + str = padding.left+str+padding.right; + break; + default: // 'left' + padlen = (length - str.length); + padding = strRepeat(padStr, padlen);; + str = padding+str; + } + return str; + }), + + lpad: function(str, length, padStr) { + return _s.pad(str, length, padStr); + }, + + rpad: function(str, length, padStr) { + return _s.pad(str, length, padStr, 'right'); + }, + + lrpad: function(str, length, padStr) { + return _s.pad(str, length, padStr, 'both'); + }, + + sprintf: sprintf, + + vsprintf: function(fmt, argv){ + argv.unshift(fmt); + return sprintf.apply(null, argv); + }, + + toNumber: function(str, decimals) { + var num = parseNumber(parseNumber(str).toFixed(parseNumber(decimals))); + return (!(num === 0 && (str !== "0" && str !== 0))) ? num : Number.NaN; + }, + + strRight: sArgs(function(sourceStr, sep){ + var pos = (!sep) ? -1 : sourceStr.indexOf(sep); + return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr; + }), + + strRightBack: sArgs(function(sourceStr, sep){ + var pos = (!sep) ? -1 : sourceStr.lastIndexOf(sep); + return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr; + }), + + strLeft: sArgs(function(sourceStr, sep){ + var pos = (!sep) ? -1 : sourceStr.indexOf(sep); + return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr; + }), + + strLeftBack: sArgs(function(sourceStr, sep){ + var pos = sourceStr.lastIndexOf(sep); + return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr; + }) + + }; + + // Aliases + + _s.strip = _s.trim; + _s.lstrip = _s.ltrim; + _s.rstrip = _s.rtrim; + _s.center = _s.lrpad; + _s.ljust = _s.lpad; + _s.rjust = _s.rpad; + + // CommonJS module is defined + if (typeof module !== 'undefined' && module.exports) { + // Export module + module.exports = _s; + + // Integrate with Underscore.js + } else if (typeof root._ !== 'undefined') { + root._.mixin(_s); + + // Or define it + } else { + root._ = _s; + } + +}(this || window)); diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index c75fe786543..8c99823434c 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -125,7 +125,7 @@ body.openerp, .openerp textarea, .openerp input, .openerp select, .openerp optio margin-top: 5px; text-align: center; } -.openerp .login.login_invalid .login_error_message { +.openerp .login .login_invalid .login_error_message { display: block; } diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index c6b065205d7..1955298ba27 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -1,7 +1,6 @@ /*--------------------------------------------------------- * OpenERP Web chrome *---------------------------------------------------------*/ - openerp.web.chrome = function(openerp) { var QWeb = openerp.web.qweb; @@ -129,6 +128,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# // Destroy widget this.close(); this.$dialog.dialog('destroy'); + this._super(); } }); @@ -253,7 +253,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database .removeClass("database_block") .end() .empty(); - + this._super(); }, /** * Converts a .serializeArray() result into a dict. Does not bother folding @@ -299,9 +299,9 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database var admin = result[1][0]; setTimeout(function () { - self.stop(); self.widget_parent.do_login( info.db, admin.login, admin.password); + self.stop(); $.unblockUI(); }); }); @@ -463,6 +463,9 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ remember_creditentials: true, + + template: "Login", + identifier_prefix: 'oe-app-login-', /** * @constructs openerp.web.Login * @extends openerp.web.Widget @@ -471,8 +474,8 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ * @param element_id */ - init: function(parent, element_id) { - this._super(parent, element_id); + init: function(parent) { + this._super(parent); this.has_local_storage = typeof(localStorage) != 'undefined'; this.selected_db = null; this.selected_login = null; @@ -487,17 +490,6 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ }, start: function() { var self = this; - this.rpc("/web/database/get_list", {}, function(result) { - self.db_list = result.db_list; - self.display(); - }, function() { - self.display(); - }); - }, - display: function() { - var self = this; - - this.$element.html(QWeb.render("Login", this)); this.database = new openerp.web.Database( this, "oe_database", "oe_db_options"); @@ -506,6 +498,17 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ }); this.$element.find("form").submit(this.on_submit); + + this.rpc("/web/database/get_list", {}, function(result) { + var tpl = openerp.web.qweb.render('Login_dblist', {db_list: result.db_list, selected_db: self.selected_db}); + self.$element.find("input[name=db]").replaceWith(tpl) + }, + function(error, event) { + if (error.data.fault_code === 'AccessDenied') { + event.preventDefault(); + } + }); + }, on_login_invalid: function() { this.$element.closest(".openerp").addClass("login-mode"); @@ -933,7 +936,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie openerp.web.Widget.prototype.notification = new openerp.web.Notification(this, "oe_notification"); this.header = new openerp.web.Header(this); - this.login = new openerp.web.Login(this, "oe_login"); + this.login = new openerp.web.Login(this); this.header.on_logout.add(this.login.on_logout); this.header.on_action.add(this.on_menu_action); @@ -953,7 +956,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie start: function() { this.header.appendTo($("#oe_header")); this.session.start(); - this.login.start(); + this.login.appendTo($('#oe_login')); this.menu.start(); }, on_logged: function() { diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 00281628e75..fcf6a65dcf5 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -233,6 +233,7 @@ openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends }); openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet# */{ + identifier_prefix: "dataset", /** * DateaManagement interface between views and the collection of selected * OpenERP records (represents the view's state?) @@ -242,16 +243,12 @@ openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet * * @param {String} model the OpenERP model this dataset will manage */ - init: function(source_controller, model, context) { - // we don't want the dataset to be a child of anything! - this._super(null); - this.session = source_controller ? source_controller.session : undefined; + init: function(parent, model, context) { + this._super(parent); this.model = model; this.context = context || {}; this.index = null; }, - start: function() { - }, previous: function () { this.index -= 1; if (this.index < 0) { @@ -549,13 +546,11 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D sort: this.sort(), offset: offset, limit: options.limit || false - }, function (result) { + }).pipe(function (result) { self.ids = result.ids; self.offset = offset; - if (callback) { - callback(result.records); - } - }); + return result.records; + }).then(callback); }, get_domain: function (other_domain) { if (other_domain) { @@ -603,7 +598,6 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D }); openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ virtual_id_prefix: "one2many_v_id_", - virtual_id_regex: /one2many_v_id_.*/, debug_mode: true, init: function() { this._super.apply(this, arguments); @@ -724,6 +718,8 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ return completion.promise(); } }); +openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/; + openerp.web.ReadOnlyDataSetSearch = openerp.web.DataSetSearch.extend({ default_get: function(fields, callback) { return this._super(fields, callback).then(this.on_default_get); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c7ffb896186..11ebad8d093 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -41,7 +41,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.has_been_loaded = $.Deferred(); this.$form_header = null; this.translatable_fields = []; - _.defaults(this.options, {"always_show_new_button": true}); + _.defaults(this.options, {"always_show_new_button": true, + "not_interactible_on_create": false}); }, start: function() { this._super(); @@ -72,17 +73,25 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# _.each(this.widgets, function(w) { w.stop(); }); + this._super(); + }, + reposition: function ($e) { + this.$element = $e; + this.on_loaded(); }, on_loaded: function(data) { var self = this; - this.fields_view = data; - var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch); + if (data) { + this.fields_view = data; + var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch); - this.$element.html(QWeb.render(this.form_template, { 'frame': frame, 'view': this })); + this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'view': this }); + } + this.$element.html(this.rendered); _.each(this.widgets, function(w) { w.start(); }); - this.$form_header = this.$element.find('#' + this.element_id + '_header'); + this.$form_header = this.$element.find('.oe_form_header:first'); this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() { var action = $(this).data('pager-action'); self.on_pager_action(action); @@ -93,6 +102,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.$form_header.find('button.oe_form_button_cancel').click(this.do_cancel); this.$form_header.find('button.oe_form_button_new').click(this.on_button_new); this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate); + this.$form_header.find('button.oe_form_button_toggle').click(function () { + self.translatable_fields = []; + self.widgets = {}; + self.fields = {}; + self.$form_header.find('button').unbind('click'); + self.registry = self.registry === openerp.web.form.widgets + ? openerp.web.form.readonly + : openerp.web.form.widgets; + self.on_loaded(self.fields_view); + self.reload(); + }); if (this.options.sidebar && this.options.sidebar_id) { this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id); @@ -196,7 +216,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }, do_update_pager: function(hide_index) { - var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager'); + var $pager = this.$form_header.find('div.oe_form_pager'); var index = hide_index ? '-' : this.dataset.index + 1; $pager.find('span.oe_pager_index').html(index); $pager.find('span.oe_pager_count').html(this.dataset.ids.length); @@ -477,6 +497,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# if (self.dataset.parent_view) return self.dataset.parent_view.recursive_save(); }); + }, + is_interactible_record: function() { + var id = this.datarecord.id; + if (!id) { + if (this.options.not_interactible_on_create) + return false; + } else if (typeof(id) === "string") { + if(openerp.web.BufferedDataSet.virtual_id_regex.test(id)) + return false; + } + return true; } }); openerp.web.FormDialog = openerp.web.Dialog.extend({ @@ -624,6 +655,7 @@ openerp.web.form.compute_domain = function(expr, fields) { openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.Widget# */{ template: 'Widget', + identifier_prefix: 'formview-widget-', /** * @constructs openerp.web.form.Widget * @extends openerp.web.Widget @@ -637,11 +669,13 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form. this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}'); this.type = this.type || node.tag; this.element_name = this.element_name || this.type; - this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_"); + this.element_class = [ + 'formview', this.view.view_id, this.element_name, + this.view.widgets_counter++].join("_"); - this._super(view, this.element_id); + this._super(view); - this.view.widgets[this.element_id] = this; + this.view.widgets[this.element_class] = this; this.children = node.children; this.colspan = parseInt(node.attrs.colspan || 1, 10); this.decrease_max_width = 0; @@ -654,12 +688,7 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form. this.width = this.node.attrs.width; }, start: function() { - this.$element = $('#' + this.element_id); - }, - stop: function() { - if (this.$element) { - this.$element.remove(); - } + this.$element = this.view.$element.find('.' + this.element_class); }, process_modifiers: function() { var compute_domain = openerp.web.form.compute_domain; @@ -763,13 +792,24 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ for (var i = 0; i < node.children.length; i++) { var n = node.children[i]; if (n.tag == "page") { - var page = new openerp.web.form.WidgetNotebookPage(this.view, n, this, this.pages.length); + var page = new openerp.web.form.WidgetNotebookPage( + this.view, n, this, this.pages.length); this.pages.push(page); } } }, start: function() { + var self = this; this._super.apply(this, arguments); + this.$element.find('> ul > li').each(function (index, tab_li) { + var page = self.pages[index], + id = _.uniqueId(self.element_name + '-'); + page.element_id = id; + $(tab_li).find('a').attr('href', '#' + id); + }); + this.$element.find('> div').each(function (index, page) { + page.id = self.pages[index].element_id; + }); this.$element.tabs(); this.view.on_button_new.add_last(this.do_select_first_visible_tab); }, @@ -791,11 +831,11 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({ this.index = index; this.element_name = 'page_' + index; this._super(view, node); - this.element_tab_id = this.element_id + '_tab'; }, start: function() { this._super.apply(this, arguments); - this.$element_tab = $('#' + this.element_tab_id); + this.$element_tab = this.notebook.$element.find( + '> ul > li:eq(' + this.index + ')'); }, update_dom: function() { if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) { @@ -807,9 +847,9 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({ }); openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({ + template: 'WidgetSeparator', init: function(view, node) { this._super(view, node); - this.template = "WidgetSeparator"; this.orientation = node.attrs.orientation || 'horizontal'; if (this.orientation === 'vertical') { this.width = '1'; @@ -819,9 +859,10 @@ openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({ }); openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ + template: 'WidgetButton', init: function(view, node) { this._super(view, node); - this.template = "WidgetButton"; + this.force_disabled = false; if (this.string) { // We don't have button key bindings in the webclient this.string = this.string.replace(/_/g, ''); @@ -833,46 +874,74 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ }, start: function() { this._super.apply(this, arguments); - this.$element.click(this.on_click); + this.$element.find("button").click(this.on_click); }, on_click: function() { + var self = this; + this.force_disabled = true; + this.check_disable(); + this.execute_action().always(function() { + self.force_disabled = false; + self.check_disable(); + }); + }, + execute_action: function() { var self = this; var exec_action = function() { if (self.node.attrs.confirm) { + var def = $.Deferred(); var dialog = $('
' + self.node.attrs.confirm + '
').dialog({ title: 'Confirm', modal: true, buttons: { Ok: function() { - self.on_confirmed(); + self.on_confirmed().then(function() { + def.resolve(); + }); $(self).dialog("close"); }, Cancel: function() { + def.resolve(); $(self).dialog("close"); } } }); + return def.promise(); } else { - self.on_confirmed(); + return self.on_confirmed(); } }; if ((!this.node.attrs.special && this.view.dirty_for_user) || !this.view.datarecord.id) { - this.view.recursive_save().then(exec_action); + return this.view.recursive_save().pipe(exec_action); } else { - exec_action(); + return exec_action(); } }, on_confirmed: function() { var self = this; - this.view.do_execute_action( + return this.view.do_execute_action( this.node.attrs, this.view.dataset, this.view.datarecord.id, function () { self.view.reload(); }); + }, + update_dom: function() { + this._super(); + this.check_disable(); + }, + check_disable: function() { + if (this.force_disabled || !this.view.is_interactible_record()) { + this.$element.find("button").attr("disabled", "disabled"); + this.$element.find("button").css("color", "grey"); + } else { + this.$element.find("button").removeAttr("disabled"); + this.$element.find("button").css("color", ""); + } } }); openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ + template: 'WidgetLabel', init: function(view, node) { this.element_name = 'label_' + node.attrs.name; @@ -883,7 +952,6 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ this.template = "WidgetParagraph"; this.colspan = parseInt(this.node.attrs.colspan || 1, 10); } else { - this.template = "WidgetLabel"; this.colspan = 1; this.width = '1%'; this.decrease_max_width = 1; @@ -902,7 +970,7 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ var self = this; this.$element.find("label").dblclick(function() { var widget = self['for'] || self; - console.log(widget.element_id , widget); + console.log(widget.element_class , widget); window.w = widget; }); } @@ -1002,12 +1070,16 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f focus: function() { }, _build_view_fields_values: function() { - var a_dataset = this.view.dataset || {}; + var a_dataset = this.view.dataset; var fields_values = this.view.get_fields_values(); var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {}; fields_values.parent = parent_values; return fields_values; }, + _build_eval_context: function() { + var a_dataset = this.view.dataset; + return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values()); + }, /** * Builds a new context usable for operations related to fields by merging * the fields'context with the action's context. @@ -1020,7 +1092,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f var v_context = new openerp.web.CompoundContext(); _.each(v_contexts, function(x) {v_context.add(x);}); if (_.detect(v_contexts, function(x) {return !!x.__ref;})) { - var fields_values = this._build_view_fields_values(); + var fields_values = this._build_eval_context(); v_context.set_eval_context(fields_values); } // if there is a context on the node, overrides the model's context @@ -1031,7 +1103,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f var f_domain = this.field.domain || null; var v_domain = this.node.attrs.domain || []; if (!(v_domain instanceof Array) || true) { //TODO niv: remove || true - var fields_values = this._build_view_fields_values(); + var fields_values = this._build_eval_context(); v_domain = new openerp.web.CompoundDomain(v_domain).set_eval_context(fields_values); } // if there is a domain on the node, overrides the model's domain @@ -1040,10 +1112,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f }); openerp.web.form.FieldChar = openerp.web.form.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldChar"; - }, + template: 'FieldChar', start: function() { this._super.apply(this, arguments); this.$element.find('input').change(this.on_ui_change); @@ -1052,6 +1121,7 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({ this._super.apply(this, arguments); var show_value = openerp.web.format_value(value, this, ''); this.$element.find('input').val(show_value); + return show_value; }, update_dom: function() { this._super.apply(this, arguments); @@ -1076,10 +1146,7 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({ }); openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldEmail"; - }, + template: 'FieldEmail', start: function() { this._super.apply(this, arguments); this.$element.find('button').click(this.on_button_clicked); @@ -1090,18 +1157,11 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ } else { location.href = 'mailto:' + this.value; } - }, - set_value: function(value) { - this._super.apply(this, arguments); - this.$element.find('a').attr('href', 'mailto:' + this.$element.find('input').val()); } }); openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldUrl"; - }, + template: 'FieldUrl', start: function() { this._super.apply(this, arguments); this.$element.find('button').click(this.on_button_clicked); @@ -1252,10 +1312,7 @@ openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({ }); openerp.web.form.FieldText = openerp.web.form.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldText"; - }, + template: 'FieldText', start: function() { this._super.apply(this, arguments); this.$element.find('textarea').change(this.on_ui_change); @@ -1288,10 +1345,7 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({ }); openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldBoolean"; - }, + template: 'FieldBoolean', start: function() { var self = this; this._super.apply(this, arguments); @@ -1322,10 +1376,7 @@ openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({ }); openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldProgressBar"; - }, + template: 'FieldProgressBar', start: function() { this._super.apply(this, arguments); this.$element.find('div').progressbar({ @@ -1348,10 +1399,10 @@ openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({ }); openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ + template: 'FieldSelection', init: function(view, node) { var self = this; this._super(view, node); - this.template = "FieldSelection"; this.values = this.field.selection; _.each(this.values, function(v, i) { if (v[0] === false && v[1] === '') { @@ -1458,9 +1509,9 @@ openerp.web.form.dialog = function(content, options) { }; openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ + template: 'FieldMany2One', init: function(view, node) { this._super(view, node); - this.template = "FieldMany2One"; this.limit = 7; this.value = null; this.cm_id = _.uniqueId('m2o_cm_'); @@ -1785,10 +1836,10 @@ var commands = { } }; openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ + template: 'FieldOne2Many', multi_selection: false, init: function(view, node) { this._super(view, node); - this.template = "FieldOne2Many"; this.is_started = $.Deferred(); this.form_last_update = $.Deferred(); this.disable_utility_classes = true; @@ -1819,6 +1870,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ } if(view.view_type === "list") { view.options.selectable = self.multi_selection; + } else if (view.view_type === "form") { + view.options.not_interactible_on_create = true; } views.push(view); }); @@ -1878,6 +1931,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ switch (command[0]) { case commands.CREATE: obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix); + obj.defaults = {}; self.dataset.to_create.push(obj); self.dataset.cache.push(_.clone(obj)); ids.push(obj.id); @@ -1907,6 +1961,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ _.each(value, function(command) { var obj = {values: command}; obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix); + obj.defaults = {}; self.dataset.to_create.push(obj); self.dataset.cache.push(_.clone(obj)); ids.push(obj.id); @@ -1965,9 +2020,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }, validate: function() { this.invalid = false; - var self = this; - var view = self.viewmanager.views[self.viewmanager.active_view].controller; - if (self.viewmanager.active_view === "form") { + var view = this.viewmanager.views[this.viewmanager.active_view].controller; + if (this.viewmanager.active_view === "form") { for (var f in view.fields) { f = view.fields[f]; if (!f.is_valid()) { @@ -2014,7 +2068,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ self.o2m.dataset.on_change(); }); }, - parent_view: self.o2m.view + parent_view: self.o2m.view, + form_view_options: {'not_interactible_on_create':true} }, self.o2m.build_domain(), self.o2m.build_context()); pop.on_select_elements.add_last(function() { self.o2m.reload_current_view(); @@ -2030,7 +2085,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ parent_view: self.o2m.view, read_function: function() { return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); - } + }, + form_view_options: {'not_interactible_on_create':true} }); pop.on_write.add(function(id, data) { self.o2m.dataset.write(id, data, {}, function(r) { @@ -2041,10 +2097,10 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ }); openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ + template: 'FieldMany2Many', multi_selection: false, init: function(view, node) { this._super(view, node); - this.template = "FieldMany2Many"; this.list_id = _.uniqueId("many2many"); this.is_started = $.Deferred(); }, @@ -2145,6 +2201,8 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope * - alternative_form_view * - create_function (defaults to a naive saving behavior) * - parent_view + * - form_view_options + * - list_view_options */ select_element: function(model, options, domain, context) { var self = this; @@ -2209,7 +2267,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }); self.view_list = new openerp.web.form.SelectCreateListView(self, self.dataset, false, - {'deletable': false}); + _.extend({'deletable': false}, self.options.list_view_options || {})); self.view_list.popup = self; self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() { self.view_list.do_show(); @@ -2245,7 +2303,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.view_list.$element.hide(); } this.dataset.index = null; - this.view_form = new openerp.web.FormView(this, this.dataset, false); + this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } @@ -2320,6 +2378,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp * - auto_write (default true) * - read_function * - parent_view + * - form_view_options */ show_element: function(model, row_id, context, options) { this.model = model; @@ -2355,7 +2414,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp on_write_completed: function() {}, setup_form_view: function() { var self = this; - this.view_form = new openerp.web.FormView(this, this.dataset, false); + this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } @@ -2388,9 +2447,9 @@ openerp.web.form.FormOpenDataset = openerp.web.ReadOnlyDataSetSearch.extend({ }); openerp.web.form.FieldReference = openerp.web.form.Field.extend({ + template: 'FieldReference', init: function(view, node) { this._super(view, node); - this.template = "FieldReference"; this.fields_view = { fields: { selection: { @@ -2525,10 +2584,7 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ }); openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldBinaryFile"; - }, + template: 'FieldBinaryFile', set_value: function(value) { this._super.apply(this, arguments); var show_value = (value != null && value !== false) ? value : ''; @@ -2556,10 +2612,7 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ }); openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldBinaryImage"; - }, + template: 'FieldBinaryImage', start: function() { this._super.apply(this, arguments); this.$image = this.$element.find('img.oe-binary-image'); @@ -2634,6 +2687,93 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ } }); +openerp.web.form.WidgetNotebookReadonly = openerp.web.form.WidgetNotebook.extend({ + template: 'WidgetNotebook.readonly' +}); +openerp.web.form.FieldReadonly = openerp.web.form.Field.extend({ + +}); +openerp.web.form.FieldCharReadonly = openerp.web.form.FieldReadonly.extend({ + template: 'FieldChar.readonly', + set_value: function (value) { + this._super.apply(this, arguments); + var show_value = openerp.web.format_value(value, this, ''); + this.$element.find('div').text(show_value); + return show_value; + } +}); +openerp.web.form.FieldURIReadonly = openerp.web.form.FieldCharReadonly.extend({ + template: 'FieldURI.readonly', + scheme: null, + set_value: function (value) { + var displayed = this._super.apply(this, arguments); + this.$element.find('a') + .attr('href', this.scheme + ':' + displayed) + .text(displayed); + } +}); +openerp.web.form.FieldEmailReadonly = openerp.web.form.FieldURIReadonly.extend({ + scheme: 'mailto' +}); +openerp.web.form.FieldUrlReadonly = openerp.web.form.FieldURIReadonly.extend({ + set_value: function (value) { + var s = /(\w+):(\.+)/.match(value); + if (!(s[0] === 'http' || s[0] === 'https')) { return; } + this.scheme = s[0]; + this._super(s[1]); + } +}); +openerp.web.form.FieldBooleanReadonly = openerp.web.form.FieldCharReadonly.extend({ + set_value: function (value) { + this._super(value ? '\u2714' : '\u2718'); + } +}); +openerp.web.form.FieldSelectionReadonly = openerp.web.form.FieldReadonly.extend({ + template: 'FieldChar.readonly', + init: function(view, node) { + // lifted straight from r/w version + var self = this; + this._super(view, node); + this.values = this.field.selection; + _.each(this.values, function(v, i) { + if (v[0] === false && v[1] === '') { + self.values.splice(i, 1); + } + }); + this.values.unshift([false, '']); + }, + set_value: function (value) { + value = value === null ? false : value; + value = value instanceof Array ? value[0] : value; + var option = _(this.values) + .detect(function (record) { return record[0] === value; }); + this._super(value); + this.$element.find('div').text(option ? option[1] : this.values[0][1]); + } +}); +openerp.web.form.FieldMany2OneReadonly = openerp.web.form.FieldCharReadonly.extend({ + set_value: function (value) { + value = value || null; + this.invalid = false; + var self = this; + this.tmp_value = value; + self.update_dom(); + self.on_value_changed(); + var real_set_value = function(rval) { + self.$element.find('div').text(rval ? rval[1] : ''); + }; + if(typeof(value) === "number") { + var dataset = new openerp.web.DataSetStatic( + this, this.field.relation, self.build_context()); + dataset.name_get([value], function(data) { + real_set_value(data[0]); + }).fail(function() {self.tmp_value = undefined;}); + } else { + setTimeout(function() {real_set_value(value);}, 0); + } + } +}); + /** * Registry of form widgets, called by :js:`openerp.web.FormView` */ @@ -2666,6 +2806,22 @@ openerp.web.form.widgets = new openerp.web.Registry({ 'binary': 'openerp.web.form.FieldBinaryFile', 'statusbar': 'openerp.web.form.FieldStatus' }); +openerp.web.form.readonly = openerp.web.form.widgets.clone({ + 'notebook': 'openerp.web.form.WidgetNotebookReadonly', + 'char': 'openerp.web.form.FieldCharReadonly', + 'email': 'openerp.web.form.FieldEmailReadonly', + 'url': 'openerp.web.form.FieldUrlReadonly', + 'text': 'openerp.web.form.FieldCharReadonly', + 'text_wiki' : 'openerp.web.form.FieldCharReadonly', + 'date': 'openerp.web.form.FieldCharReadonly', + 'datetime': 'openerp.web.form.FieldCharReadonly', + 'selection' : 'openerp.web.form.FieldSelectionReadonly', + 'many2one': 'openerp.web.form.FieldMany2OneReadonly', + 'boolean': 'openerp.web.form.FieldBooleanReadonly', + 'float': 'openerp.web.form.FieldCharReadonly', + 'integer': 'openerp.web.form.FieldCharReadonly', + 'float_time': 'openerp.web.form.FieldCharReadonly' +}); }; diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index eeda44c4c99..8a9deaab228 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -49,6 +49,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# this.model = dataset.model; this.view_id = view_id; this.previous_colspan = null; + this.colors = null; this.columns = []; @@ -75,6 +76,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# } self.compute_aggregates(); }); + }, /** * Retrieves the view's number of records per page (|| section) @@ -131,6 +133,31 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# this.$element.addClass('oe-listview'); return this.reload_view(null, null, true); }, + /** + * Returns the color for the provided record in the current view (from the + * ``@colors`` attribute) + * + * @param {Record} record record for the current row + * @returns {String} CSS color declaration + */ + color_for: function (record) { + if (!this.colors) { return ''; } + var context = _.extend({}, record.attributes, { + uid: this.session.uid, + current_date: new Date().toString('yyyy-MM-dd') + // TODO: time, datetime, relativedelta + }); + for(var i=0, len=this.colors.length; i + + + + + +
@@ -253,33 +269,18 @@ - - - - - - + + t-att-value="widget.selected_login || ''" autofocus="true"/> + t-att-value="widget.selected_password || ''"/> @@ -330,6 +331,7 @@
+
@@ -614,7 +616,8 @@ + t-att-data-id="record.get('id')" + t-att-style="view.color_for(record)"> @@ -639,7 +642,7 @@ -
+
+
@@ -711,8 +715,7 @@ t-att-width="td.width" t-att-nowrap="td.nowrap or td.is_field_m2o? 'true' : undefined" t-att-valign="td.table ? 'top' : undefined" - t-att-id="td.element_id" - t-attf-class="oe_form_frame_cell #{td.classname}" + t-attf-class="oe_form_frame_cell #{td.classname} #{td.element_class}" > @@ -722,8 +725,8 @@
    -
  • - +
  • +
  • @@ -733,17 +736,23 @@ -
    +
    + + +

    + + +
    -