[MERGE] Merge with trunk

bzr revid: fme@openerp.com-20111003135613-8d2ep2fv3bv713t7
This commit is contained in:
Fabien Meghazi 2011-10-03 15:56:13 +02:00
commit 82006438fe
27 changed files with 1147 additions and 186 deletions

View File

@ -9,4 +9,3 @@ RE:^include/
RE:^share/
RE:^man/
RE:^lib/
logging.cfg

View File

@ -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

View File

@ -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",

View File

@ -3,6 +3,7 @@ from __future__ import with_statement
import functools
import logging
import urllib
import os
import pprint
import sys
@ -13,7 +14,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 +98,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 +310,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'
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 +349,12 @@ 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)
params = urllib.urlencode(dict(request.args, debug=''))
return werkzeug.utils.redirect(self.root + '?' + params, 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:]))

View File

@ -71,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.
@ -321,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):
"""

View File

@ -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

View File

@ -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("""<!DOCTYPE html>
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(['<script type="text/javascript" src="%s"></script>'%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(['<link rel="stylesheet" href="%s">'%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

View File

@ -0,0 +1,5 @@
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec
branch: default
latesttag: 0.2
latesttagdistance: 1

View File

@ -0,0 +1 @@
Parser and evaluator of Python expressions

View File

@ -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<http://docs.python.org/reference/datamodel.html#basic-customization>`_
for *all* supported operations, optimizations can come later
* Automatically type-wrap everything (for now anyway)

View File

@ -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<this.operators.length; ++i) {
repr.push(this.operators[i], this.expressions[i+1]);
}
return repr.join(' ') + ')';
}
var out = [this.id, this.first, this.second, this.third]
.filter(function (r){return r}).join(' ');
return '(' + out + ')';
}
};
function symbol(id, bp) {
bp = bp || 0;
var s = symbols[id];
if (s) {
if (bp > 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<len; ++i) {
if (str[index+1] === follow[i]) {
character += follow[i];
index++;
break;
}
}
}
if (character === ' ') {
return index+1;
} else if (character === '\0') {
this.tokens.push(symbols['(end)']);
return index + 1
} else if (character === '"' || character === "'") {
this.push('string');
return index + 1;
} else if (NUMBER.test(character)) {
this.push('number');
return index;
} else if (NAME_FIRST.test(character)) {
this.push('name');
return index;
} else if (character in symbols) {
this.tokens.push(create(symbols[character]));
return index + 1;
}
throw new Error("Tokenizing failure of <<" + str + ">> 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<len; ++i) {
if (this.values[i] === value) {
return true;
}
}
return false;
},
toJSON: function () {
return this.values;
}
});
exports.list = exports.tuple;
exports.dict = create(exports.object, {
toJSON: function () {
return this.values;
}
});
exports.parse = function (toks) {
var index = 0;
token = toks[0];
next = function () { return toks[++index]; };
return expression();
};
var evaluate_operator = function (operator, a, b) {
switch (operator) {
case '==': case 'is': return a === b;
case '!=': case 'is not': return a !== b;
case '<': return a < b;
case '<=': return a <= b;
case '>': 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<expr.operators.length; ++i) {
result = evaluate_operator(
expr.operators[i],
left,
left = exports.evaluate(expr.expressions[i+1], context));
if (!result) { return false; }
}
return true;
case '-':
if (expr.second) {
throw new Error('SyntaxError: binary [-] not implemented yet');
}
return -(exports.evaluate(expr.first, context));
case 'not':
return !(exports.evaluate(expr.first, context));
case 'and':
return (exports.evaluate(expr.first, context)
&& exports.evaluate(expr.second, context));
case 'or':
return (exports.evaluate(expr.first, context)
|| exports.evaluate(expr.second, context));
case '(':
if (expr.second) {
var fn = exports.evaluate(expr.first, context), args=[];
for (var jj=0; jj<expr.second.length; ++jj) {
args.push(exports.evaluate(
expr.second[jj], context));
}
return fn.apply(null, args);
}
var tuple_exprs = expr.first,
tuple_values = [];
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
tuple_values.push(exports.evaluate(
tuple_exprs[j], context));
}
return create(exports.tuple, {values: tuple_values});
case '[':
if (expr.second) {
throw new Error('SyntaxError: indexing not implemented yet');
}
var list_exprs = expr.first, list_values = [];
for (var k=0; k<list_exprs.length; ++k) {
list_values.push(exports.evaluate(
list_exprs[k], context));
}
return create(exports.list, {values: list_values});
case '{':
var dict_exprs = expr.first, dict_values = {};
for(var l=0; l<dict_exprs.length; ++l) {
dict_values[exports.evaluate(dict_exprs[l][0], context)] =
exports.evaluate(dict_exprs[l][1], context);
}
return create(exports.dict, {values: dict_values});
case '.':
if (expr.second.id !== '(name)') {
throw new Error('SyntaxError: ' + expr);
}
return exports.evaluate(expr.first, context)[expr.second.value];
default:
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
}
};
exports.eval = function (str, context) {
return exports.evaluate(
exports.parse(
exports.tokenize(
str)),
context);
}
})(typeof exports === 'undefined' ? py : exports);

View File

@ -0,0 +1,90 @@
var py = require('../lib/py.js'),
assert = require('assert');
// Literals
assert.strictEqual(py.eval('1'), 1);
assert.strictEqual(py.eval('None'), null);
assert.strictEqual(py.eval('False'), false);
assert.strictEqual(py.eval('True'), true);
assert.strictEqual(py.eval('"somestring"'), 'somestring');
assert.strictEqual(py.eval("'somestring'"), 'somestring');
assert.deepEqual(py.eval("()").toJSON(), []);
assert.deepEqual(py.eval("[]").toJSON(), []);
assert.deepEqual(py.eval("{}").toJSON(), {});
assert.deepEqual(py.eval("(None, True, False, 0, 1, 'foo')").toJSON(),
[null, true, false, 0, 1, 'foo']);
assert.deepEqual(py.eval("[None, True, False, 0, 1, 'foo']").toJSON(),
[null, true, false, 0, 1, 'foo']);
assert.deepEqual(py.eval("{'foo': 1, foo: 2}", {foo: 'bar'}).toJSON(),
{foo: 1, bar: 2});
// Equality tests
assert.ok(py.eval(
"foo == 'foo'", {foo: 'foo'}));
// Inequality
assert.ok(py.eval(
"foo != bar", {foo: 'foo', bar: 'bar'}));
// Comparisons
assert.ok(py.eval('3 < 5'));
assert.ok(py.eval('5 >= 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'}));;

View File

@ -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;
}

View File

@ -1,7 +1,6 @@
/*---------------------------------------------------------
* OpenERP Web chrome
*---------------------------------------------------------*/
openerp.web.chrome = function(openerp) {
var QWeb = openerp.web.qweb;
@ -300,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();
});
});

View File

@ -486,8 +486,6 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
self.user_context = result.context;
self.db = result.db;
self.session_save();
if (self.uid)
self.on_session_valid();
return true;
}).then(success_callback);
},

View File

@ -598,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);
@ -719,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);

View File

@ -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();
@ -74,16 +75,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
});
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);
@ -94,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);
@ -197,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);
@ -475,6 +494,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({
@ -622,6 +652,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
@ -635,11 +666,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;
@ -652,7 +685,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);
this.$element = this.view.$element.find('.' + this.element_class);
},
process_modifiers: function() {
var compute_domain = openerp.web.form.compute_domain;
@ -756,13 +789,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);
},
@ -784,11 +828,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')) {
@ -800,9 +844,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';
@ -812,9 +856,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, '');
@ -826,46 +871,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 = $('<div>' + self.node.attrs.confirm + '</div>').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;
@ -876,7 +949,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;
@ -895,7 +967,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;
});
}
@ -1037,10 +1109,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);
@ -1049,6 +1118,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);
@ -1073,10 +1143,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);
@ -1087,18 +1154,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);
@ -1249,10 +1309,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);
@ -1285,10 +1342,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);
@ -1319,10 +1373,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({
@ -1345,10 +1396,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] === '') {
@ -1455,9 +1506,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_');
@ -1782,10 +1833,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;
@ -1816,6 +1867,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);
});
@ -1964,9 +2017,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()) {
@ -2013,7 +2065,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();
@ -2029,7 +2082,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) {
@ -2040,10 +2094,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();
},
@ -2144,6 +2198,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;
@ -2208,7 +2264,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();
@ -2244,7 +2300,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);
}
@ -2319,6 +2375,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;
@ -2354,7 +2411,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);
}
@ -2387,9 +2444,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: {
@ -2524,10 +2581,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 : '';
@ -2555,10 +2609,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');
@ -2633,6 +2684,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`
*/
@ -2665,6 +2803,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'
});
};

View File

@ -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<len; ++i) {
var pair = this.colors[i],
color = pair[0],
expression = pair[1];
if (py.evaluate(expression, context)) {
return 'color: ' + color + ';';
}
// TODO: handle evaluation errors
}
return '';
},
/**
* Called after loading the list view's description, sets up such things
* as the view table's columns, renders the table itself and hooks up the
@ -159,6 +186,17 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.fields_view = data;
this.name = "" + this.fields_view.arch.attrs.string;
if (this.fields_view.arch.attrs.colors) {
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
.compact()
.map(function(color_pair) {
var pair = color_pair.split(':'),
color = pair[0],
expr = pair[1];
return [color, py.parse(py.tokenize(expr)), expr];
}).value();
}
this.setup_columns(this.fields_view.fields, grouped);
this.$element.html(QWeb.render("ListView", this));
@ -445,7 +483,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
do_select: function (ids, records) {
this.$element.find('.oe-list-delete')
.attr('disabled', !ids.length);
if(ids.length) {
this.sidebar.do_unfold();
} else {
this.sidebar.do_fold();
}
if (!records.length) {
this.compute_aggregates();
return;
@ -1333,10 +1375,10 @@ var Record = openerp.web.Class.extend(/** @lends Record# */{
* @returns {Object} record displayable in a form view
*/
toForm: function () {
var form_data = {};
_(this.attributes).each(function (value, key) {
form_data[key] = {value: value};
});
var form_data = {}, attrs = this.attributes;
for(var k in attrs) {
form_data[k] = {value: attrs[k]};
}
return {data: form_data};
}

View File

@ -86,7 +86,7 @@ db.web.ActionManager = db.web.Widget.extend({
console.log("Action manager can't handle action of type " + action.type, action);
return;
}
this[type](action, on_close);
return this[type](action, on_close);
},
ir_actions_act_window: function (action, on_close) {
if (action.target === 'new') {
@ -468,7 +468,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
* Intercept do_action resolution from children views
*/
on_action_executed: function () {
new db.web.DataSet(this, 'res.log')
return new db.web.DataSet(this, 'res.log')
.call('get', [], this.do_display_log);
},
/**
@ -768,31 +768,31 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{
var self = this;
var result_handler = function () {
if (on_closed) { on_closed.apply(null, arguments); }
self.widget_parent.on_action_executed.apply(null, arguments);
return self.widget_parent.on_action_executed.apply(null, arguments);
};
var handler = function (r) {
var action = r.result;
if (action && action.constructor == Object) {
self.rpc('/web/session/eval_domain_and_context', {
return self.rpc('/web/session/eval_domain_and_context', {
contexts: [dataset.get_context(), action.context || {}, {
active_id: record_id || false,
active_ids: [record_id || false],
active_model: dataset.model
}],
domains: []
}, function (results) {
}).pipe(function (results) {
action.context = results.context
self.do_action(action, result_handler);
return self.do_action(action, result_handler);
});
} else {
result_handler();
return result_handler();
}
};
var context = new db.web.CompoundContext(dataset.get_context(), action_data.context || {});
if (action_data.special) {
handler({result: {"type":"ir.actions.act_window_close"}});
return handler({result: {"type":"ir.actions.act_window_close"}});
} else if (action_data.type=="object") {
return dataset.call_button(action_data.name, [[record_id], context], handler);
} else if (action_data.type=="action") {

View File

@ -616,7 +616,8 @@
</t>
</t>
<tr t-name="ListView.row" t-att-class="row_parity"
t-att-data-id="record.get('id')">
t-att-data-id="record.get('id')"
t-att-style="view.color_for(record)">
<t t-foreach="columns" t-as="column">
<td t-if="column.meta">
@ -641,7 +642,7 @@
<t t-raw="frame.render()"/>
</t>
<t t-name="FormView">
<div class="oe_form_header" t-att-id="view.element_id + '_header'">
<div class="oe_form_header">
<div class="oe_form_buttons" t-if="view.options.action_buttons !== false">
<!--<button type="button" class="oe_form_button_save">
<span class="oe_form_on_update">Save</span>
@ -654,6 +655,7 @@
<!--<button type="button" class="oe_form_button_cancel">Cancel</button>-->
<button type="button" class="oe_form_button_new">New</button>
<button type="button" class="oe_form_button_duplicate oe_form_on_update">Duplicate</button>
<button type="button" class="oe_form_button_toggle">Readonly/Editable</button>
</div>
<div class="oe_form_pager" t-if="view.options.pager !== false">
<button type="button" data-pager-action="first">First</button>
@ -713,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}"
>
<t t-raw="td.render()"/>
</td>
@ -724,8 +725,8 @@
</t>
<t t-name="WidgetNotebook">
<ul>
<li t-foreach="widget.pages" t-as="page" t-att-id="page.element_tab_id">
<a t-att-href="'#' + page.element_id">
<li t-foreach="widget.pages" t-as="page">
<a href="#">
<t t-esc="page.string"/>
</a>
</li>
@ -735,17 +736,23 @@
</t>
</t>
<t t-name="WidgetNotebookPage">
<div t-att-id="widget.element_id">
<div>
<t t-call="WidgetFrame"/>
</div>
</t>
<t t-name="WidgetNotebook.readonly">
<t t-foreach="widget.pages" t-as="page">
<h3><t t-esc="page.string"/></h3>
<t t-raw="page.render()"/>
</t>
</t>
<t t-name="WidgetSeparator">
<div t-if="widget.orientation !== 'vertical'" t-att-class="'separator ' + widget.orientation">
<t t-esc="widget.string"/>
</div>
</t>
<t t-name="WidgetLabel">
<label t-att-for="widget.element_id + '_field'"
<label t-att-for="widget.element_id"
t-att-class="'oe_label' + (widget.help ? '_help' : '')"
t-att-title="widget.help">
<t t-esc="widget.string"/>
@ -759,12 +766,22 @@
<t t-name="FieldChar">
<input type="text" size="1"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
/>
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
</t>
<t t-name="FieldChar.readonly">
<div
t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%">
</div>
</t>
<t t-name="FieldURI.readonly">
<a href="#">#</a>
</t>
<t t-name="FieldEmail">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
@ -796,8 +813,8 @@
<t t-name="FieldText">
<textarea rows="6"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
></textarea>
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
@ -817,7 +834,7 @@
<select
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-attf-class="field_#{widget.type} #{widget.element_class}"
style="width: 100%">
<t t-foreach="widget.values" t-as="option">
<option><t t-esc="option[1]"/></option>
@ -825,9 +842,9 @@
</select>
</t>
<t t-name="FieldMany2One">
<div t-att-id="widget.element_id" class="oe-m2o">
<input t-att-id="widget.element_id + '_input'" type="text" size="1" style="width: 100%;"/>
<span class="oe-m2o-drop-down-button" t-att-id="widget.element_id + '_drop_down'">
<div t-att-class="widget.element_class" class="oe-m2o">
<input type="text" size="1" style="width: 100%;"/>
<span class="oe-m2o-drop-down-button">
<img src="/web/static/src/img/down-arrow.png" /></span>
<span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'">
<img src="/web/static/src/img/icons/gtk-index.png"/></span>
@ -850,8 +867,6 @@
</ul>
</t>
<t t-name="FieldOne2Many">
<div t-att-id="widget.element_id">
</div>
</t>
<t t-name="FieldMany2Many">
<div t-att-id="widget.list_id"></div>
@ -859,10 +874,10 @@
<t t-name="FieldReference">
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="oe_frame oe_forms">
<tr>
<td t-att-id="widget.selection.element_id" class="oe_form_frame_cell oe_form_selection">
<td t-attf-class="oe_form_frame_cell oe_form_selection #{widget.selection.element_class}">
<t t-raw="widget.selection.render()"/>
</td>
<td t-att-id="widget.m2o.element_id" class="oe_form_frame_cell oe_form_many2one" nowrap="true">
<td class="oe_form_frame_cell oe_form_many2one #{widget.selection.element_class}" nowrap="true">
<t t-raw="widget.m2o.render()"/>
</td>
</tr>
@ -872,7 +887,7 @@
<input type="checkbox"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"/>
t-attf-class="field_#{widget.type} #{widget.element_class}"/>
</t>
<t t-name="FieldProgressBar">
<div t-opentag="true" class="oe-progressbar">
@ -887,7 +902,7 @@
t-att-border="widget.readonly ? 0 : 1"
t-att-id="widget.element_id + '_field'"
t-att-name="widget.name"
t-att-class="'field_' + widget.type"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
/>
@ -935,7 +950,7 @@
<input type="text" size="1"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type" style="width: 100%"
t-attf-class="field_#{widget.type} #{widget.element_class}" style="width: 100%"
/>
</td>
<td class="oe-binary" nowrap="true">
@ -980,7 +995,7 @@
</t>
<t t-name="WidgetButton">
<button type="button"
t-att-id="widget.element_id + '_button'"
t-attf-class="#{widget.element_class}"
t-att-title="widget.help"
style="width: 100%" class="button">
<img t-if="widget.node.attrs.icon" t-att-src="'/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/>

View File

@ -94,8 +94,10 @@ openerp.web_default_home = function (openerp) {
})
},
install_module: function (module_name) {
var self = this;
var Modules = new openerp.web.DataSetSearch(
this, 'ir.module.module', null, [['name', '=', module_name], ['state', '=', 'uninstalled']]);
this, 'ir.module.module', null,
[['name', '=', module_name], ['state', '=', 'uninstalled']]);
var Upgrade = new openerp.web.DataSet(this, 'base.module.upgrade');
$.blockUI({message:'<img src="/web/static/src/img/throbber2.gif">'});
@ -105,13 +107,21 @@ openerp.web_default_home = function (openerp) {
[_.pluck(records, 'id'), 'to install', ['uninstalled']],
function () {
Upgrade.call('upgrade_module', [[]], function () {
$.unblockUI();
// TODO: less brutal reloading
window.location.reload(true);
self.run_configuration_wizards();
});
}
)
});
},
run_configuration_wizards: function () {
var self = this;
new openerp.web.DataSet(this, 'res.config').call('start', [[]], function (action) {
$.unblockUI();
self.do_action(action, function () {
// TODO: less brutal reloading
window.location.reload(true);
});
});
}
});
};

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,8 @@
{
"name": "Tests",
"version": "2.0",
"depends": [],
"js": ["static/src/js/*.js"],
"css": ['static/src/css/*.css'],
'active': True,
}

View File

@ -0,0 +1,3 @@
.oe-bunchaforms > div {
float: left;
}

View File

@ -0,0 +1,37 @@
openerp.web_tests = function (db) {
db.web.client_actions.add(
'buncha-forms', 'instance.web_tests.BunchaForms');
db.web_tests = {};
db.web_tests.BunchaForms = db.web.Widget.extend({
init: function (parent) {
this._super(parent);
this.dataset = new db.web.DataSetSearch(this, 'test.listview.relations');
this.form = new db.web.FormView(this, this.dataset, false, {
action_buttons: false,
pager: false
});
this.form.registry = db.web.form.readonly;
},
render: function () {
return '<div class="oe-bunchaforms"></div>';
},
start: function () {
$.when(
this.dataset.read_slice(),
this.form.appendTo(this.$element)).then(this.on_everything_loaded);
},
on_everything_loaded: function (slice) {
var records = slice[0].records;
if (!records.length) {
this.form.on_record_loaded({});
return;
}
this.form.on_record_loaded(records[0]);
_(records.slice(1)).each(function (record, index) {
this.dataset.index = index+1;
this.form.reposition($('<div>').appendTo(this.$element));
this.form.on_record_loaded(record);
}, this);
}
});
};

27
logging.json Normal file
View File

@ -0,0 +1,27 @@
{
"version": 1,
"formatters": {
"simple": {
"format": "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"web": {
},
"web.common.openerplib": {
"level": "INFO"
}
},
"root": {
"level": "DEBUG",
"handlers": ["console"]
}
}

View File

@ -2,6 +2,7 @@
import optparse
import os
import sys
import json
import tempfile
import logging
import logging.config
@ -26,6 +27,8 @@ optparser.add_option("--db-filter", dest="dbfilter", default='.*',
help="Filter listed database", metavar="REGEXP")
optparser.add_option('--addons-path', dest='addons_path', default=[path_addons], action='append',
help="Path do addons directory", metavar="PATH")
optparser.add_option('--load', dest='server_wide_modules', default=['web'], action='append',
help="Load a additional module before login (by default only 'web' is loaded)", metavar="MODULE")
server_options = optparse.OptionGroup(optparser, "Server configuration")
server_options.add_option("-p", "--port", dest="socket_port", default=8002,
@ -48,7 +51,7 @@ logging_opts = optparse.OptionGroup(optparser, "Logging")
logging_opts.add_option("--log-level", dest="log_level", type="choice",
default='debug', help="Global logging level", metavar="LOG_LEVEL",
choices=['debug', 'info', 'warning', 'error', 'critical'])
logging_opts.add_option("--log-config", dest="log_config",
logging_opts.add_option("--log-config", dest="log_config", default=os.path.join(os.path.dirname(__file__), "logging.json"),
help="Logging configuration file", metavar="FILE")
optparser.add_option_group(logging_opts)
@ -60,10 +63,13 @@ if __name__ == "__main__":
os.environ["TZ"] = "UTC"
if not options.log_config:
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
if sys.version_info >= (2, 7):
with open(options.log_config) as file:
dct = json.load(file)
logging.config.dictConfig(dct)
logging.getLogger("").setLevel(getattr(logging, options.log_level.upper()))
else:
logging.config.fileConfig(options.log_config)
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
app = web.common.dispatch.Root(options)