[MERGE] Merged trunk.
bzr revid: jra@tinyerp.com-20121119054138-lkrx5jo1tgbr71ek
This commit is contained in:
+13
-18
@@ -1,19 +1,14 @@
|
||||
.*.swp
|
||||
.bzrignore
|
||||
.idea
|
||||
.project
|
||||
.pydevproject
|
||||
.ropeproject
|
||||
.settings
|
||||
.DS_Store
|
||||
openerp/addons/*
|
||||
openerp/filestore*
|
||||
.Python
|
||||
*.pyc
|
||||
*.pyo
|
||||
bin/*
|
||||
.*
|
||||
*.egg-info
|
||||
*.orig
|
||||
*.vim
|
||||
build/
|
||||
include/
|
||||
lib/
|
||||
share/
|
||||
doc/_build/*
|
||||
RE:^bin/
|
||||
RE:^dist/
|
||||
RE:^include/
|
||||
|
||||
RE:^share/
|
||||
RE:^man/
|
||||
RE:^lib/
|
||||
|
||||
RE:^addons/\w+/doc/_build/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
'name': 'Web',
|
||||
'category': 'Hidden',
|
||||
'version': '7.0.1.0',
|
||||
'description':
|
||||
"""
|
||||
OpenERP Web core module.
|
||||
@@ -38,8 +39,9 @@ This module provides the core of the OpenERP Web Client.
|
||||
"static/lib/underscore/underscore.string.js",
|
||||
"static/lib/backbone/backbone.js",
|
||||
"static/lib/cleditor/jquery.cleditor.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/src/js/boot.js",
|
||||
"static/src/js/testing.js",
|
||||
"static/src/js/corelib.js",
|
||||
"static/src/js/coresetup.js",
|
||||
"static/src/js/dates.js",
|
||||
@@ -67,4 +69,21 @@ This module provides the core of the OpenERP Web Client.
|
||||
'qweb' : [
|
||||
"static/src/xml/*.xml",
|
||||
],
|
||||
'test': [
|
||||
"static/test/testing.js",
|
||||
"static/test/class.js",
|
||||
"static/test/registry.js",
|
||||
"static/test/form.js",
|
||||
"static/test/list-utils.js",
|
||||
"static/test/formats.js",
|
||||
"static/test/rpc.js",
|
||||
"static/test/evals.js",
|
||||
"static/test/search.js",
|
||||
"static/test/Widget.js",
|
||||
"static/test/list.js",
|
||||
"static/test/list-editable.js",
|
||||
"static/test/mutex.js"
|
||||
],
|
||||
'bootstrap': True,
|
||||
'twitter': False,
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from . import main
|
||||
from . import testing
|
||||
|
||||
+76
-212
@@ -81,57 +81,6 @@ def rjsmin(script):
|
||||
).strip()
|
||||
return result
|
||||
|
||||
def sass2scss(src):
|
||||
# Validated by diff -u of sass2scss against:
|
||||
# sass-convert -F sass -T scss openerp.sass openerp.scss
|
||||
block = []
|
||||
sass = ('', block)
|
||||
reComment = re.compile(r'//.*$')
|
||||
reIndent = re.compile(r'^\s+')
|
||||
reIgnore = re.compile(r'^\s*(//.*)?$')
|
||||
reFixes = { re.compile(r'\(\((.*)\)\)') : r'(\1)', }
|
||||
lastLevel = 0
|
||||
prevBlocks = {}
|
||||
for l in src.split('\n'):
|
||||
l = l.rstrip()
|
||||
if reIgnore.search(l): continue
|
||||
l = reComment.sub('', l)
|
||||
l = l.rstrip()
|
||||
indent = reIndent.match(l)
|
||||
level = indent.end() if indent else 0
|
||||
l = l[level:]
|
||||
if level>lastLevel:
|
||||
prevBlocks[lastLevel] = block
|
||||
newBlock = []
|
||||
block[-1] = (block[-1], newBlock)
|
||||
block = newBlock
|
||||
elif level<lastLevel:
|
||||
block = prevBlocks[level]
|
||||
lastLevel = level
|
||||
if not l: continue
|
||||
# Fixes
|
||||
for ereg, repl in reFixes.items():
|
||||
l = ereg.sub(repl if type(repl)==str else repl(), l)
|
||||
block.append(l)
|
||||
|
||||
def write(sass, level=-1):
|
||||
out = ""
|
||||
indent = ' '*level
|
||||
if type(sass)==tuple:
|
||||
if level>=0:
|
||||
out += indent+sass[0]+" {\n"
|
||||
for e in sass[1]:
|
||||
out += write(e, level+1)
|
||||
if level>=0:
|
||||
out = out.rstrip(" \n")
|
||||
out += ' }\n'
|
||||
if level==0:
|
||||
out += "\n"
|
||||
else:
|
||||
out += indent+sass+";\n"
|
||||
return out
|
||||
return write(sass)
|
||||
|
||||
def db_list(req):
|
||||
dbs = []
|
||||
proxy = req.session.proxy("db")
|
||||
@@ -142,6 +91,17 @@ def db_list(req):
|
||||
dbs = [i for i in dbs if re.match(r, i)]
|
||||
return dbs
|
||||
|
||||
def db_monodb(req):
|
||||
# if only one db is listed returns it else return False
|
||||
try:
|
||||
dbs = db_list(req)
|
||||
if len(dbs) == 1:
|
||||
return dbs[0]
|
||||
except xmlrpclib.Fault:
|
||||
# ignore access denied
|
||||
pass
|
||||
return False
|
||||
|
||||
def module_topological_sort(modules):
|
||||
""" Return a list of module names sorted so that their dependencies of the
|
||||
modules are listed before the module itself
|
||||
@@ -231,22 +191,14 @@ def module_installed_bypass_session(dbname):
|
||||
|
||||
def module_boot(req):
|
||||
server_wide_modules = openerp.conf.server_wide_modules or ['web']
|
||||
return [m for m in server_wide_modules if m in openerpweb.addons_manifest]
|
||||
# TODO the following will be enabled once we separate the module code and translation loading
|
||||
serverside = []
|
||||
dbside = []
|
||||
for i in server_wide_modules:
|
||||
if i in openerpweb.addons_manifest:
|
||||
serverside.append(i)
|
||||
# if only one db load every module at boot
|
||||
dbs = []
|
||||
try:
|
||||
dbs = db_list(req)
|
||||
except xmlrpclib.Fault:
|
||||
# ignore access denied
|
||||
pass
|
||||
if len(dbs) == 1:
|
||||
dbside = module_installed_bypass_session(dbs[0])
|
||||
monodb = db_monodb(req)
|
||||
if monodb:
|
||||
dbside = module_installed_bypass_session(monodb)
|
||||
dbside = [i for i in dbside if i not in serverside]
|
||||
addons = serverside + dbside
|
||||
return addons
|
||||
@@ -306,10 +258,20 @@ def concat_files(file_list, reader=None, intersperse=""):
|
||||
files_concat = intersperse.join(files_content)
|
||||
return files_concat, checksum.hexdigest()
|
||||
|
||||
concat_js_cache = {}
|
||||
|
||||
def concat_js(file_list):
|
||||
content, checksum = concat_files(file_list, intersperse=';')
|
||||
content = rjsmin(content)
|
||||
return content, checksum
|
||||
if checksum in concat_js_cache:
|
||||
content = concat_js_cache[checksum]
|
||||
else:
|
||||
content = rjsmin(content)
|
||||
concat_js_cache[checksum] = content
|
||||
return content, checksum
|
||||
|
||||
def fs2web(path):
|
||||
"""convert FS path into web path"""
|
||||
return '/'.join(path.split(os.path.sep))
|
||||
|
||||
def manifest_glob(req, addons, key):
|
||||
if addons is None:
|
||||
@@ -326,7 +288,7 @@ def manifest_glob(req, addons, key):
|
||||
globlist = manifest.get(key, [])
|
||||
for pattern in globlist:
|
||||
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
|
||||
r.append((path, path[len(addons_path):]))
|
||||
r.append((path, fs2web(path[len(addons_path):])))
|
||||
return r
|
||||
|
||||
def manifest_list(req, mods, extension):
|
||||
@@ -549,7 +511,7 @@ def _local_web_translations(trans_file):
|
||||
messages.append({'id': x.id, 'string': x.string})
|
||||
return messages
|
||||
|
||||
def from_elementtree(el, preserve_whitespaces=False):
|
||||
def xml2json_from_elementtree(el, preserve_whitespaces=False):
|
||||
""" xml2json-direct
|
||||
Simple and straightforward XML-to-JSON converter in Python
|
||||
New BSD Licensed
|
||||
@@ -569,13 +531,12 @@ def from_elementtree(el, preserve_whitespaces=False):
|
||||
if el.text and (preserve_whitespaces or el.text.strip() != ''):
|
||||
kids.append(el.text)
|
||||
for kid in el:
|
||||
kids.append(from_elementtree(kid, preserve_whitespaces))
|
||||
kids.append(xml2json_from_elementtree(kid, preserve_whitespaces))
|
||||
if kid.tail and (preserve_whitespaces or kid.tail.strip() != ''):
|
||||
kids.append(kid.tail)
|
||||
res["children"] = kids
|
||||
return res
|
||||
|
||||
|
||||
def content_disposition(filename, req):
|
||||
filename = filename.encode('utf8')
|
||||
escaped = urllib2.quote(filename)
|
||||
@@ -612,8 +573,7 @@ html_template = """<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lte IE 8]>
|
||||
<script type="text/javascript"
|
||||
src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
|
||||
<script>
|
||||
var test = function() {
|
||||
CFInstall.check({
|
||||
@@ -687,8 +647,7 @@ class WebClient(openerpweb.Controller):
|
||||
data = fp.read().decode('utf-8')
|
||||
|
||||
path = file_map[f]
|
||||
# convert FS path into web path
|
||||
web_dir = '/'.join(os.path.dirname(path).split(os.path.sep))
|
||||
web_dir = os.path.dirname(path)
|
||||
|
||||
data = re.sub(
|
||||
rx_import,
|
||||
@@ -741,21 +700,20 @@ class WebClient(openerpweb.Controller):
|
||||
until we have established a valid session. This is meant only
|
||||
for translating the login page and db management chrome, using
|
||||
the browser's language. """
|
||||
lang = req.httprequest.accept_languages.best or 'en'
|
||||
# For performance reasons we only load a single translation, so for
|
||||
# sub-languages (that should only be partially translated) we load the
|
||||
# main language PO instead - that should be enough for the login screen.
|
||||
if '-' in lang: # RFC2616 uses '-' separators for sublanguages
|
||||
lang = lang.split('-')[0]
|
||||
lang = req.lang.split('_')[0]
|
||||
|
||||
translations_per_module = {}
|
||||
for addon_name in mods:
|
||||
addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
|
||||
f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po")
|
||||
if not os.path.exists(f_name):
|
||||
continue
|
||||
translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
|
||||
|
||||
if openerpweb.addons_manifest[addon_name].get('bootstrap'):
|
||||
addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
|
||||
f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po")
|
||||
if not os.path.exists(f_name):
|
||||
continue
|
||||
translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
|
||||
|
||||
return {"modules": translations_per_module,
|
||||
"lang_parameters": None}
|
||||
|
||||
@@ -814,21 +772,36 @@ class Database(openerpweb.Controller):
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get_list(self, req):
|
||||
dbs = db_list(req)
|
||||
return {"db_list": dbs}
|
||||
return db_list(req)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def create(self, req, fields):
|
||||
params = dict(map(operator.itemgetter('name', 'value'), fields))
|
||||
create_attrs = (
|
||||
return req.session.proxy("db").create_database(
|
||||
params['super_admin_pwd'],
|
||||
params['db_name'],
|
||||
bool(params.get('demo_data')),
|
||||
params['db_lang'],
|
||||
params['create_admin_pwd']
|
||||
params['create_admin_pwd'])
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def duplicate(self, req, fields):
|
||||
params = dict(map(operator.itemgetter('name', 'value'), fields))
|
||||
return req.session.proxy("db").duplicate_database(
|
||||
params['super_admin_pwd'],
|
||||
params['db_original_name'],
|
||||
params['db_name'])
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def duplicate(self, req, fields):
|
||||
params = dict(map(operator.itemgetter('name', 'value'), fields))
|
||||
duplicate_attrs = (
|
||||
params['super_admin_pwd'],
|
||||
params['db_original_name'],
|
||||
params['db_name'],
|
||||
)
|
||||
|
||||
return req.session.proxy("db").create_database(*create_attrs)
|
||||
return req.session.proxy("db").duplicate_database(*duplicate_attrs)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def drop(self, req, fields):
|
||||
@@ -936,10 +909,7 @@ class Session(openerpweb.Controller):
|
||||
@openerpweb.jsonrequest
|
||||
def get_lang_list(self, req):
|
||||
try:
|
||||
return {
|
||||
'lang_list': (req.session.proxy("db").list_lang() or []),
|
||||
'error': ""
|
||||
}
|
||||
return req.session.proxy("db").list_lang() or []
|
||||
except Exception, e:
|
||||
return {"error": e, "title": "Languages"}
|
||||
|
||||
@@ -1298,17 +1268,6 @@ class DataSet(openerpweb.Controller):
|
||||
m.write(id, { field: i + offset })
|
||||
return True
|
||||
|
||||
class DataGroup(openerpweb.Controller):
|
||||
_cp_path = "/web/group"
|
||||
@openerpweb.jsonrequest
|
||||
def read(self, req, model, fields, group_by_fields, domain=None, sort=None):
|
||||
Model = req.session.model(model)
|
||||
context, domain = eval_context_and_domain(req.session, req.context, domain)
|
||||
|
||||
return Model.read_group(
|
||||
domain or [], fields, group_by_fields, 0, False,
|
||||
dict(context, group_by=group_by_fields), sort or False)
|
||||
|
||||
class View(openerpweb.Controller):
|
||||
_cp_path = "/web/view"
|
||||
|
||||
@@ -1342,7 +1301,7 @@ class View(openerpweb.Controller):
|
||||
xml = self.transform_view(arch, session, evaluation_context)
|
||||
else:
|
||||
xml = ElementTree.fromstring(arch)
|
||||
fvg['arch'] = from_elementtree(xml, preserve_whitespaces)
|
||||
fvg['arch'] = xml2json_from_elementtree(xml, preserve_whitespaces)
|
||||
|
||||
if 'id' in fvg['fields']:
|
||||
# Special case for id's
|
||||
@@ -1516,11 +1475,24 @@ class Binary(openerpweb.Controller):
|
||||
try:
|
||||
if not id:
|
||||
res = Model.default_get([field], context).get(field)
|
||||
image_data = base64.b64decode(res)
|
||||
image_base64 = res
|
||||
else:
|
||||
res = Model.read([id], [last_update, field], context)[0]
|
||||
retag = hashlib.md5(res.get(last_update)).hexdigest()
|
||||
image_data = base64.b64decode(res.get(field))
|
||||
image_base64 = res.get(field)
|
||||
|
||||
if kw.get('resize'):
|
||||
resize = kw.get('resize').split(',');
|
||||
if len(resize) == 2 and int(resize[0]) and int(resize[1]):
|
||||
width = int(resize[0])
|
||||
height = int(resize[1])
|
||||
# resize maximum 500*500
|
||||
if width > 500: width = 500
|
||||
if height > 500: height = 500
|
||||
image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
|
||||
|
||||
image_data = base64.b64decode(image_base64)
|
||||
|
||||
except (TypeError, xmlrpclib.Fault):
|
||||
image_data = self.placeholder(req)
|
||||
headers.append(('ETag', retag))
|
||||
@@ -2010,112 +1982,4 @@ class Reports(View):
|
||||
('Content-Length', len(report))],
|
||||
cookies={'fileToken': int(token)})
|
||||
|
||||
class Import(View):
|
||||
_cp_path = "/web/import"
|
||||
|
||||
def fields_get(self, req, model):
|
||||
Model = req.session.model(model)
|
||||
fields = Model.fields_get(False, req.session.eval_context(req.context))
|
||||
return fields
|
||||
|
||||
@openerpweb.httprequest
|
||||
def detect_data(self, req, csvfile, csvsep=',', csvdel='"', csvcode='utf-8', jsonp='callback'):
|
||||
try:
|
||||
data = list(csv.reader(
|
||||
csvfile, quotechar=str(csvdel), delimiter=str(csvsep)))
|
||||
except csv.Error, e:
|
||||
csvfile.seek(0)
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {
|
||||
'message': 'Error parsing CSV file: %s' % e,
|
||||
# decodes each byte to a unicode character, which may or
|
||||
# may not be printable, but decoding will succeed.
|
||||
# Otherwise simplejson will try to decode the `str` using
|
||||
# utf-8, which is very likely to blow up on characters out
|
||||
# of the ascii range (in range [128, 256))
|
||||
'preview': csvfile.read(200).decode('iso-8859-1')}}))
|
||||
|
||||
try:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps(
|
||||
{'records': data[:10]}, encoding=csvcode))
|
||||
except UnicodeDecodeError:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({
|
||||
'message': u"Failed to decode CSV file using encoding %s, "
|
||||
u"try switching to a different encoding" % csvcode
|
||||
}))
|
||||
|
||||
@openerpweb.httprequest
|
||||
def import_data(self, req, model, csvfile, csvsep, csvdel, csvcode, jsonp,
|
||||
meta):
|
||||
modle_obj = req.session.model(model)
|
||||
skip, indices, fields = operator.itemgetter('skip', 'indices', 'fields')(
|
||||
simplejson.loads(meta))
|
||||
|
||||
error = None
|
||||
if not (csvdel and len(csvdel) == 1):
|
||||
error = u"The CSV delimiter must be a single character"
|
||||
|
||||
if not indices and fields:
|
||||
error = u"You must select at least one field to import"
|
||||
|
||||
if error:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {'message': error}}))
|
||||
|
||||
# skip ignored records (@skip parameter)
|
||||
# then skip empty lines (not valid csv)
|
||||
# nb: should these operations be reverted?
|
||||
rows_to_import = itertools.ifilter(
|
||||
None,
|
||||
itertools.islice(
|
||||
csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep)),
|
||||
skip, None))
|
||||
|
||||
# if only one index, itemgetter will return an atom rather than a tuple
|
||||
if len(indices) == 1: mapper = lambda row: [row[indices[0]]]
|
||||
else: mapper = operator.itemgetter(*indices)
|
||||
|
||||
data = None
|
||||
error = None
|
||||
try:
|
||||
# decode each data row
|
||||
data = [
|
||||
[record.decode(csvcode) for record in row]
|
||||
for row in itertools.imap(mapper, rows_to_import)
|
||||
# don't insert completely empty rows (can happen due to fields
|
||||
# filtering in case of e.g. o2m content rows)
|
||||
if any(row)
|
||||
]
|
||||
except UnicodeDecodeError:
|
||||
error = u"Failed to decode CSV file using encoding %s" % csvcode
|
||||
except csv.Error, e:
|
||||
error = u"Could not process CSV file: %s" % e
|
||||
|
||||
# If the file contains nothing,
|
||||
if not data:
|
||||
error = u"File to import is empty"
|
||||
if error:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {'message': error}}))
|
||||
|
||||
try:
|
||||
(code, record, message, _nope) = modle_obj.import_data(
|
||||
fields, data, 'init', '', False,
|
||||
req.session.eval_context(req.context))
|
||||
except xmlrpclib.Fault, e:
|
||||
error = {"message": u"%s, %s" % (e.faultCode, e.faultString)}
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error':error}))
|
||||
|
||||
if code != -1:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'success':True}))
|
||||
|
||||
msg = u"Error during import: %s\n\nTrying to import record %r" % (
|
||||
message, record)
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {'message':msg}}))
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
# coding=utf-8
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import glob
|
||||
import itertools
|
||||
import json
|
||||
import operator
|
||||
import os
|
||||
|
||||
from mako.template import Template
|
||||
from openerp.modules import module
|
||||
|
||||
from .main import module_topological_sort
|
||||
from ..http import Controller, httprequest
|
||||
|
||||
NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>OpenERP Testing</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/web/tests" method="GET">
|
||||
<button name="mod" value="*">Run all tests</button>
|
||||
<ul>
|
||||
% for name, module in modules:
|
||||
<li>${name} <button name="mod" value="${module}">
|
||||
Run Tests</button></li>
|
||||
% endfor
|
||||
</ul>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
NOTFOUND = Template(u"""
|
||||
<p>Unable to find the module [${module}], please check that the module
|
||||
name is correct and the module is on OpenERP's path.</p>
|
||||
<a href="/web/tests"><< Back to tests</a>
|
||||
""")
|
||||
TESTING = Template(u"""<!DOCTYPE html>
|
||||
<html style="height: 100%">
|
||||
<%def name="to_path(module, p)">/${module}/${p}</%def>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>OpenERP Web Tests</title>
|
||||
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
|
||||
|
||||
<link rel="stylesheet" href="/web/static/lib/qunit/qunit.css">
|
||||
<script src="/web/static/lib/qunit/qunit.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var oe_db_info = ${db_info};
|
||||
// List of modules, each module is preceded by its dependencies
|
||||
var oe_all_dependencies = ${dependencies};
|
||||
QUnit.config.testTimeout = 5 * 60 * 1000;
|
||||
</script>
|
||||
</head>
|
||||
<body id="oe" class="openerp">
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
</body>
|
||||
% for module, jss, tests, templates in files:
|
||||
% for js in jss:
|
||||
<script src="${to_path(module, js)}"></script>
|
||||
% endfor
|
||||
% if tests or templates:
|
||||
<script>
|
||||
openerp.testing.current_module = "${module}";
|
||||
% for template in templates:
|
||||
openerp.testing.add_template("${to_path(module, template)}");
|
||||
% endfor
|
||||
</script>
|
||||
% endif
|
||||
% if tests:
|
||||
% for test in tests:
|
||||
<script type="text/javascript" src="${to_path(module, test)}"></script>
|
||||
% endfor
|
||||
% endif
|
||||
% endfor
|
||||
</html>
|
||||
""")
|
||||
|
||||
class TestRunnerController(Controller):
|
||||
_cp_path = '/web/tests'
|
||||
|
||||
@httprequest
|
||||
def index(self, req, mod=None, **kwargs):
|
||||
ms = module.get_modules()
|
||||
manifests = dict(
|
||||
(name, desc)
|
||||
for name, desc in zip(ms, map(self.load_manifest, ms))
|
||||
if desc # remove not-actually-openerp-modules
|
||||
)
|
||||
|
||||
if not mod:
|
||||
return NOMODULE_TEMPLATE.render(modules=(
|
||||
(manifest['name'], name)
|
||||
for name, manifest in manifests.iteritems()
|
||||
if any(testfile.endswith('.js')
|
||||
for testfile in manifest['test'])
|
||||
))
|
||||
sorted_mods = module_topological_sort(dict(
|
||||
(name, manifest.get('depends', []))
|
||||
for name, manifest in manifests.iteritems()
|
||||
))
|
||||
# to_load and to_test should be zippable lists of the same length.
|
||||
# A falsy value in to_test indicate nothing to test at that index (just
|
||||
# load the corresponding part of to_load)
|
||||
to_test = sorted_mods
|
||||
if mod != '*':
|
||||
if mod not in manifests:
|
||||
return req.not_found(NOTFOUND.render(module=mod))
|
||||
idx = sorted_mods.index(mod)
|
||||
to_test = [None] * len(sorted_mods)
|
||||
to_test[idx] = mod
|
||||
|
||||
tests_candicates = [
|
||||
filter(lambda path: path.endswith('.js'),
|
||||
manifests[mod]['test'] if mod else [])
|
||||
for mod in to_test]
|
||||
# remove trailing test-less modules
|
||||
tests = reversed(list(
|
||||
itertools.dropwhile(
|
||||
operator.not_,
|
||||
reversed(tests_candicates))))
|
||||
|
||||
files = [
|
||||
(mod, manifests[mod]['js'], tests, manifests[mod]['qweb'])
|
||||
for mod, tests in itertools.izip(sorted_mods, tests)
|
||||
]
|
||||
|
||||
# if all three db_info parameters are present, send them to the page
|
||||
db_info = dict((k, v) for k, v in kwargs.iteritems()
|
||||
if k in ['source', 'supadmin', 'password'])
|
||||
if len(db_info) != 3:
|
||||
db_info = None
|
||||
|
||||
return TESTING.render(files=files, dependencies=json.dumps(
|
||||
[name for name in sorted_mods
|
||||
if module.get_module_resource(name, 'static')
|
||||
if manifests[name]['js']]), db_info=json.dumps(db_info))
|
||||
|
||||
def load_manifest(self, name):
|
||||
manifest = module.load_information_from_description_file(name)
|
||||
if manifest:
|
||||
path = module.get_module_path(name)
|
||||
manifest['js'] = list(
|
||||
self.expand_patterns(path, manifest.get('js', [])))
|
||||
manifest['test'] = list(
|
||||
self.expand_patterns(path, manifest.get('test', [])))
|
||||
manifest['qweb'] = list(
|
||||
self.expand_patterns(path, manifest.get('qweb', [])))
|
||||
return manifest
|
||||
|
||||
def expand_patterns(self, root, patterns):
|
||||
for pattern in patterns:
|
||||
normalized_pattern = os.path.normpath(os.path.join(root, pattern))
|
||||
for path in glob.glob(normalized_pattern):
|
||||
# replace OS path separators (from join & normpath) by URI ones
|
||||
yield path[len(root):].replace(os.path.sep, '/')
|
||||
+22
-31
@@ -26,8 +26,7 @@ Deferreds
|
||||
---------
|
||||
|
||||
Deferreds are a form of `promises`_. OpenERP Web currently uses
|
||||
`jQuery's deferred`_, but any `CommonJS Promises/A`_ implementation
|
||||
should work.
|
||||
`jQuery's deferred`_.
|
||||
|
||||
The core idea of deferreds is that potentially asynchronous methods
|
||||
will return a :js:class:`Deferred` object instead of an arbitrary
|
||||
@@ -44,9 +43,8 @@ directly to asynchronous methods is the ability to :ref:`compose them
|
||||
Using deferreds
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
`CommonJS Promises/A`_ deferreds have only one method of importance:
|
||||
:js:func:`Deferred.then`. This method is used to attach new callbacks
|
||||
to the deferred object.
|
||||
Deferreds's most important method is :js:func:`Deferred.then`. It is
|
||||
used to attach new callbacks to the deferred object.
|
||||
|
||||
* the first parameter attaches a success callback, called when the
|
||||
deferred object is successfully resolved and provided with the
|
||||
@@ -178,9 +176,9 @@ Deferred chaining
|
||||
|
||||
A second useful composition is starting an asynchronous operation as
|
||||
the result of an other asynchronous operation, and wanting the result
|
||||
of both: :js:func:`Deferred.then` returns the deferred on which it was
|
||||
called, so handle e.g. OpenERP's search/read sequence with this would
|
||||
require something along the lines of:
|
||||
of both: with the tools described so far, handling e.g. OpenERP's
|
||||
search/read sequence with this would require something along the lines
|
||||
of:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
@@ -195,21 +193,14 @@ require something along the lines of:
|
||||
While it doesn't look too bad for trivial code, this quickly gets
|
||||
unwieldy.
|
||||
|
||||
Instead, jQuery provides a tool to handle this kind of chains:
|
||||
:js:func:`Deferred.pipe`.
|
||||
|
||||
:js:func:`~Deferred.pipe` has the same signature as
|
||||
:js:func:`~Deferred.then` and could be used in the same manner
|
||||
provided its return value was not used.
|
||||
|
||||
It differs from :js:func:`~Deferred.then` in two ways: it returns a
|
||||
new promise object, not the one it was called with, and the return
|
||||
values of the callbacks is actually important to it: whichever
|
||||
callback is called,
|
||||
But :js:func:`~Deferred.then` also allows handling this kind of
|
||||
chains: it returns a new promise object, not the one it was called
|
||||
with, and the return values of the callbacks is actually important to
|
||||
it: whichever callback is called,
|
||||
|
||||
* If the callback is not set (not provided or left to null), the
|
||||
resolution or rejection value(s) is simply forwarded to
|
||||
:js:func:`~Deferred.pipe`'s promise (it's essentially a noop)
|
||||
:js:func:`~Deferred.then`'s promise (it's essentially a noop)
|
||||
|
||||
* If the callback is set and does not return an observable object (a
|
||||
deferred or a promise), the value it returns (``undefined`` if it
|
||||
@@ -217,7 +208,7 @@ callback is called,
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
promise.pipe(function () {
|
||||
promise.then(function () {
|
||||
console.log('called');
|
||||
});
|
||||
|
||||
@@ -234,7 +225,7 @@ callback is called,
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
return Model.search(condition).pipe(function (ids) {
|
||||
return Model.search(condition).then(function (ids) {
|
||||
return Model.read(ids, fields);
|
||||
});
|
||||
|
||||
@@ -243,10 +234,11 @@ callback is called,
|
||||
will be resolved with ``read``'s resolution values if the chain
|
||||
executes correctly.
|
||||
|
||||
:js:func:`~Deferred.pipe` is also useful to adapt third-party
|
||||
:js:func:`~Deferred.then` is also useful to adapt third-party
|
||||
promise-based APIs, in order to filter their resolution value counts
|
||||
for instance (to take advantage of :js:func:`when` 's special treatment
|
||||
of single-value promises).
|
||||
for instance (to take advantage of :js:func:`when` 's special
|
||||
treatment of single-value promises).
|
||||
|
||||
|
||||
jQuery.Deferred API
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -269,6 +261,11 @@ jQuery.Deferred API
|
||||
``doneCallback``, to provide only a success callback the
|
||||
second argument can just be ignored (and not passed at all).
|
||||
|
||||
Returns a new deferred which resolves to the result of the
|
||||
corresponding callback, if a callback returns a deferred
|
||||
itself that new deferred will be used as the resolution of the
|
||||
chain.
|
||||
|
||||
:param doneCallback: function called when the deferred is resolved
|
||||
:type doneCallback: Function
|
||||
:param failCallback: function called when the deferred is rejected
|
||||
@@ -326,12 +323,6 @@ jQuery.Deferred API
|
||||
Rejecting a deferred which has already been resolved or
|
||||
rejected has no effect.
|
||||
|
||||
.. js:function:: Deferred.pipe(doneFilter[, failFilter])
|
||||
|
||||
Filters the result of a deferred, able to transform a success
|
||||
into failure and a failure into success, or to delay
|
||||
resolution further.
|
||||
|
||||
.. [#] or simply calling :js:class:`Deferred` as a function, the
|
||||
result is the same
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.. highlight:: javascript
|
||||
|
||||
Creating a new client action
|
||||
============================
|
||||
Client actions
|
||||
==============
|
||||
|
||||
Client actions are the client-side version of OpenERP's "Server
|
||||
Actions": instead of allowing for semi-arbitrary code to be executed
|
||||
+12
-11
@@ -16,10 +16,9 @@ import sys, os
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
sys.path.append(os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('../openerp'))
|
||||
sys.path.insert(0, os.path.abspath('../addons'))
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
@@ -43,7 +42,7 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'OpenERP Server Developers Documentation'
|
||||
project = u'OpenERP Web Developers Documentation'
|
||||
copyright = u'2012, OpenERP s.a.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@@ -53,7 +52,7 @@ copyright = u'2012, OpenERP s.a.'
|
||||
# The short X.Y version.
|
||||
version = '7.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '7.0b'
|
||||
release = '7.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -171,7 +170,7 @@ html_sidebars = {
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'openerp-server-doc'
|
||||
htmlhelp_basename = 'openerp-web-doc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
@@ -190,7 +189,7 @@ latex_elements = {
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'openerp-server-doc.tex', u'OpenERP Server Developers Documentation',
|
||||
('index', 'openerp-web-doc.tex', u'OpenERP Web Developers Documentation',
|
||||
u'OpenERP s.a.', 'manual'),
|
||||
]
|
||||
|
||||
@@ -220,7 +219,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'openerp-server-doc', u'OpenERP Server Developers Documentation',
|
||||
('index', 'openerp-web-doc', u'OpenERP Web Developers Documentation',
|
||||
[u'OpenERP s.a.'], 1)
|
||||
]
|
||||
|
||||
@@ -234,8 +233,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'OpenERPServerDocumentation', u'OpenERP Server Developers Documentation',
|
||||
u'OpenERP s.a.', 'OpenERPServerDocumentation', 'Developers documentation for the openobject-server project.',
|
||||
('index', 'OpenERPWebDocumentation', u'OpenERP Web Developers Documentation',
|
||||
u'OpenERP s.a.', 'OpenERPWebDocumentation', 'Developers documentation for the openerp-web project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
@@ -248,10 +247,12 @@ texinfo_documents = [
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
todo_include_todos = True
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/', None),
|
||||
'openerpweb': ('http://doc.openerp.com/trunk/developers/web', None),
|
||||
'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None),
|
||||
'openerpdev': ('http://doc.openerp.com/trunk/developers', None),
|
||||
'openerpcommand': ('http://doc.openerp.com/trunk/developers/command', None),
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -12,20 +12,19 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
presentation
|
||||
|
||||
changelog-7.0
|
||||
|
||||
async
|
||||
rpc
|
||||
|
||||
testing
|
||||
|
||||
widget
|
||||
qweb
|
||||
search-view
|
||||
rpc
|
||||
client_action
|
||||
form_view
|
||||
search_view
|
||||
list_view
|
||||
|
||||
list-view
|
||||
form-notes
|
||||
|
||||
guides/client-action
|
||||
changelog-7.0
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
@@ -451,7 +451,7 @@ formview, delegating instead to its
|
||||
created.
|
||||
|
||||
The result should be a valid form view, see :doc:`Form Notes
|
||||
<form-notes>` for various peculiarities of the form view
|
||||
<form_view>` for various peculiarities of the form view
|
||||
format.
|
||||
|
||||
:param editor: editor object asking for the view
|
||||
@@ -7,17 +7,17 @@ Prerequisites
|
||||
|
||||
Prerequisites to code addons for the OpenERP Web Client:
|
||||
|
||||
- Html
|
||||
- Css
|
||||
- Javascript
|
||||
- HTML5
|
||||
- CSS
|
||||
- Javascript (Ecmascript 5)
|
||||
- jQuery
|
||||
|
||||
Once you know all this, that's only the beginning. Most usages of Javascript/jQuery are small hacks to make a web page nicer. The OpenERP Client Web is different: it's a web application, not a web site. It doesn't have multiple pages generated by a server side code. Only one unique empty page is loaded and all the html is generated by Javascript code, the page is never reloaded. If you never developed that kind of application you still have lot of good practices to learn.
|
||||
|
||||
Recommendations
|
||||
---------------
|
||||
Generic Guidelines
|
||||
------------------
|
||||
|
||||
First, here are the 5 golden rules when you create web 2.0 applications:
|
||||
First, here are some generic recommandations about web 2.0 applications:
|
||||
|
||||
* **Do not use ids**. Html ids are evil, the key anti-feature that makes your components non-reusable. You want to identify a dom part? Save a jQuery object over that dom element. If you really need to use ids, use _.uniqueId(), but 99% of the time you don't need to, classes are sufficient.
|
||||
* **Do not use predictable css class names** like "content" or "navigation", ten other developers will have the same idea than you and there will be clashes. A simple way to avoid this is to use prefixes. For example, if you need a css class for the button named "new" that is contained in the form view which is itself contained in the OpenERP application, name it "oe-form-view-new-button".
|
||||
@@ -25,12 +25,18 @@ First, here are the 5 golden rules when you create web 2.0 applications:
|
||||
* As a general advice, **Never assume you own the page**. When you create a component, it is never unique and is always surrounded by a bunch of crazy html. You have to do with it.
|
||||
* **Learn how to use jQuery's deferreds** [1]_. That concept may seem over-complicated, but the experience tell us that it is nearly impossible to create big-size javascript applications without that.
|
||||
|
||||
OpenERP guidelines
|
||||
------------------
|
||||
More recommendations related to the specific case of the OpenERP Web Client:
|
||||
|
||||
* Your components should inherit from the *Widget* class.
|
||||
* The code should conform to EcmasScript 5 without the "use strict" mode as we support IE9.
|
||||
|
||||
* Your components should inherit from the *Widget* class. And use *Widget* 's methods (*appendTo*, *replace*,...) to insert your component and its content into the dom.
|
||||
|
||||
* Use QWeb templates for html rendering.
|
||||
* Use *Widget* 's methods (*appendTo*, *replace*,...) to insert your component and its content into the dom.
|
||||
|
||||
* All css classes should have the prefix *oe-* .
|
||||
|
||||
* Functions that call rpc() should return a deferred, even if it calls it indirectly. So a function that calls a function that calls a function that calls rpc() should return a deferred too.
|
||||
|
||||
.. [1] http://api.jquery.com/category/deferred-object/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
QWeb Cookbook
|
||||
=============
|
||||
QWeb
|
||||
====
|
||||
|
||||
QWeb is the template engine used by the OpenERP Web Client. It is a home made engine create by OpenERP developers. There are a few things to note about it:
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
test_empty_find (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
|
||||
test_ids_shortcut (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
|
||||
test_regular_find (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
|
||||
web.testing.stack: direct, value, success ... ok
|
||||
web.testing.stack: direct, deferred, success ... ok
|
||||
web.testing.stack: direct, value, error ... ok
|
||||
web.testing.stack: direct, deferred, failure ... ok
|
||||
web.testing.stack: successful setup ... ok
|
||||
web.testing.stack: successful teardown ... ok
|
||||
web.testing.stack: successful setup and teardown ... ok
|
||||
|
||||
[snip ~150 lines]
|
||||
|
||||
test_convert_complex_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
|
||||
test_convert_complex_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
|
||||
test_convert_literal_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
|
||||
test_convert_literal_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
|
||||
test_retrieve_nonliteral_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
|
||||
test_retrieve_nonliteral_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 181 tests in 15.706s
|
||||
|
||||
OK
|
||||
|
||||
@@ -0,0 +1,693 @@
|
||||
.. highlight:: javascript
|
||||
|
||||
Testing in OpenERP Web
|
||||
======================
|
||||
|
||||
Javascript Unit Testing
|
||||
-----------------------
|
||||
|
||||
OpenERP Web 7.0 includes means to unit-test both the core code of
|
||||
OpenERP Web and your own javascript modules. On the javascript side,
|
||||
unit-testing is based on QUnit_ with a number of helpers and
|
||||
extensions for better integration with OpenERP.
|
||||
|
||||
To see what the runner looks like, find (or start) an OpenERP server
|
||||
with the web client enabled, and navigate to ``/web/tests`` e.g. `on
|
||||
OpenERP's CI <http://trunk.runbot.openerp.com/web/tests>`_. This will
|
||||
show the runner selector, which lists all modules with javascript unit
|
||||
tests, and allows starting any of them (or all javascript tests in all
|
||||
modules at once).
|
||||
|
||||
.. image:: ./images/runner.png
|
||||
:align: center
|
||||
|
||||
Clicking any runner button will launch the corresponding tests in the
|
||||
bundled QUnit_ runner:
|
||||
|
||||
.. image:: ./images/tests.png
|
||||
:align: center
|
||||
|
||||
Writing a test case
|
||||
-------------------
|
||||
|
||||
The first step is to list the test file(s). This is done through the
|
||||
``test`` key of the openerp manifest, by adding javascript files to it
|
||||
(next to the usual YAML files, if any):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
'name': "Demonstration of web/javascript tests",
|
||||
'category': 'Hidden',
|
||||
'depends': ['web'],
|
||||
'test': ['static/test/demo.js'],
|
||||
}
|
||||
|
||||
and to create the corresponding test file(s)
|
||||
|
||||
.. note::
|
||||
|
||||
Test files which do not exist will be ignored, if all test files
|
||||
of a module are ignored (can not be found), the test runner will
|
||||
consider that the module has no javascript tests.
|
||||
|
||||
After that, refreshing the runner selector will display the new module
|
||||
and allow running all of its (0 so far) tests:
|
||||
|
||||
.. image:: ./images/runner2.png
|
||||
:align: center
|
||||
|
||||
The next step is to create a test case::
|
||||
|
||||
openerp.testing.section('basic section', function (test) {
|
||||
test('my first test', function () {
|
||||
ok(false, "this test has run");
|
||||
});
|
||||
});
|
||||
|
||||
All testing helpers and structures live in the ``openerp.testing``
|
||||
module. OpenERP tests live in a :js:func:`~openerp.testing.section`,
|
||||
which is itself part of a module. The first argument to a section is
|
||||
the name of the section, the second one is the section body.
|
||||
|
||||
:js:func:`test <openerp.testing.case>`, provided by the
|
||||
:js:func:`~openerp.testing.section` to the callback, is used to
|
||||
register a given test case which will be run whenever the test runner
|
||||
actually does its job. OpenERP Web test case use standard `QUnit
|
||||
assertions`_ within them.
|
||||
|
||||
Launching the test runner at this point will run the test and display
|
||||
the corresponding assertion message, with red colors indicating the
|
||||
test failed:
|
||||
|
||||
.. image:: ./images/tests2.png
|
||||
:align: center
|
||||
|
||||
Fixing the test (by replacing ``false`` to ``true`` in the assertion)
|
||||
will make it pass:
|
||||
|
||||
.. image:: ./images/tests3.png
|
||||
:align: center
|
||||
|
||||
Assertions
|
||||
----------
|
||||
|
||||
As noted above, OpenERP Web's tests use `qunit assertions`_. They are
|
||||
available globally (so they can just be called without references to
|
||||
anything). The following list is available:
|
||||
|
||||
.. js:function:: ok(state[, message])
|
||||
|
||||
checks that ``state`` is truthy (in the javascript sense)
|
||||
|
||||
.. js:function:: strictEqual(actual, expected[, message])
|
||||
|
||||
checks that the actual (produced by a method being tested) and
|
||||
expected values are identical (roughly equivalent to ``ok(actual
|
||||
=== expected, message)``)
|
||||
|
||||
.. js:function:: notStrictEqual(actual, expected[, message])
|
||||
|
||||
checks that the actual and expected values are *not* identical
|
||||
(roughly equivalent to ``ok(actual !== expected, message)``)
|
||||
|
||||
.. js:function:: deepEqual(actual, expected[, message])
|
||||
|
||||
deep comparison between actual and expected: recurse into
|
||||
containers (objects and arrays) to ensure that they have the same
|
||||
keys/number of elements, and the values match.
|
||||
|
||||
.. js:function:: notDeepEqual(actual, expected[, message])
|
||||
|
||||
inverse operation to :js:func:`deepEqual`
|
||||
|
||||
.. js:function:: throws(block[, expected][, message])
|
||||
|
||||
checks that, when called, the ``block`` throws an
|
||||
error. Optionally validates that error against ``expected``.
|
||||
|
||||
:param Function block:
|
||||
:param expected: if a regexp, checks that the thrown error's
|
||||
message matches the regular expression. If an
|
||||
error type, checks that the thrown error is of
|
||||
that type.
|
||||
:type expected: Error | RegExp
|
||||
|
||||
.. js:function:: equal(actual, expected[, message])
|
||||
|
||||
checks that ``actual`` and ``expected`` are loosely equal, using
|
||||
the ``==`` operator and its coercion rules.
|
||||
|
||||
.. js:function:: notEqual(actual, expected[, message])
|
||||
|
||||
inverse operation to :js:func:`equal`
|
||||
|
||||
Getting an OpenERP instance
|
||||
---------------------------
|
||||
|
||||
The OpenERP instance is the base through which most OpenERP Web
|
||||
modules behaviors (functions, objects, …) are accessed. As a result,
|
||||
the test framework automatically builds one, and loads the module
|
||||
being tested and all of its dependencies inside it. This new instance
|
||||
is provided as the first positional parameter to your test
|
||||
cases. Let's observe by adding javascript code (not test code) to the
|
||||
test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
'name': "Demonstration of web/javascript tests",
|
||||
'category': 'Hidden',
|
||||
'depends': ['web'],
|
||||
'js': ['static/src/js/demo.js'],
|
||||
'test': ['static/test/demo.js'],
|
||||
}
|
||||
|
||||
::
|
||||
|
||||
// src/js/demo.js
|
||||
openerp.web_tests_demo = function (instance) {
|
||||
instance.web_tests_demo = {
|
||||
value_true: true,
|
||||
SomeType: instance.web.Class.extend({
|
||||
init: function (value) {
|
||||
this.value = value;
|
||||
}
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
and then adding a new test case, which simply checks that the
|
||||
``instance`` contains all the expected stuff we created in the
|
||||
module::
|
||||
|
||||
// test/demo.js
|
||||
test('module content', function (instance) {
|
||||
ok(instance.web_tests_demo.value_true, "should have a true value");
|
||||
var type_instance = new instance.web_tests_demo.SomeType(42);
|
||||
strictEqual(type_instance.value, 42, "should have provided value");
|
||||
});
|
||||
|
||||
DOM Scratchpad
|
||||
--------------
|
||||
|
||||
As in the wider client, arbitrarily accessing document content is
|
||||
strongly discouraged during tests. But DOM access is still needed to
|
||||
e.g. fully initialize :js:class:`widgets <~openerp.web.Widget>` before
|
||||
testing them.
|
||||
|
||||
Thus, a test case gets a DOM scratchpad as its second positional
|
||||
parameter, in a jQuery instance. That scratchpad is fully cleaned up
|
||||
before each test, and as long as it doesn't do anything outside the
|
||||
scratchpad your code can do whatever it wants::
|
||||
|
||||
// test/demo.js
|
||||
test('DOM content', function (instance, $scratchpad) {
|
||||
$scratchpad.html('<div><span class="foo bar">ok</span></div>');
|
||||
ok($scratchpad.find('span').hasClass('foo'),
|
||||
"should have provided class");
|
||||
});
|
||||
test('clean scratchpad', function (instance, $scratchpad) {
|
||||
ok(!$scratchpad.children().length, "should have no content");
|
||||
ok(!$scratchpad.text(), "should have no text");
|
||||
});
|
||||
|
||||
.. note::
|
||||
|
||||
The top-level element of the scratchpad is not cleaned up, test
|
||||
cases can add text or DOM children but shoud not alter
|
||||
``$scratchpad`` itself.
|
||||
|
||||
Loading templates
|
||||
-----------------
|
||||
|
||||
To avoid the corresponding processing costs, by default templates are
|
||||
not loaded into QWeb. If you need to render e.g. widgets making use of
|
||||
QWeb templates, you can request their loading through the
|
||||
:js:attr:`~TestOptions.templates` option to the :js:func:`test case
|
||||
function <openerp.testing.case>`.
|
||||
|
||||
This will automatically load all relevant templates in the instance's
|
||||
qweb before running the test case:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
'name': "Demonstration of web/javascript tests",
|
||||
'category': 'Hidden',
|
||||
'depends': ['web'],
|
||||
'js': ['static/src/js/demo.js'],
|
||||
'test': ['static/test/demo.js'],
|
||||
'qweb': ['static/src/xml/demo.xml'],
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- src/xml/demo.xml -->
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="DemoTemplate">
|
||||
<t t-foreach="5" t-as="value">
|
||||
<p><t t-esc="value"/></p>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
::
|
||||
|
||||
// test/demo.js
|
||||
test('templates', {templates: true}, function (instance) {
|
||||
var s = instance.web.qweb.render('DemoTemplate');
|
||||
var texts = $(s).find('p').map(function () {
|
||||
return $(this).text();
|
||||
}).get();
|
||||
|
||||
deepEqual(texts, ['0', '1', '2', '3', '4']);
|
||||
});
|
||||
|
||||
Asynchronous cases
|
||||
------------------
|
||||
|
||||
The test case examples so far are all synchronous, they execute from
|
||||
the first to the last line and once the last line has executed the
|
||||
test is done. But the web client is full of :doc:`asynchronous code
|
||||
</async>`, and thus test cases need to be async-aware.
|
||||
|
||||
This is done by returning a :js:class:`deferred <Deferred>` from the
|
||||
case callback::
|
||||
|
||||
// test/demo.js
|
||||
test('asynchronous', {
|
||||
asserts: 1
|
||||
}, function () {
|
||||
var d = $.Deferred();
|
||||
setTimeout(function () {
|
||||
ok(true);
|
||||
d.resolve();
|
||||
}, 100);
|
||||
return d;
|
||||
});
|
||||
|
||||
This example also uses the :js:class:`options parameter <TestOptions>`
|
||||
to specify the number of assertions the case should expect, if less or
|
||||
more assertions are specified the case will count as failed.
|
||||
|
||||
Asynchronous test cases *must* specify the number of assertions they
|
||||
will run. This allows more easily catching situations where e.g. the
|
||||
test architecture was not warned about asynchronous operations.
|
||||
|
||||
.. note::
|
||||
|
||||
Asynchronous test cases also have a 2 seconds timeout: if the test
|
||||
does not finish within 2 seconds, it will be considered
|
||||
failed. This pretty much always means the test will not
|
||||
resolve. This timeout *only* applies to the test itself, not to
|
||||
the setup and teardown processes.
|
||||
|
||||
.. note::
|
||||
|
||||
If the returned deferred is rejected, the test will be failed
|
||||
unless :js:attr:`~TestOptions.fail_on_rejection` is set to
|
||||
``false``.
|
||||
|
||||
RPC
|
||||
---
|
||||
|
||||
An important subset of asynchronous test cases is test cases which
|
||||
need to perform (and chain, to an extent) RPC calls.
|
||||
|
||||
.. note::
|
||||
|
||||
Because they are a subset of asynchronous cases, RPC cases must
|
||||
also provide a valid :js:attr:`assertions count
|
||||
<TestOptions.asserts>`.
|
||||
|
||||
By default, test cases will fail when trying to perform an RPC
|
||||
call. The ability to perform RPC calls must be explicitly requested by
|
||||
a test case (or its containing test suite) through
|
||||
:js:attr:`~TestOptions.rpc`, and can be one of two modes: ``mock`` or
|
||||
``rpc``.
|
||||
|
||||
Mock RPC
|
||||
++++++++
|
||||
|
||||
The preferred (and fastest from a setup and execution time point of
|
||||
view) way to do RPC during tests is to mock the RPC calls: while
|
||||
setting up the test case, provide what the RPC responses "should" be,
|
||||
and only test the code between the "user" (the test itself) and the
|
||||
RPC call, before the call is effectively done.
|
||||
|
||||
To do this, set the :js:attr:`rpc option <TestOptions.rpc>` to
|
||||
``mock``. This will add a third parameter to the test case callback:
|
||||
|
||||
.. js:function:: mock(rpc_spec, handler)
|
||||
|
||||
Can be used in two different ways depending on the shape of the
|
||||
first parameter:
|
||||
|
||||
* If it matches the pattern ``model:method`` (if it contains a
|
||||
colon, essentially) the call will set up the mocking of an RPC
|
||||
call straight to the OpenERP server (through XMLRPC) as
|
||||
performed via e.g. :js:func:`openerp.web.Model.call`.
|
||||
|
||||
In that case, ``handler`` should be a function taking two
|
||||
arguments ``args`` and ``kwargs``, matching the corresponding
|
||||
arguments on the server side and should simply return the value
|
||||
as if it were returned by the Python XMLRPC handler::
|
||||
|
||||
test('XML-RPC', {rpc: 'mock', asserts: 3}, function (instance, $s, mock) {
|
||||
// set up mocking
|
||||
mock('people.famous:name_search', function (args, kwargs) {
|
||||
strictEqual(kwargs.name, 'bob');
|
||||
return [
|
||||
[1, "Microsoft Bob"],
|
||||
[2, "Bob the Builder"],
|
||||
[3, "Silent Bob"]
|
||||
];
|
||||
});
|
||||
|
||||
// actual test code
|
||||
return new instance.web.Model('people.famous')
|
||||
.call('name_search', {name: 'bob'}).then(function (result) {
|
||||
strictEqual(result.length, 3, "shoud return 3 people");
|
||||
strictEqual(result[0][1], "Microsoft Bob",
|
||||
"the most famous bob should be Microsoft Bob");
|
||||
});
|
||||
});
|
||||
|
||||
* Otherwise, if it matches an absolute path (e.g. ``/a/b/c``) it
|
||||
will mock a JSON-RPC call to a web client controller, such as
|
||||
``/web/webclient/translations``. In that case, the handler takes
|
||||
a single ``params`` argument holding all of the parameters
|
||||
provided over JSON-RPC.
|
||||
|
||||
As previously, the handler should simply return the result value
|
||||
as if returned by the original JSON-RPC handler::
|
||||
|
||||
test('JSON-RPC', {rpc: 'mock', asserts: 3, templates: true}, function (instance, $s, mock) {
|
||||
var fetched_dbs = false, fetched_langs = false;
|
||||
mock('/web/database/get_list', function () {
|
||||
fetched_dbs = true;
|
||||
return ['foo', 'bar', 'baz'];
|
||||
});
|
||||
mock('/web/session/get_lang_list', function () {
|
||||
fetched_langs = true;
|
||||
return [['vo_IS', 'Hopelandic / Vonlenska']];
|
||||
});
|
||||
|
||||
// widget needs that or it blows up
|
||||
instance.webclient = {toggle_bars: openerp.testing.noop};
|
||||
var dbm = new instance.web.DatabaseManager({});
|
||||
return dbm.appendTo($s).then(function () {
|
||||
ok(fetched_dbs, "should have fetched databases");
|
||||
ok(fetched_langs, "should have fetched languages");
|
||||
deepEqual(dbm.db_list, ['foo', 'bar', 'baz']);
|
||||
});
|
||||
});
|
||||
|
||||
.. note::
|
||||
|
||||
Mock handlers can contain assertions, these assertions should be
|
||||
part of the assertions count (and if multiple calls are made to a
|
||||
handler containing assertions, it multiplies the effective number
|
||||
of assertions).
|
||||
|
||||
.. _testing-rpc-rpc:
|
||||
|
||||
Actual RPC
|
||||
++++++++++
|
||||
|
||||
A more realistic (but significantly slower and more expensive) way to
|
||||
perform RPC calls is to perform actual calls to an actually running
|
||||
OpenERP server. To do this, set the :js:attr:`rpc option
|
||||
<~TestOptions.rpc>` to ``rpc``, it will not provide any new parameter
|
||||
but will enable actual RPC, and the automatic creation and destruction
|
||||
of databases (from a specified source) around tests.
|
||||
|
||||
First, create a basic model we can test stuff with:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
|
||||
class TestObject(orm.Model):
|
||||
_name = 'web_tests_demo.model'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char("Name", required=True),
|
||||
'thing': fields.char("Thing"),
|
||||
'other': fields.char("Other", required=True)
|
||||
}
|
||||
_defaults = {
|
||||
'other': "bob"
|
||||
}
|
||||
|
||||
then the actual test::
|
||||
|
||||
test('actual RPC', {rpc: 'rpc', asserts: 4}, function (instance) {
|
||||
var Model = new instance.web.Model('web_tests_demo.model');
|
||||
return Model.call('create', [{name: "Bob"}])
|
||||
.then(function (id) {
|
||||
return Model.call('read', [[id]]);
|
||||
}).then(function (records) {
|
||||
strictEqual(records.length, 1);
|
||||
var record = records[0];
|
||||
strictEqual(record.name, "Bob");
|
||||
strictEqual(record.thing, false);
|
||||
// default value
|
||||
strictEqual(record.other, 'bob');
|
||||
});
|
||||
});
|
||||
|
||||
This test looks like a "mock" RPC test but for the lack of mock
|
||||
response (and the different ``rpc`` type), however it has further
|
||||
ranging consequences in that it will copy an existing database to a
|
||||
new one, run the test in full on that temporary database and destroy
|
||||
the database, to simulate an isolated and transactional context and
|
||||
avoid affecting other tests. One of the consequences is that it takes
|
||||
a *long* time to run (5~10s, most of that time being spent waiting for
|
||||
a database duplication).
|
||||
|
||||
Furthermore, as the test needs to clone a database, it also has to ask
|
||||
which database to clone, the database/super-admin password and the
|
||||
password of the ``admin`` user (in order to authenticate as said
|
||||
user). As a result, the first time the test runner encounters an
|
||||
``rpc: "rpc"`` test configuration it will produce the following
|
||||
prompt:
|
||||
|
||||
.. image:: ./images/db-query.png
|
||||
:align: center
|
||||
|
||||
and stop the testing process until the necessary information has been
|
||||
provided.
|
||||
|
||||
The prompt will only appear once per test run, all tests will use the
|
||||
same "source" database.
|
||||
|
||||
.. note::
|
||||
|
||||
The handling of that information is currently rather brittle and
|
||||
unchecked, incorrect values will likely crash the runner.
|
||||
|
||||
.. note::
|
||||
|
||||
The runner does not currently store this information (for any
|
||||
longer than a test run that is), the prompt will have to be filled
|
||||
every time.
|
||||
|
||||
Testing API
|
||||
-----------
|
||||
|
||||
.. js:function:: openerp.testing.section(name[, options], body)
|
||||
|
||||
A test section, serves as shared namespace for related tests (for
|
||||
constants or values to only set up once). The ``body`` function
|
||||
should contain the tests themselves.
|
||||
|
||||
Note that the order in which tests are run is essentially
|
||||
undefined, do *not* rely on it.
|
||||
|
||||
:param String name:
|
||||
:param TestOptions options:
|
||||
:param body:
|
||||
:type body: Function<:js:func:`~openerp.testing.case`, void>
|
||||
|
||||
.. js:function:: openerp.testing.case(name[, options], callback)
|
||||
|
||||
Registers a test case callback in the test runner, the callback
|
||||
will only be run once the runner is started (or maybe not at all,
|
||||
if the test is filtered out).
|
||||
|
||||
:param String name:
|
||||
:param TestOptions options:
|
||||
:param callback:
|
||||
:type callback: Function<instance, $, Function<String, Function, void>>
|
||||
|
||||
.. js:class:: TestOptions
|
||||
|
||||
the various options which can be passed to
|
||||
:js:func:`~openerp.testing.section` or
|
||||
:js:func:`~openerp.testing.case`. Except for
|
||||
:js:attr:`~TestOptions.setup` and
|
||||
:js:attr:`~TestOptions.teardown`, an option on
|
||||
:js:func:`~openerp.testing.case` will overwrite the corresponding
|
||||
option on :js:func:`~openerp.testing.section` so
|
||||
e.g. :js:attr:`~TestOptions.rpc` can be set for a
|
||||
:js:func:`~openerp.testing.section` and then differently set for
|
||||
some :js:func:`~openerp.testing.case` of that
|
||||
:js:func:`~openerp.testing.section`
|
||||
|
||||
.. js:attribute:: TestOptions.asserts
|
||||
|
||||
An integer, the number of assertions which should run during a
|
||||
normal execution of the test. Mandatory for asynchronous tests.
|
||||
|
||||
.. js:attribute:: TestOptions.setup
|
||||
|
||||
Test case setup, run right before each test case. A section's
|
||||
:js:func:`~TestOptions.setup` is run before the case's own, if
|
||||
both are specified.
|
||||
|
||||
.. js:attribute:: TestOptions.teardown
|
||||
|
||||
Test case teardown, a case's :js:func:`~TestOptions.teardown`
|
||||
is run before the corresponding section if both are present.
|
||||
|
||||
.. js:attribute:: TestOptions.fail_on_rejection
|
||||
|
||||
If the test is asynchronous and its resulting promise is
|
||||
rejected, fail the test. Defaults to ``true``, set to
|
||||
``false`` to not fail the test in case of rejection::
|
||||
|
||||
// test/demo.js
|
||||
test('unfail rejection', {
|
||||
asserts: 1,
|
||||
fail_on_rejection: false
|
||||
}, function () {
|
||||
var d = $.Deferred();
|
||||
setTimeout(function () {
|
||||
ok(true);
|
||||
d.reject();
|
||||
}, 100);
|
||||
return d;
|
||||
});
|
||||
|
||||
.. js:attribute:: TestOptions.rpc
|
||||
|
||||
RPC method to use during tests, one of ``"mock"`` or
|
||||
``"rpc"``. Any other value will disable RPC for the test (if
|
||||
they were enabled by the suite for instance).
|
||||
|
||||
.. js:attribute:: TestOptions.templates
|
||||
|
||||
Whether the current module (and its dependencies)'s templates
|
||||
should be loaded into QWeb before starting the test. A
|
||||
boolean, ``false`` by default.
|
||||
|
||||
The test runner can also use two global configuration values set
|
||||
directly on the ``window`` object:
|
||||
|
||||
* ``oe_all_dependencies`` is an ``Array`` of all modules with a web
|
||||
component, ordered by dependency (for a module ``A`` with
|
||||
dependencies ``A'``, any module of ``A'`` must come before ``A`` in
|
||||
the array)
|
||||
|
||||
* ``oe_db_info`` is an object with 3 keys ``source``, ``supadmin`` and
|
||||
``password``. It is used to pre-configure :ref:`actual RPC
|
||||
<testing-rpc-rpc>` tests, to avoid a prompt being displayed
|
||||
(especially for headless situations).
|
||||
|
||||
Running through Python
|
||||
----------------------
|
||||
|
||||
The web client includes the means to run these tests on the
|
||||
command-line (or in a CI system), but while actually running it is
|
||||
pretty simple the setup of the pre-requisite parts has some
|
||||
complexities.
|
||||
|
||||
1. Install unittest2_ and QUnitSuite_ in your Python environment. Both
|
||||
can trivially be installed via `pip <http://pip-installer.org>`_ or
|
||||
`easy_install
|
||||
<http://packages.python.org/distribute/easy_install.html>`_.
|
||||
|
||||
The former is the unit-testing framework used by OpenERP, the
|
||||
latter is an adapter module to run qunit_ test suites and convert
|
||||
their result into something unittest2_ can understand and report.
|
||||
|
||||
2. Install PhantomJS_. It is a headless
|
||||
browser which allows automating running and testing web
|
||||
pages. QUnitSuite_ uses it to actually run the qunit_ test suite.
|
||||
|
||||
The PhantomJS_ website provides pre-built binaries for some
|
||||
platforms, and your OS's package management probably provides it as
|
||||
well.
|
||||
|
||||
If you're building PhantomJS_ from source, I recommend preparing
|
||||
for some knitting time as it's not exactly fast (it needs to
|
||||
compile both `Qt <http://qt-project.org/>`_ and `Webkit
|
||||
<http://www.webkit.org/>`_, both being pretty big projects).
|
||||
|
||||
.. note::
|
||||
|
||||
Because PhantomJS_ is webkit-based, it will not be able to test
|
||||
if Firefox, Opera or Internet Explorer can correctly run the
|
||||
test suite (and it is only an approximation for Safari and
|
||||
Chrome). It is therefore recommended to *also* run the test
|
||||
suites in actual browsers once in a while.
|
||||
|
||||
.. note::
|
||||
|
||||
The version of PhantomJS_ this was build through is 1.7,
|
||||
previous versions *should* work but are not actually supported
|
||||
(and tend to just segfault when something goes wrong in
|
||||
PhantomJS_ itself so they're a pain to debug).
|
||||
|
||||
3. Set up :ref:`OpenERP Command <openerpcommand:openerp-command>`,
|
||||
which will be used to actually run the tests: running the qunit_
|
||||
test suite requires a running server, so at this point OpenERP
|
||||
Server isn't able to do it on its own during the building/testing
|
||||
process.
|
||||
|
||||
4. Install a new database with all relevant modules (all modules with
|
||||
a web component at least), then restart the server
|
||||
|
||||
.. note::
|
||||
|
||||
For some tests, a source database needs to be duplicated. This
|
||||
operation requires that there be no connection to the database
|
||||
being duplicated, but OpenERP doesn't currently break
|
||||
existing/outstanding connections, so restarting the server is
|
||||
the simplest way to ensure everything is in the right state.
|
||||
|
||||
5. Launch ``oe run-tests -d $DATABASE -mweb`` with the correct
|
||||
addons-path specified (and replacing ``$DATABASE`` by the source
|
||||
database you created above)
|
||||
|
||||
.. note::
|
||||
|
||||
If you leave out ``-mweb``, the runner will attempt to run all
|
||||
the tests in all the modules, which may or may not work.
|
||||
|
||||
If everything went correctly, you should now see a list of tests with
|
||||
(hopefully) ``ok`` next to their names, closing with a report of the
|
||||
number of tests run and the time it took:
|
||||
|
||||
.. literalinclude:: test-report.txt
|
||||
:language: text
|
||||
|
||||
Congratulation, you have just performed a successful "offline" run of
|
||||
the OpenERP Web test suite.
|
||||
|
||||
.. note::
|
||||
|
||||
Note that this runs all the Python tests for the ``web`` module,
|
||||
but all the web tests for all of OpenERP. This can be surprising.
|
||||
|
||||
.. _qunit: http://qunitjs.com/
|
||||
|
||||
.. _qunit assertions: http://api.qunitjs.com/category/assert/
|
||||
|
||||
.. _unittest2: http://pypi.python.org/pypi/unittest2
|
||||
|
||||
.. _QUnitSuite: http://pypi.python.org/pypi/QUnitSuite/
|
||||
|
||||
.. _PhantomJS: http://phantomjs.org/
|
||||
@@ -1,5 +1,5 @@
|
||||
User Interaction: Widget
|
||||
========================
|
||||
Widget
|
||||
======
|
||||
|
||||
This is the base class for all visual components. It corresponds to an MVC
|
||||
view. It provides a number of services to handle a section of a page:
|
||||
|
||||
+62
-27
@@ -11,6 +11,7 @@ import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
@@ -21,6 +22,7 @@ import urlparse
|
||||
import uuid
|
||||
import xmlrpclib
|
||||
|
||||
import babel.core
|
||||
import simplejson
|
||||
import werkzeug.contrib.sessions
|
||||
import werkzeug.datastructures
|
||||
@@ -91,9 +93,25 @@ class WebRequest(object):
|
||||
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
|
||||
self.session = self.httpsession.get(self.session_id)
|
||||
if not self.session:
|
||||
self.httpsession[self.session_id] = self.session = session.OpenERPSession()
|
||||
self.session = session.OpenERPSession()
|
||||
self.httpsession[self.session_id] = self.session
|
||||
self.context = self.params.pop('context', None)
|
||||
self.debug = self.params.pop('debug', False) != False
|
||||
self.debug = self.params.pop('debug', False) is not False
|
||||
# Determine self.lang
|
||||
lang = self.params.get('lang', None)
|
||||
if lang is None:
|
||||
lang = self.session.eval_context(self.context).get('lang')
|
||||
if lang is None:
|
||||
lang = self.httprequest.cookies.get('lang')
|
||||
if lang is None:
|
||||
lang = self.httprequest.accept_languages.best
|
||||
if lang is None:
|
||||
lang = 'en_US'
|
||||
# tranform 2 letters lang like 'en' into 5 letters like 'en_US'
|
||||
lang = babel.core.LOCALE_ALIASES.get(lang, lang)
|
||||
# we use _ as seprator where RFC2616 uses '-'
|
||||
self.lang = lang.replace('-', '_')
|
||||
|
||||
|
||||
class JsonRequest(WebRequest):
|
||||
""" JSON-RPC2 over HTTP.
|
||||
@@ -210,6 +228,10 @@ class JsonRequest(WebRequest):
|
||||
_logger.debug("<--\n%s", pprint.pformat(response))
|
||||
|
||||
if jsonp:
|
||||
# If we use jsonp, that's mean we are called from another host
|
||||
# Some browser (IE and Safari) do no allow third party cookies
|
||||
# We need then to manage http sessions manually.
|
||||
response['httpsessionid'] = self.httpsession.sid
|
||||
mime = 'application/javascript'
|
||||
body = "%s(%s);" % (jsonp, simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),)
|
||||
else:
|
||||
@@ -336,23 +358,13 @@ class Controller(object):
|
||||
#----------------------------------------------------------
|
||||
# Session context manager
|
||||
#----------------------------------------------------------
|
||||
STORES = {}
|
||||
|
||||
@contextlib.contextmanager
|
||||
def session_context(request, storage_path, session_cookie='httpsessionid'):
|
||||
session_store, session_lock = STORES.get(storage_path, (None, None))
|
||||
if not session_store:
|
||||
session_store = werkzeug.contrib.sessions.FilesystemSessionStore( storage_path)
|
||||
session_lock = threading.Lock()
|
||||
STORES[storage_path] = session_store, session_lock
|
||||
|
||||
sid = request.cookies.get(session_cookie)
|
||||
def session_context(request, session_store, session_lock, sid):
|
||||
with session_lock:
|
||||
if sid:
|
||||
request.session = session_store.get(sid)
|
||||
else:
|
||||
request.session = session_store.new()
|
||||
|
||||
try:
|
||||
yield request.session
|
||||
finally:
|
||||
@@ -404,6 +416,18 @@ def session_context(request, storage_path, session_cookie='httpsessionid'):
|
||||
|
||||
session_store.save(request.session)
|
||||
|
||||
def session_gc(session_store):
|
||||
if random.random() < 0.001:
|
||||
# we keep session one week
|
||||
last_week = time.time() - 60*60*24*7
|
||||
for fname in os.listdir(session_store.path):
|
||||
path = os.path.join(session_store.path, fname)
|
||||
try:
|
||||
if os.path.getmtime(path) < last_week:
|
||||
os.unlink(path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------
|
||||
# WSGI Application
|
||||
#----------------------------------------------------------
|
||||
@@ -434,26 +458,31 @@ class DisableCacheMiddleware(object):
|
||||
start_response(status, new_headers)
|
||||
return self.app(environ, start_wrapped)
|
||||
|
||||
def session_path():
|
||||
try:
|
||||
username = getpass.getuser()
|
||||
except Exception:
|
||||
username = "unknown"
|
||||
path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path, 0700)
|
||||
return path
|
||||
|
||||
class Root(object):
|
||||
"""Root WSGI application for the OpenERP Web Client.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.httpsession_cookie = 'httpsessionid'
|
||||
self.addons = {}
|
||||
|
||||
static_dirs = self._load_addons()
|
||||
app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs)
|
||||
self.dispatch = DisableCacheMiddleware(app)
|
||||
|
||||
try:
|
||||
username = getpass.getuser()
|
||||
except Exception:
|
||||
username = "unknown"
|
||||
self.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
|
||||
|
||||
if not os.path.exists(self.session_storage):
|
||||
os.mkdir(self.session_storage, 0700)
|
||||
_logger.debug('HTTP sessions stored in: %s', self.session_storage)
|
||||
# Setup http sessions
|
||||
path = session_path()
|
||||
self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path)
|
||||
self.session_lock = threading.Lock()
|
||||
_logger.debug('HTTP sessions stored in: %s', path)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
""" Handle a WSGI request
|
||||
@@ -476,8 +505,14 @@ class Root(object):
|
||||
if not handler:
|
||||
response = werkzeug.exceptions.NotFound()
|
||||
else:
|
||||
with session_context(request, self.session_storage, self.httpsession_cookie) as session:
|
||||
result = handler( request)
|
||||
sid = request.cookies.get('sid')
|
||||
if not sid:
|
||||
sid = request.args.get('sid')
|
||||
|
||||
session_gc(self.session_store)
|
||||
|
||||
with session_context(request, self.session_store, self.session_lock, sid) as session:
|
||||
result = handler(request)
|
||||
|
||||
if isinstance(result, basestring):
|
||||
headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
|
||||
@@ -486,7 +521,7 @@ class Root(object):
|
||||
response = result
|
||||
|
||||
if hasattr(response, 'set_cookie'):
|
||||
response.set_cookie(self.httpsession_cookie, session.sid)
|
||||
response.set_cookie('sid', session.sid)
|
||||
|
||||
return response(environ, start_response)
|
||||
|
||||
@@ -530,7 +565,7 @@ class Root(object):
|
||||
:rtype: ``Controller | None``
|
||||
"""
|
||||
if l:
|
||||
ps = '/' + '/'.join(l)
|
||||
ps = '/' + '/'.join(filter(None, l))
|
||||
method_name = 'index'
|
||||
while ps:
|
||||
c = controllers_path.get(ps)
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
"X-Poedit-Language: Czech\n"
|
||||
|
||||
#. openerp-web
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
"Language: es\n"
|
||||
|
||||
#. openerp-web
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -9,13 +9,13 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-07-02 09:06+0200\n"
|
||||
"PO-Revision-Date: 2012-10-26 12:17+0000\n"
|
||||
"Last-Translator: Herczeg Péter <herczegp@gmail.com>\n"
|
||||
"Last-Translator: Herczeg Péter <hp@erp-cloud.hu>\n"
|
||||
"Language-Team: Hungarian <hu@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-27 05:15+0000\n"
|
||||
"X-Generator: Launchpad (build 16194)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-23 05:05+0000\n"
|
||||
"X-Generator: Launchpad (build 16179)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
+45
-29
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-22 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
@@ -66,7 +66,7 @@ msgstr ""
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:499
|
||||
msgid "Restored"
|
||||
msgstr ""
|
||||
msgstr "Atkurta"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:499
|
||||
@@ -253,7 +253,7 @@ msgstr ""
|
||||
#: addons/web/static/src/js/formats.js:139
|
||||
#, python-format
|
||||
msgid "(%d records)"
|
||||
msgstr ""
|
||||
msgstr "(%d įrašai(-ų))"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/formats.js:325
|
||||
@@ -265,7 +265,7 @@ msgstr "Atsisiųsti"
|
||||
#: addons/web/static/src/js/formats.js:330
|
||||
#, python-format
|
||||
msgid "Download \"%s\""
|
||||
msgstr ""
|
||||
msgstr "Parsisiųsti \"%s\""
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/search.js:437
|
||||
@@ -527,12 +527,12 @@ msgstr "Forma"
|
||||
#: addons/web/static/src/xml/base.xml:763
|
||||
#: addons/web/static/src/xml/base.xml:1714
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
msgstr "Ištrinti"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:762
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
msgstr "Sukurti kopiją"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/view_form.js:133
|
||||
@@ -589,12 +589,12 @@ msgstr ""
|
||||
#: addons/web/static/src/js/view_form.js:2238
|
||||
#, python-format
|
||||
msgid "<em> Create \"<strong>%s</strong>\"</em>"
|
||||
msgstr ""
|
||||
msgstr "<em> Sukurti \"<strong>%s</strong>\"</em>"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/view_form.js:2244
|
||||
msgid "<em> Create and Edit...</em>"
|
||||
msgstr ""
|
||||
msgstr "<em> Sukurti ir redaguoti...</em>"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/view_form.js:2277
|
||||
@@ -606,7 +606,7 @@ msgstr "Paieška: "
|
||||
#: addons/web/static/src/js/view_form.js:2277
|
||||
#: addons/web/static/src/js/view_form.js:2765
|
||||
msgid "Create: "
|
||||
msgstr ""
|
||||
msgstr "Sukurti "
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/view_form.js:2062
|
||||
@@ -668,7 +668,7 @@ msgstr "Grupė"
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/view_list.js:549
|
||||
msgid "Do you really want to remove these records?"
|
||||
msgstr ""
|
||||
msgstr "Ar tikrai norite ištrinti šiuos įrašus?"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/views.js:925
|
||||
@@ -813,25 +813,25 @@ msgstr ""
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:297
|
||||
msgid "Invalid username or password"
|
||||
msgstr ""
|
||||
msgstr "Neteisingas naudotojo vardas arba slaptažodis"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:116
|
||||
#: addons/web/static/src/xml/base.xml:150
|
||||
#: addons/web/static/src/xml/base.xml:301
|
||||
msgid "Database:"
|
||||
msgstr ""
|
||||
msgstr "Duomenų bazė:"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:306
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgstr "Naudotojas"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:308
|
||||
#: addons/web/static/src/xml/base.xml:331
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "Slaptažodis"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:310
|
||||
@@ -870,17 +870,17 @@ msgstr ""
|
||||
#: addons/web/static/src/xml/base.xml:195
|
||||
#: addons/web/static/src/xml/base.xml:330
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
msgstr "Atkurti"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:332
|
||||
msgid "Back to Login"
|
||||
msgstr ""
|
||||
msgstr "Grįžti į prisijungimo langą"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:61
|
||||
msgid "CREATE DATABASE"
|
||||
msgstr ""
|
||||
msgstr "SUKURTI DUOMENŲ BAZĘ"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:68 addons/web/static/src/xml/base.xml:211
|
||||
@@ -922,7 +922,7 @@ msgstr ""
|
||||
#: addons/web/static/src/xml/base.xml:162
|
||||
#: addons/web/static/src/xml/base.xml:187
|
||||
msgid "Master Password:"
|
||||
msgstr ""
|
||||
msgstr "Pagrindinis slaptažodis"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:143
|
||||
@@ -932,7 +932,7 @@ msgstr ""
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:175
|
||||
msgid "RESTORE DATABASE"
|
||||
msgstr ""
|
||||
msgstr "ATKURTI DUOMENŲ BAZĘ"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:182
|
||||
@@ -1012,17 +1012,17 @@ msgstr "OpenERP.com"
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1720
|
||||
msgid "Old Password:"
|
||||
msgstr ""
|
||||
msgstr "Esamas slaptažodis:"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1725
|
||||
msgid "New Password:"
|
||||
msgstr ""
|
||||
msgstr "Naujas slaptažodis:"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1730
|
||||
msgid "Confirm Password:"
|
||||
msgstr ""
|
||||
msgstr "Patvirtinti slaptažodį:"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:390
|
||||
@@ -1254,7 +1254,7 @@ msgstr ""
|
||||
#: addons/web/static/src/xml/base.xml:1222
|
||||
#: addons/web/static/src/xml/base.xml:1279
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
msgstr "Išvalyti"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1179
|
||||
@@ -1408,12 +1408,12 @@ msgstr ""
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1509
|
||||
msgid "Save & New"
|
||||
msgstr ""
|
||||
msgstr "Išsaugoti ir sukurti naują"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1510
|
||||
msgid "Save & Close"
|
||||
msgstr ""
|
||||
msgstr "Išsaugoti ir uždaryti"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/xml/base.xml:1617
|
||||
@@ -1572,9 +1572,6 @@ msgstr ""
|
||||
#~ msgid "Advanced Filters"
|
||||
#~ msgstr "Išplėstiniai filtrai"
|
||||
|
||||
#~ msgid "Other Options"
|
||||
#~ msgstr "Kiti nustatymai"
|
||||
|
||||
#~ msgid "OK"
|
||||
#~ msgstr "Gerai"
|
||||
|
||||
@@ -1602,3 +1599,22 @@ msgstr ""
|
||||
|
||||
#~ msgid "Reports"
|
||||
#~ msgstr "Ataskaitos"
|
||||
|
||||
#~ msgid "Other Options"
|
||||
#~ msgstr "Kitos parinktys"
|
||||
|
||||
#~ msgid "LOGOUT"
|
||||
#~ msgstr "ATSIJUNGTI"
|
||||
|
||||
#~ msgid "Create..."
|
||||
#~ msgstr "Sukurti..."
|
||||
|
||||
#~ msgid "Search"
|
||||
#~ msgstr "Ieškoti"
|
||||
|
||||
#~ msgid "Search..."
|
||||
#~ msgstr "Ieškoti..."
|
||||
|
||||
#, python-format
|
||||
#~ msgid "[%(first_record)d to %(last_record)d] of %(records_count)d"
|
||||
#~ msgstr "[%(first_record)d iki %(last_record)d] iš %(records_count)d"
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
@@ -243,17 +243,19 @@ msgid ""
|
||||
"Destination fields should only be selected once, some fields are selected "
|
||||
"more than once:"
|
||||
msgstr ""
|
||||
"Pola docelowe muszą być wybierane tylko raz. niektóre inne pola mogą być "
|
||||
"wybierane więcej niż raz:"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/data_import.js:386
|
||||
msgid "*Required Fields are not selected :"
|
||||
msgstr ""
|
||||
msgstr "*Wymagane pola nie zostały wybrane :"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/formats.js:139
|
||||
#, python-format
|
||||
msgid "(%d records)"
|
||||
msgstr ""
|
||||
msgstr "(%d rekordów)"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/formats.js:325
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -15,8 +15,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-02 05:20+0000\n"
|
||||
"X-Generator: Launchpad (build 16218)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-02 05:20+0000\n"
|
||||
"X-Generator: Launchpad (build 16218)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-07 04:55+0000\n"
|
||||
"X-Generator: Launchpad (build 16232)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-21 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16165)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-15 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16265)\n"
|
||||
|
||||
#. openerp-web
|
||||
#: addons/web/static/src/js/chrome.js:176
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
var $tip = this.tip();
|
||||
|
||||
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
|
||||
$tip[0].className = 'tipsy openerp oe_tooltip '; // reset classname in case of dynamic gravity
|
||||
$tip[0].className = 'tipsy '; // reset classname in case of dynamic gravity
|
||||
$tip.openerpClass('oe_tooltip');
|
||||
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
|
||||
|
||||
var pos = $.extend({}, this.$element.offset(), {
|
||||
|
||||
+5
-5
@@ -1115,7 +1115,7 @@ body .ui-tooltip { border-width:2px; }
|
||||
|
||||
|
||||
/*** Input field styling from Bootstrap **/
|
||||
input, textarea {
|
||||
/* input, textarea {
|
||||
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
@@ -1137,12 +1137,12 @@ input[type=file]:focus, input[type=checkbox]:focus, select:focus {
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
outline: 1px dotted #666;
|
||||
}
|
||||
}*/
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
/*input[type="text"],
|
||||
input[type="password"],*/
|
||||
.ui-autocomplete-input,
|
||||
textarea,
|
||||
/*textarea,*/
|
||||
.uneditable-input {
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
|
||||
+106
-102
@@ -20,20 +20,6 @@
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.oe_topbar, .oe_leftbar, .oe_loading {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.openerp.openerp_webclient_container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.text-tag .text-button {
|
||||
height: auto !important;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
.openerp {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -46,6 +32,9 @@
|
||||
* http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image
|
||||
*/
|
||||
}
|
||||
.openerp.openerp_webclient_container {
|
||||
height: 100%;
|
||||
}
|
||||
.openerp :-moz-placeholder {
|
||||
color: #afafb6 !important;
|
||||
font-style: italic !important;
|
||||
@@ -197,6 +186,10 @@
|
||||
.openerp .oe_bounce_container {
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp .text-tag .text-button {
|
||||
height: auto !important;
|
||||
min-height: 16px;
|
||||
}
|
||||
.openerp .ui-tabs {
|
||||
position: static;
|
||||
}
|
||||
@@ -1136,6 +1129,7 @@
|
||||
height: 40px;
|
||||
width: 157px;
|
||||
margin: 14px 0;
|
||||
border: 0;
|
||||
}
|
||||
.openerp .oe_footer {
|
||||
position: fixed;
|
||||
@@ -2306,16 +2300,16 @@
|
||||
text-align: justify;
|
||||
}
|
||||
.openerp .oe_form_editable .oe_form .oe_form_field_integer input {
|
||||
width: 6em !important;
|
||||
width: 6em;
|
||||
}
|
||||
.openerp .oe_form_editable .oe_form .oe_form_field_float input {
|
||||
width: 7em !important;
|
||||
width: 7em;
|
||||
}
|
||||
.openerp .oe_form_editable .oe_form .oe_form_field_date input {
|
||||
width: 7.5em !important;
|
||||
width: 7.5em;
|
||||
}
|
||||
.openerp .oe_form_editable .oe_form .oe_form_field_datetime input {
|
||||
width: 11.5em !important;
|
||||
width: 11.5em;
|
||||
}
|
||||
.openerp .oe_hidden_input_file {
|
||||
position: relative;
|
||||
@@ -2361,6 +2355,77 @@
|
||||
.openerp .oe_form .oe_form_field_image:hover .oe_form_field_image_controls {
|
||||
display: block;
|
||||
}
|
||||
.openerp .oe_fileupload {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
left: 2px;
|
||||
top: 7px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add button {
|
||||
display: inline;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add button.oe_attach {
|
||||
width: 24px;
|
||||
overflow: hidden;
|
||||
width: 24px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
color: #7c7bad;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -9px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
|
||||
display: inline-block;
|
||||
margin-left: -5px;
|
||||
height: 28px;
|
||||
width: 52px;
|
||||
margin-top: -26px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add .oe_attach_label {
|
||||
color: #7c7bad;
|
||||
margin-left: -3px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments {
|
||||
margin-bottom: 4px;
|
||||
margin-right: 0px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
border: solid 1px rgba(124, 123, 173, 0.14);
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment {
|
||||
padding: 2px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e {
|
||||
font-size: 23px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) {
|
||||
background: white;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) {
|
||||
background: #f4f5fa;
|
||||
}
|
||||
.openerp .oe_form_field_many2one td:first-child {
|
||||
position: relative;
|
||||
}
|
||||
@@ -2374,6 +2439,9 @@
|
||||
float: right;
|
||||
padding-left: 2px;
|
||||
}
|
||||
.openerp .oe_form_field_many2one input {
|
||||
padding-right: 13px;
|
||||
}
|
||||
.openerp.ui-autocomplete li.oe_m2o_dropdown_option a {
|
||||
font-style: italic;
|
||||
padding-left: 2em;
|
||||
@@ -2578,32 +2646,33 @@
|
||||
top: 5px;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_cm_button {
|
||||
display: none;
|
||||
line-height: 19px;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_input_icon {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field {
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
|
||||
height: 27px;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
border: 1px solid #aaaaff;
|
||||
margin: 0;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select {
|
||||
min-width: 0;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
|
||||
text-align: right;
|
||||
width: 100% !important;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime > span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date > span {
|
||||
width: 100% !important;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master {
|
||||
width: 100% !important;
|
||||
}
|
||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_field_float, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_view_integer, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date {
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
.openerp .oe_list_group_name {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -2668,6 +2737,9 @@
|
||||
text-align: right !important;
|
||||
max-width: 100px;
|
||||
}
|
||||
.openerp .oe_list_content td.oe_list_field_date, .openerp .oe_list_content th.oe_list_header_date {
|
||||
min-width: 6em;
|
||||
}
|
||||
.openerp .oe_list_content > thead {
|
||||
border-bottom: 2px solid #cacaca;
|
||||
background: #eeeeee;
|
||||
@@ -2735,6 +2807,10 @@
|
||||
.openerp .oe_list_content > tbody > tr > td.oe_list_checkbox:first-child:after, .openerp .oe_list_content > tbody > tr th.oe_list_checkbox:first-child:after {
|
||||
border-width: 0;
|
||||
}
|
||||
.openerp .oe_list_content > tbody > tr > td.oe_list_field_boolean input {
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.openerp .oe_list_content > tbody > tr:nth-child(odd) {
|
||||
background-color: #f0f0fa;
|
||||
background-color: #f0f0fa;
|
||||
@@ -2882,78 +2958,6 @@
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.openerp .oe_fileupload {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
left: 2px;
|
||||
top: 7px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add button {
|
||||
display: inline;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add button.oe_attach {
|
||||
width: 24px;
|
||||
overflow: hidden;
|
||||
width: 24px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
color: #7c7bad;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -9px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
|
||||
display: inline-block;
|
||||
margin-left: -5px;
|
||||
height: 28px;
|
||||
width: 52px;
|
||||
margin-top: -26px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_add .oe_attach_label {
|
||||
color: #7c7bad;
|
||||
margin-left: -3px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments {
|
||||
margin-bottom: 4px;
|
||||
margin-right: 0px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
border: solid 1px rgba(124, 123, 173, 0.14);
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment {
|
||||
padding: 2px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e {
|
||||
font-size: 23px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) {
|
||||
background: white;
|
||||
}
|
||||
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) {
|
||||
background: #f4f5fa;
|
||||
}
|
||||
|
||||
.kitten-mode-activated {
|
||||
background-image: url(http://placekitten.com/g/1365/769);
|
||||
background-size: cover;
|
||||
@@ -3012,8 +3016,8 @@ div.ui-widget-overlay {
|
||||
.openerp {
|
||||
text-shadow: none;
|
||||
}
|
||||
.openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default {
|
||||
display: none;
|
||||
.openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default, .openerp .oe_topbar, .openerp .oe_leftbar, .openerp .oe_loading {
|
||||
display: none !important;
|
||||
}
|
||||
.openerp .oe_list_content button, .openerp .oe_list_content input[type=checkbox] {
|
||||
visibility: hidden;
|
||||
|
||||
@@ -140,18 +140,6 @@ $sheet-padding: 16px
|
||||
|
||||
// }}}
|
||||
|
||||
@media print
|
||||
.oe_topbar, .oe_leftbar, .oe_loading
|
||||
display: none !important
|
||||
|
||||
.openerp.openerp_webclient_container
|
||||
height: 100%
|
||||
|
||||
// jQueryUI css bug fixing
|
||||
.text-tag .text-button
|
||||
height: auto !important
|
||||
min-height: 16px
|
||||
|
||||
.openerp
|
||||
// Global style {{{
|
||||
padding: 0
|
||||
@@ -161,6 +149,8 @@ $sheet-padding: 16px
|
||||
font-size: 13px
|
||||
background: white
|
||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5)
|
||||
&.openerp_webclient_container
|
||||
height: 100%
|
||||
// }}}
|
||||
//Placeholder style{{{
|
||||
\:-moz-placeholder
|
||||
@@ -254,6 +244,11 @@ $sheet-padding: 16px
|
||||
.oe_bounce_container
|
||||
display: inline-block
|
||||
|
||||
// Bug lp:1051746
|
||||
.text-tag .text-button
|
||||
height: auto !important
|
||||
min-height: 16px
|
||||
|
||||
// bug noted in jquery ui CSS doesn't seem to occur in IE9,
|
||||
// so remove position:relative
|
||||
.ui-tabs
|
||||
@@ -928,6 +923,7 @@ $sheet-padding: 16px
|
||||
height: 40px
|
||||
width: 157px
|
||||
margin: 14px 0
|
||||
border: 0
|
||||
.oe_footer
|
||||
position: fixed
|
||||
bottom: 0
|
||||
@@ -1837,13 +1833,13 @@ $sheet-padding: 16px
|
||||
.oe_form_editable
|
||||
.oe_form
|
||||
.oe_form_field_integer input
|
||||
width: 6em !important
|
||||
width: 6em
|
||||
.oe_form_field_float input
|
||||
width: 7em !important
|
||||
width: 7em
|
||||
.oe_form_field_date input
|
||||
width: 7.5em !important
|
||||
width: 7.5em
|
||||
.oe_form_field_datetime input
|
||||
width: 11.5em !important
|
||||
width: 11.5em
|
||||
// }}}
|
||||
// FormView.fields_binary {{{
|
||||
/* http://www.quirksmode.org/dom/inputfile.html
|
||||
@@ -1886,6 +1882,64 @@ $sheet-padding: 16px
|
||||
@include box-sizing(border)
|
||||
&:hover .oe_form_field_image_controls
|
||||
display: block
|
||||
.oe_fileupload
|
||||
display: inline-block
|
||||
clear: both
|
||||
width: 100%
|
||||
.oe_add
|
||||
float: left
|
||||
position: relative
|
||||
width: 100%
|
||||
left: +2px
|
||||
top: +7px
|
||||
button
|
||||
display: inline
|
||||
height: 24px
|
||||
font-size: 12px
|
||||
line-height: 12px
|
||||
vertical-align: middle
|
||||
button.oe_attach
|
||||
width: 24px
|
||||
overflow: hidden
|
||||
width: 24px
|
||||
overflow: hidden
|
||||
background: transparent
|
||||
color: #7C7BAD
|
||||
box-shadow: none
|
||||
border: none
|
||||
text-shadow: none
|
||||
.oe_e
|
||||
position: relative
|
||||
top: -1px
|
||||
left: -9px
|
||||
input.oe_form_binary_file
|
||||
display: inline-block
|
||||
margin-left: -5px
|
||||
height: 28px
|
||||
width: 52px
|
||||
margin-top: -26px
|
||||
.oe_attach_label
|
||||
color: #7C7BAD
|
||||
margin-left: -3px
|
||||
.oe_attachments
|
||||
margin-bottom: 4px
|
||||
margin-right: 0px
|
||||
font-size: 12px
|
||||
border-radius: 2px
|
||||
border: solid 1px rgba(124,123,173,0.14)
|
||||
.oe_attachment
|
||||
padding: 2px
|
||||
padding-left: 4px
|
||||
padding-right: 4px
|
||||
.oe_e
|
||||
font-size: 23px
|
||||
margin-top: -5px
|
||||
.oe_e:hover
|
||||
text-decoration: none
|
||||
.oe_attachment:nth-child(odd)
|
||||
background: white
|
||||
.oe_attachment:nth-child(even)
|
||||
background: #F4F5FA
|
||||
// }}}
|
||||
// FormView.many2one {{{
|
||||
.oe_form_field_many2one
|
||||
@@ -1899,6 +1953,8 @@ $sheet-padding: 16px
|
||||
line-height: 14px
|
||||
float: right
|
||||
padding-left: 2px
|
||||
input
|
||||
padding-right: 13px
|
||||
&.ui-autocomplete
|
||||
li.oe_m2o_dropdown_option a
|
||||
font-style: italic
|
||||
@@ -2041,26 +2097,26 @@ $sheet-padding: 16px
|
||||
.oe_m2o_drop_down_button
|
||||
top: 5px
|
||||
.oe_m2o_cm_button
|
||||
display: none
|
||||
line-height: 19px
|
||||
.oe_input_icon
|
||||
margin-top: 5px
|
||||
.oe_form_field
|
||||
min-width: 0
|
||||
max-width: none
|
||||
input, textarea
|
||||
height: $row-height
|
||||
input, textarea
|
||||
@include radius(0)
|
||||
border: 1px solid #aaf
|
||||
margin: 0
|
||||
input, textarea, select
|
||||
min-width: 0
|
||||
&.oe_form_field_float,&.oe_form_view_integer
|
||||
input
|
||||
text-align: right
|
||||
width: 100% !important
|
||||
&.oe_form_field_datetime,&.oe_form_field_date
|
||||
> span
|
||||
width: 100% !important
|
||||
input.oe_datepicker_master
|
||||
width: 100% !important
|
||||
.oe_form_field_float,.oe_form_view_integer,&.oe_form_field_datetime,&.oe_form_field_date
|
||||
min-width: 0 !important
|
||||
max-width: none !important
|
||||
.oe_list_group_name
|
||||
white-space: nowrap
|
||||
// }}}
|
||||
@@ -2119,6 +2175,8 @@ $sheet-padding: 16px
|
||||
td.oe_number
|
||||
text-align: right !important
|
||||
max-width: 100px
|
||||
td.oe_list_field_date, th.oe_list_header_date
|
||||
min-width: 6em
|
||||
> thead
|
||||
border-bottom: 2px solid #cacaca
|
||||
background: #eee
|
||||
@@ -2168,6 +2226,8 @@ $sheet-padding: 16px
|
||||
width: 17px
|
||||
&:after
|
||||
border-width: 0
|
||||
> td.oe_list_field_boolean input
|
||||
@include opacity()
|
||||
> tr:nth-child(odd)
|
||||
background-color: #f0f0fa
|
||||
@include vertical-gradient(#f0f0fa, #eeeef6)
|
||||
@@ -2282,67 +2342,6 @@ $sheet-padding: 16px
|
||||
float: right
|
||||
color: #333
|
||||
// }}}
|
||||
|
||||
.openerp
|
||||
.oe_fileupload
|
||||
display: inline-block
|
||||
clear: both
|
||||
width: 100%
|
||||
.oe_add
|
||||
float: left
|
||||
position: relative
|
||||
width: 100%
|
||||
left: +2px
|
||||
top: +7px
|
||||
button
|
||||
display: inline
|
||||
height: 24px
|
||||
font-size: 12px
|
||||
line-height: 12px
|
||||
vertical-align: middle
|
||||
button.oe_attach
|
||||
width: 24px
|
||||
overflow: hidden
|
||||
width: 24px
|
||||
overflow: hidden
|
||||
background: transparent
|
||||
color: #7C7BAD
|
||||
box-shadow: none
|
||||
border: none
|
||||
text-shadow: none
|
||||
.oe_e
|
||||
position: relative
|
||||
top: -1px
|
||||
left: -9px
|
||||
input.oe_form_binary_file
|
||||
display: inline-block
|
||||
margin-left: -5px
|
||||
height: 28px
|
||||
width: 52px
|
||||
margin-top: -26px
|
||||
.oe_attach_label
|
||||
color: #7C7BAD
|
||||
margin-left: -3px
|
||||
.oe_attachments
|
||||
margin-bottom: 4px
|
||||
margin-right: 0px
|
||||
font-size: 12px
|
||||
border-radius: 2px
|
||||
border: solid 1px rgba(124,123,173,0.14)
|
||||
.oe_attachment
|
||||
padding: 2px
|
||||
padding-left: 4px
|
||||
padding-right: 4px
|
||||
.oe_e
|
||||
font-size: 23px
|
||||
margin-top: -5px
|
||||
.oe_e:hover
|
||||
text-decoration: none
|
||||
.oe_attachment:nth-child(odd)
|
||||
background: white
|
||||
.oe_attachment:nth-child(even)
|
||||
background: #F4F5FA
|
||||
|
||||
// Kitten Mode {{{
|
||||
.kitten-mode-activated
|
||||
background-image: url(http://placekitten.com/g/1365/769)
|
||||
@@ -2352,11 +2351,13 @@ $sheet-padding: 16px
|
||||
opacity: 0.70
|
||||
// }}}
|
||||
|
||||
// jQueryUI top level {{{
|
||||
// The jQuery-ui overlay and Autocomplete are outside the .openerp div, please don't add indentation !!!
|
||||
div.ui-widget-overlay
|
||||
background: black
|
||||
@include opacity(0.3)
|
||||
|
||||
// TODO: I think only the overlay is problematic, the other top level widgets should use $.fn.openerpClass()
|
||||
// eg: $el.autocomplete().openerpClass();
|
||||
.ui-widget
|
||||
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif
|
||||
color: #4c4c4c
|
||||
@@ -2383,11 +2384,14 @@ div.ui-widget-overlay
|
||||
|
||||
.ui-corner-all
|
||||
@include radius(3px)
|
||||
// }}}
|
||||
|
||||
// @media print {{{
|
||||
@media print
|
||||
.openerp
|
||||
.oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default
|
||||
display: none
|
||||
.oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default, .oe_topbar, .oe_leftbar, .oe_loading
|
||||
// We use !important here because jQuery adds @style = display: block on elements when using $.fn.show()
|
||||
display: none !important
|
||||
.oe_list_content
|
||||
button, input[type=checkbox]
|
||||
visibility: hidden
|
||||
@@ -2413,5 +2417,7 @@ div.ui-widget-overlay
|
||||
background: none
|
||||
.openerp div.oe_mail_wall
|
||||
overflow: hidden !important
|
||||
// }}}
|
||||
|
||||
// au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css"
|
||||
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
/**
|
||||
* OpenERP instance constructor
|
||||
*
|
||||
* @param {Array} modules list of modules to initialize
|
||||
* @param {Array|String} modules list of modules to initialize
|
||||
*/
|
||||
init: function(modules) {
|
||||
// By default only web will be loaded, the rest will be by loaded
|
||||
// by openerp.web.Session on the first session_authenticate
|
||||
modules = _.union(['web'], modules || []);
|
||||
if (modules === "fuck your shit, don't load anything you cunt") {
|
||||
modules = [];
|
||||
} else {
|
||||
modules = _.union(['web'], modules || []);
|
||||
}
|
||||
var new_instance = {
|
||||
// links to the global openerp
|
||||
_openerp: openerp,
|
||||
|
||||
@@ -48,7 +48,7 @@ instance.web.Notification = instance.web.Widget.extend({
|
||||
*/
|
||||
instance.web.dialog = function(element) {
|
||||
var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
|
||||
result.dialog("widget").addClass("openerp");
|
||||
result.dialog("widget").openerpClass();
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -189,8 +189,15 @@ instance.web.Dialog = instance.web.Widget.extend({
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
||||
instance.web.CrashManager = instance.web.Class.extend({
|
||||
init: function() {
|
||||
this.active = true;
|
||||
},
|
||||
|
||||
rpc_error: function(error) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
if (error.data.fault_code) {
|
||||
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
|
||||
if (split.length > 1) {
|
||||
@@ -205,6 +212,9 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
||||
}
|
||||
},
|
||||
show_warning: function(error) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
|
||||
title: "OpenERP " + _.str.capitalize(error.type),
|
||||
buttons: [
|
||||
@@ -213,7 +223,9 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
||||
});
|
||||
},
|
||||
show_error: function(error) {
|
||||
var self = this;
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
var buttons = {};
|
||||
buttons[_t("Ok")] = function() {
|
||||
$(this).dialog("close");
|
||||
@@ -303,14 +315,14 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
||||
$('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty();
|
||||
var fetch_db = this.rpc("/web/database/get_list", {}).then(
|
||||
function(result) {
|
||||
self.db_list = result.db_list;
|
||||
self.db_list = result;
|
||||
},
|
||||
function (_, ev) {
|
||||
ev.preventDefault();
|
||||
self.db_list = null;
|
||||
});
|
||||
var fetch_langs = this.rpc("/web/session/get_lang_list", {}).done(function(result) {
|
||||
self.lang_list = result.lang_list;
|
||||
self.lang_list = result;
|
||||
});
|
||||
return $.when(fetch_db, fetch_langs).done(self.do_render);
|
||||
},
|
||||
@@ -333,6 +345,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
||||
self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label");
|
||||
self.$el.find("label").addClass("oe_form_label");
|
||||
self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create });
|
||||
self.$el.find("form[name=duplicate_db_form]").validate({ submitHandler: self.do_duplicate });
|
||||
self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop });
|
||||
self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup });
|
||||
self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore });
|
||||
@@ -418,6 +431,18 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
||||
self.do_action(client_action);
|
||||
});
|
||||
},
|
||||
do_duplicate: function(form) {
|
||||
var self = this;
|
||||
var fields = $(form).serializeArray();
|
||||
self.rpc("/web/database/duplicate", {'fields': fields}).then(function(result) {
|
||||
if (result.error) {
|
||||
self.display_error(result);
|
||||
return;
|
||||
}
|
||||
self.do_notify("Duplicating database", "The database has been duplicated.");
|
||||
self.start();
|
||||
});
|
||||
},
|
||||
do_drop: function(form) {
|
||||
var self = this;
|
||||
var $form = $(form),
|
||||
@@ -518,6 +543,9 @@ instance.web.Login = instance.web.Widget.extend({
|
||||
this.selected_db = null;
|
||||
this.selected_login = null;
|
||||
this.params = action.params || {};
|
||||
if (_.isEmpty(this.params)) {
|
||||
this.params = $.bbq.getState(true);
|
||||
}
|
||||
|
||||
if (this.params.login_successful) {
|
||||
this.on('login_successful', this, this.params.login_successful);
|
||||
@@ -548,7 +576,7 @@ instance.web.Login = instance.web.Widget.extend({
|
||||
return d;
|
||||
},
|
||||
on_db_loaded: function (result) {
|
||||
this.db_list = result.db_list;
|
||||
this.db_list = result;
|
||||
this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
|
||||
if(this.db_list.length === 0) {
|
||||
this.do_action("database_manager");
|
||||
@@ -619,9 +647,35 @@ instance.web.Login = instance.web.Widget.extend({
|
||||
});
|
||||
instance.web.client_actions.add("login", "instance.web.Login");
|
||||
|
||||
/**
|
||||
* Redirect to url by replacing window.location
|
||||
* If wait is true, sleep 1s and wait for the server i.e. after a restart.
|
||||
*/
|
||||
instance.web.redirect = function(url, wait) {
|
||||
// Dont display a dialog if some xmlhttprequest are in progress
|
||||
if (instance.client && instance.client.crashmanager) {
|
||||
instance.client.crashmanager.active = false;
|
||||
}
|
||||
|
||||
var wait_server = function() {
|
||||
instance.session.rpc("/web/webclient/version_info", {}).done(function() {
|
||||
window.location = url;
|
||||
}).fail(function() {
|
||||
setTimeout(wait_server, 250);
|
||||
});
|
||||
};
|
||||
|
||||
if (wait) {
|
||||
setTimeout(wait_server, 1000);
|
||||
} else {
|
||||
window.location = url;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Client action to reload the whole interface.
|
||||
* If params has an entry 'menu_id', it opens the given menu entry.
|
||||
* If params.menu_id, it opens the given menu entry.
|
||||
* If params.wait, reload will wait the openerp server to be reachable before reloading
|
||||
*/
|
||||
instance.web.Reload = function(parent, action) {
|
||||
var params = action.params || {};
|
||||
@@ -637,8 +691,8 @@ instance.web.Reload = function(parent, action) {
|
||||
hash = "#menu_id=" + menu_id;
|
||||
}
|
||||
var url = l.protocol + "//" + l.host + l.pathname + search + hash;
|
||||
window.onerror = function() {};
|
||||
window.location = url;
|
||||
|
||||
instance.web.redirect(url, params.wait);
|
||||
};
|
||||
instance.web.client_actions.add("reload", "instance.web.Reload");
|
||||
|
||||
@@ -648,7 +702,7 @@ instance.web.client_actions.add("reload", "instance.web.Reload");
|
||||
*/
|
||||
instance.web.HistoryBack = function(parent) {
|
||||
if (!parent.history_back()) {
|
||||
window.location = '/' + (window.location.search || '');
|
||||
instance.web.Home(parent);
|
||||
}
|
||||
};
|
||||
instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
|
||||
@@ -656,11 +710,10 @@ instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
|
||||
/**
|
||||
* Client action to go back home.
|
||||
*/
|
||||
instance.web.Home = instance.web.Widget.extend({
|
||||
init: function(parent) {
|
||||
window.location = '/' + (window.location.search || '');
|
||||
}
|
||||
});
|
||||
instance.web.Home = function(parent, action) {
|
||||
var url = '/' + (window.location.search || '');
|
||||
instance.web.redirect(url, action.params && action.params.wait);
|
||||
};
|
||||
instance.web.client_actions.add("home", "instance.web.Home");
|
||||
|
||||
instance.web.ChangePassword = instance.web.Widget.extend({
|
||||
@@ -887,7 +940,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
|
||||
if(res.company_id[0] > 1)
|
||||
topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]);
|
||||
self.$el.find('.oe_topbar_name').text(topbar_name);
|
||||
var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=image_small&id=%s', self.session.prefix, self.session.session_id, self.session.uid);
|
||||
var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid});
|
||||
$avatar.attr('src', avatar_src);
|
||||
});
|
||||
};
|
||||
@@ -899,7 +952,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
|
||||
on_menu_settings: function() {
|
||||
var self = this;
|
||||
if (!this.getParent().has_uncommitted_changes()) {
|
||||
self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) {
|
||||
self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
|
||||
result.res_id = instance.session.uid;
|
||||
self.getParent().action_manager.do_action(result);
|
||||
});
|
||||
@@ -930,6 +983,7 @@ instance.web.Client = instance.web.Widget.extend({
|
||||
return instance.session.session_bind(this.origin).then(function() {
|
||||
var $e = $(QWeb.render(self._template, {}));
|
||||
self.replaceElement($e);
|
||||
$e.openerpClass();
|
||||
self.bind_events();
|
||||
return self.show_common();
|
||||
});
|
||||
@@ -1106,10 +1160,10 @@ instance.web.WebClient = instance.web.Client.extend({
|
||||
var self = this;
|
||||
var state = event.getState(true);
|
||||
if (!_.isEqual(this._current_state, state)) {
|
||||
if(state.action_id === undefined && state.menu_id) {
|
||||
if(!state.action && state.menu_id) {
|
||||
self.menu.has_been_loaded.done(function() {
|
||||
self.menu.do_reload().done(function() {
|
||||
self.menu.menu_click(state.menu_id)
|
||||
self.menu.menu_click(state.menu_id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
+144
-156
@@ -243,15 +243,17 @@ instance.web.ParentedMixin = {
|
||||
|
||||
/**
|
||||
* Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
|
||||
*
|
||||
* This class just handle the dispatching of events, it is not meant to be extended,
|
||||
* nor used directly. All integration with parenting and automatic unregistration of
|
||||
* events is done in EventDispatcherMixin.
|
||||
*
|
||||
* Copyright notice for the following Class:
|
||||
*
|
||||
* (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
* Backbone may be freely distributed under the MIT license.
|
||||
* For all details and documentation:
|
||||
* http://backbonejs.org
|
||||
*
|
||||
* This class just handle the dispatching of events, it is not meant to be extended,
|
||||
* nor used directly. All integration with parenting and automatic unregistration of
|
||||
* events is done in EventDispatcherMixin.
|
||||
*
|
||||
*/
|
||||
var Events = instance.web.Class.extend({
|
||||
@@ -334,7 +336,6 @@ var Events = instance.web.Class.extend({
|
||||
return this;
|
||||
}
|
||||
});
|
||||
// end of Jeremy Ashkenas' code
|
||||
|
||||
instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
|
||||
__eventDispatcherMixin: true,
|
||||
@@ -426,66 +427,94 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
|
||||
init: function() {
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
var self = this;
|
||||
// Transform on_/do_* methods into callbacks
|
||||
var callback_maker = function(fn) {
|
||||
return function() {
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
for (var name in this) {
|
||||
if(typeof(this[name]) == "function") {
|
||||
if((/^on_|^do_/).test(name)) {
|
||||
this[name] = callback_maker(this[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Proxies a method of the object, in order to keep the right ``this`` on
|
||||
* method invocations.
|
||||
*
|
||||
* This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
|
||||
* even more so to ``jQuery.proxy`` with a fundamental difference: its
|
||||
* resolution of the method being called is lazy, meaning it will use the
|
||||
* method as it is when the proxy is called, not when the proxy is created.
|
||||
*
|
||||
* Other methods will fix the bound method to what it is when creating the
|
||||
* binding/proxy, which is fine in most javascript code but problematic in
|
||||
* OpenERP Web where developers may want to replace existing callbacks with
|
||||
* theirs.
|
||||
*
|
||||
* The semantics of this precisely replace closing over the method call.
|
||||
*
|
||||
* @param {String|Function} method function or name of the method to invoke
|
||||
* @returns {Function} proxied method
|
||||
*/
|
||||
proxy: function (method) {
|
||||
var self = this;
|
||||
return function () {
|
||||
var fn = (typeof method === 'string') ? self[method] : method;
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Classes
|
||||
|
||||
instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||
/**
|
||||
* Base class for all visual components. Provides a lot of functionalities helpful
|
||||
* for the management of a part of the DOM.
|
||||
*
|
||||
* Widget handles:
|
||||
* - Rendering with QWeb.
|
||||
* - Life-cycle management and parenting (when a parent is destroyed, all its children are
|
||||
* destroyed too).
|
||||
* - Insertion in DOM.
|
||||
*
|
||||
* Guide to create implementations of the Widget class:
|
||||
* ==============================================
|
||||
*
|
||||
* Here is a sample child class:
|
||||
*
|
||||
* MyWidget = instance.base.Widget.extend({
|
||||
* // the name of the QWeb template to use for rendering
|
||||
* template: "MyQWebTemplate",
|
||||
*
|
||||
* init: function(parent) {
|
||||
* this._super(parent);
|
||||
* // stuff that you want to init before the rendering
|
||||
* },
|
||||
* start: function() {
|
||||
* // stuff you want to make after the rendering, `this.$el` holds a correct value
|
||||
* this.$el.find(".my_button").click(/* an example of event binding * /);
|
||||
*
|
||||
* // if you have some asynchronous operations, it's a good idea to return
|
||||
* // a promise in start()
|
||||
* var promise = this.rpc(...);
|
||||
* return promise;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Now this class can simply be used with the following syntax:
|
||||
*
|
||||
* var my_widget = new MyWidget(this);
|
||||
* my_widget.appendTo($(".some-div"));
|
||||
*
|
||||
* With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
|
||||
* DOM inside the ".some-div" div and its events were binded.
|
||||
*
|
||||
* And of course, when you don't need that widget anymore, just do:
|
||||
*
|
||||
* my_widget.destroy();
|
||||
*
|
||||
* That will kill the widget in a clean way and erase its content from the dom.
|
||||
*/
|
||||
instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
// Backbone-ish API
|
||||
tagName: 'div',
|
||||
id: null,
|
||||
className: null,
|
||||
attributes: {},
|
||||
events: {},
|
||||
/**
|
||||
* The name of the QWeb template that will be used for rendering. Must be
|
||||
* redefined in subclasses or the default render() method can not be used.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
template: null,
|
||||
/**
|
||||
* Constructs the widget and sets its parent if a parent is given.
|
||||
*
|
||||
* @constructs instance.web.Widget
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
||||
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.CallbackEnabledMixin.init.call(this);
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.setParent(parent);
|
||||
// Bind on_/do_* methods to this
|
||||
// We might remove this automatic binding in the future
|
||||
for (var name in this) {
|
||||
if(typeof(this[name]) == "function") {
|
||||
if((/^on_|^do_/).test(name)) {
|
||||
this[name] = this[name].bind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME: this should not be
|
||||
this.setElement(this._make_descriptive());
|
||||
this.session = instance.session;
|
||||
},
|
||||
/**
|
||||
* Destroys the current widget, also destroys all its children before destroying itself.
|
||||
@@ -574,94 +603,32 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||
*/
|
||||
start: function() {
|
||||
return $.when();
|
||||
}
|
||||
});
|
||||
|
||||
// Classes
|
||||
|
||||
instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEnabledMixin, {
|
||||
init: function() {
|
||||
instance.web.CallbackEnabledMixin.init.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Base class for all visual components. Provides a lot of functionalities helpful
|
||||
* for the management of a part of the DOM.
|
||||
*
|
||||
* Widget handles:
|
||||
* - Rendering with QWeb.
|
||||
* - Life-cycle management and parenting (when a parent is destroyed, all its children are
|
||||
* destroyed too).
|
||||
* - Insertion in DOM.
|
||||
*
|
||||
* Guide to create implementations of the Widget class:
|
||||
* ==============================================
|
||||
*
|
||||
* Here is a sample child class:
|
||||
*
|
||||
* MyWidget = instance.base.Widget.extend({
|
||||
* // the name of the QWeb template to use for rendering
|
||||
* template: "MyQWebTemplate",
|
||||
*
|
||||
* init: function(parent) {
|
||||
* this._super(parent);
|
||||
* // stuff that you want to init before the rendering
|
||||
* },
|
||||
* start: function() {
|
||||
* // stuff you want to make after the rendering, `this.$el` holds a correct value
|
||||
* this.$el.find(".my_button").click(/* an example of event binding * /);
|
||||
*
|
||||
* // if you have some asynchronous operations, it's a good idea to return
|
||||
* // a promise in start()
|
||||
* var promise = this.rpc(...);
|
||||
* return promise;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Now this class can simply be used with the following syntax:
|
||||
*
|
||||
* var my_widget = new MyWidget(this);
|
||||
* my_widget.appendTo($(".some-div"));
|
||||
*
|
||||
* With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
|
||||
* DOM inside the ".some-div" div and its events were binded.
|
||||
*
|
||||
* And of course, when you don't need that widget anymore, just do:
|
||||
*
|
||||
* my_widget.destroy();
|
||||
*
|
||||
* That will kill the widget in a clean way and erase its content from the dom.
|
||||
*/
|
||||
instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||
// Backbone-ish API
|
||||
tagName: 'div',
|
||||
id: null,
|
||||
className: null,
|
||||
attributes: {},
|
||||
events: {},
|
||||
},
|
||||
/**
|
||||
* The name of the QWeb template that will be used for rendering. Must be
|
||||
* redefined in subclasses or the default render() method can not be used.
|
||||
* Proxies a method of the object, in order to keep the right ``this`` on
|
||||
* method invocations.
|
||||
*
|
||||
* @type string
|
||||
* This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
|
||||
* even more so to ``jQuery.proxy`` with a fundamental difference: its
|
||||
* resolution of the method being called is lazy, meaning it will use the
|
||||
* method as it is when the proxy is called, not when the proxy is created.
|
||||
*
|
||||
* Other methods will fix the bound method to what it is when creating the
|
||||
* binding/proxy, which is fine in most javascript code but problematic in
|
||||
* OpenERP Web where developers may want to replace existing callbacks with
|
||||
* theirs.
|
||||
*
|
||||
* The semantics of this precisely replace closing over the method call.
|
||||
*
|
||||
* @param {String|Function} method function or name of the method to invoke
|
||||
* @returns {Function} proxied method
|
||||
*/
|
||||
template: null,
|
||||
/**
|
||||
* Constructs the widget and sets its parent if a parent is given.
|
||||
*
|
||||
* @constructs instance.web.Widget
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
||||
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.WidgetMixin.init.call(this,parent);
|
||||
// FIXME: this should not be
|
||||
this.setElement(this._make_descriptive());
|
||||
this.session = instance.session;
|
||||
proxy: function (method) {
|
||||
var self = this;
|
||||
return function () {
|
||||
var fn = (typeof method === 'string') ? self[method] : method;
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Renders the element. The default implementation renders the widget using QWeb,
|
||||
@@ -678,7 +645,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||
}
|
||||
this.replaceElement($el);
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-sets the widget's root element and replaces the old root element
|
||||
* (if any) by the new one in the DOM.
|
||||
@@ -808,10 +774,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
rpc: function(url, data, success, error) {
|
||||
var def = $.Deferred().done(success).fail(error);
|
||||
rpc: function(url, data, options) {
|
||||
var def = $.Deferred();
|
||||
var self = this;
|
||||
instance.session.rpc(url, data).done(function() {
|
||||
instance.session.rpc(url, data, options).done(function() {
|
||||
if (!self.isDestroyed())
|
||||
def.resolve.apply(def, arguments);
|
||||
}).fail(function() {
|
||||
@@ -941,7 +907,7 @@ instance.web.Registry = instance.web.Class.extend({
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
triggers: {
|
||||
'request': 'Request sent',
|
||||
'response': 'Response received',
|
||||
@@ -950,13 +916,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
},
|
||||
/**
|
||||
* @constructs instance.web.JsonRPC
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {String} [server] JSON-RPC endpoint hostname
|
||||
* @param {String} [port] JSON-RPC endpoint port
|
||||
*/
|
||||
init: function() {
|
||||
this._super();
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.server = null;
|
||||
this.debug = ($.deparam($.param.querystring()).debug != undefined);
|
||||
},
|
||||
@@ -1266,12 +1231,14 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
*
|
||||
* @param {String} url RPC endpoint
|
||||
* @param {Object} params call parameters
|
||||
* @param {Object} options additional options for rpc call
|
||||
* @param {Function} success_callback function to execute on RPC call success
|
||||
* @param {Function} error_callback function to execute on RPC call failure
|
||||
* @returns {jQuery.Deferred} jquery-provided ajax deferred
|
||||
*/
|
||||
rpc: function(url, params) {
|
||||
rpc: function(url, params, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
// url can be an $.ajax option object
|
||||
if (_.isString(url)) {
|
||||
url = { url: url };
|
||||
@@ -1286,10 +1253,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
id: _.uniqueId('r')
|
||||
};
|
||||
var deferred = $.Deferred();
|
||||
this.trigger('request', url, payload);
|
||||
if (! options.shadow)
|
||||
this.trigger('request', url, payload);
|
||||
var request = this.rpc_function(url, payload).done(
|
||||
function (response, textStatus, jqXHR) {
|
||||
self.trigger('response', response);
|
||||
if (! options.shadow)
|
||||
self.trigger('response', response);
|
||||
if (!response.error) {
|
||||
if (url.url === '/web/session/eval_domain_and_context') {
|
||||
self.test_eval(params, response.result);
|
||||
@@ -1303,7 +1272,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
}
|
||||
).fail(
|
||||
function(jqXHR, textStatus, errorThrown) {
|
||||
self.trigger('response_failed', jqXHR);
|
||||
if (! options.shadow)
|
||||
self.trigger('response_failed', jqXHR);
|
||||
var error = {
|
||||
code: -32098,
|
||||
message: "XmlHttpRequestError " + errorThrown,
|
||||
@@ -1311,7 +1281,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
};
|
||||
deferred.reject(error, $.Event());
|
||||
});
|
||||
// Allow deferred user to disable on_rpc_error in fail
|
||||
// Allow deferred user to disable rpc_error call in fail
|
||||
deferred.fail(function() {
|
||||
deferred.fail(function(error, event) {
|
||||
if (!event.isDefaultPrevented()) {
|
||||
@@ -1344,9 +1314,18 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
// extracted from payload to set on the url
|
||||
var data = {
|
||||
session_id: this.session_id,
|
||||
id: payload.id
|
||||
id: payload.id,
|
||||
sid: this.httpsessionid,
|
||||
};
|
||||
url.url = this.get_url(url.url);
|
||||
|
||||
var set_sid = function (response, textStatus, jqXHR) {
|
||||
// If response give us the http session id, we store it for next requests...
|
||||
if (response.httpsessionid) {
|
||||
self.httpsessionid = response.httpsessionid;
|
||||
}
|
||||
};
|
||||
|
||||
url.url = this.url(url.url, null);
|
||||
var ajax = _.extend({
|
||||
type: "GET",
|
||||
dataType: 'jsonp',
|
||||
@@ -1361,7 +1340,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
if(payload_url.length < 2000) {
|
||||
// Direct jsonp request
|
||||
ajax.data.r = payload_str;
|
||||
return $.ajax(ajax);
|
||||
return $.ajax(ajax).done(set_sid);
|
||||
} else {
|
||||
// Indirect jsonp request
|
||||
var ifid = _.uniqueId('oe_rpc_iframe');
|
||||
@@ -1399,11 +1378,20 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
});
|
||||
// append the iframe to the DOM (will trigger the first load)
|
||||
$form.after($iframe);
|
||||
return deferred;
|
||||
return deferred.done(set_sid);
|
||||
}
|
||||
},
|
||||
get_url: function (file) {
|
||||
return this.prefix + file;
|
||||
|
||||
url: function(path, params) {
|
||||
var qs = '';
|
||||
if (!_.isNull(params)) {
|
||||
params = _.extend(params || {}, {session_id: this.session_id});
|
||||
if (this.httpsessionid) {
|
||||
params.sid = this.httpsessionid;
|
||||
}
|
||||
qs = '?' + $.param(params);
|
||||
}
|
||||
return this.prefix + path + qs;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
*--------------------------------------------------------*/
|
||||
var console;
|
||||
if (!console) {
|
||||
console = {log: function () {}};
|
||||
}
|
||||
if (!console.debug) {
|
||||
console.debug = console.log;
|
||||
// Even IE9 only exposes console object if debug window opened
|
||||
console = {};
|
||||
('log error debug info warn assert clear dir dirxml trace group'
|
||||
+ ' groupCollapsed groupEnd time timeEnd profile profileEnd count'
|
||||
+ ' exception').split(/\s+/).forEach(function(property) {
|
||||
console[property] = _.identity;
|
||||
});
|
||||
}
|
||||
|
||||
openerp.web.coresetup = function(instance) {
|
||||
@@ -19,9 +22,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
this.name = instance._session_id;
|
||||
this.qweb_mutex = new $.Mutex();
|
||||
},
|
||||
rpc: function(url, params) {
|
||||
rpc: function(url, params, options) {
|
||||
params.session_id = this.session_id;
|
||||
return this._super(url, params);
|
||||
return this._super(url, params, options);
|
||||
},
|
||||
/**
|
||||
* Setup a sessionm
|
||||
@@ -53,7 +56,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
this.session_id = this.get_cookie('session_id');
|
||||
return this.session_reload().then(function(result) {
|
||||
var modules = instance._modules.join(',');
|
||||
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.do_load_qweb);
|
||||
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
|
||||
if(self.session_is_valid()) {
|
||||
return deferred.then(function() { return self.load_modules(); });
|
||||
}
|
||||
@@ -155,28 +158,26 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
load_modules: function() {
|
||||
var self = this;
|
||||
return this.rpc('/web/session/modules', {}).then(function(result) {
|
||||
var lang = self.user_context.lang,
|
||||
all_modules = _.uniq(self.module_list.concat(result));
|
||||
var params = { mods: all_modules, lang: lang};
|
||||
var all_modules = _.uniq(self.module_list.concat(result));
|
||||
var to_load = _.difference(result, self.module_list).join(',');
|
||||
self.module_list = all_modules;
|
||||
|
||||
var loaded = self.rpc('/web/webclient/translations', params).done(function(trans) {
|
||||
instance.web._t.database.set_bundle(trans);
|
||||
});
|
||||
var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"];
|
||||
var loaded = self.load_translations();
|
||||
var datejs_locale = "/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js";
|
||||
|
||||
var file_list = [ datejs_locale ];
|
||||
if(to_load.length) {
|
||||
loaded = $.when(
|
||||
loaded,
|
||||
self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.do_load_css),
|
||||
self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.do_load_qweb),
|
||||
self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
|
||||
self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.load_qweb.bind(self)),
|
||||
self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) {
|
||||
file_list = file_list.concat(files);
|
||||
})
|
||||
);
|
||||
}
|
||||
return loaded.then(function () {
|
||||
return self.do_load_js(file_list);
|
||||
return self.load_js(file_list);
|
||||
}).done(function() {
|
||||
self.on_modules_loaded();
|
||||
self.trigger('module_loaded');
|
||||
@@ -190,29 +191,35 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
});
|
||||
});
|
||||
},
|
||||
do_load_css: function (files) {
|
||||
load_translations: function() {
|
||||
var params = { mods: this.module_list, lang: this.user_context.lang };
|
||||
return this.rpc('/web/webclient/translations', params).done(function(trans) {
|
||||
instance.web._t.database.set_bundle(trans);
|
||||
});
|
||||
},
|
||||
load_css: function (files) {
|
||||
var self = this;
|
||||
_.each(files, function (file) {
|
||||
$('head').append($('<link>', {
|
||||
'href': self.get_url(file),
|
||||
'href': self.url(file, null),
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css'
|
||||
}));
|
||||
});
|
||||
},
|
||||
do_load_js: function(files) {
|
||||
load_js: function(files) {
|
||||
var self = this;
|
||||
var d = $.Deferred();
|
||||
if(files.length != 0) {
|
||||
if(files.length !== 0) {
|
||||
var file = files.shift();
|
||||
var tag = document.createElement('script');
|
||||
tag.type = 'text/javascript';
|
||||
tag.src = self.get_url(file);
|
||||
tag.src = self.url(file, null);
|
||||
tag.onload = tag.onreadystatechange = function() {
|
||||
if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
|
||||
return;
|
||||
tag.onload_done = true;
|
||||
self.do_load_js(files).done(function () {
|
||||
self.load_js(files).done(function () {
|
||||
d.resolve();
|
||||
});
|
||||
};
|
||||
@@ -223,7 +230,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
}
|
||||
return d;
|
||||
},
|
||||
do_load_qweb: function(files) {
|
||||
load_qweb: function(files) {
|
||||
var self = this;
|
||||
_.each(files, function(file) {
|
||||
self.qweb_mutex.exec(function() {
|
||||
@@ -453,6 +460,16 @@ $.fn.getAttributes = function() {
|
||||
}
|
||||
return o;
|
||||
}
|
||||
$.fn.openerpClass = function(additionalClass) {
|
||||
// This plugin should be applied on top level elements
|
||||
additionalClass = additionalClass || '';
|
||||
if (!!$.browser.msie) {
|
||||
additionalClass += ' openerp_ie';
|
||||
}
|
||||
return this.each(function() {
|
||||
$(this).addClass('openerp ' + additionalClass);
|
||||
});
|
||||
};
|
||||
|
||||
/** Jquery extentions */
|
||||
$.Mutex = (function() {
|
||||
|
||||
@@ -278,9 +278,10 @@ instance.web.Model = instance.web.Class.extend({
|
||||
* @param {String} method name of the method to call
|
||||
* @param {Array} [args] positional arguments
|
||||
* @param {Object} [kwargs] keyword arguments
|
||||
* @param {Object} [options] additional options for the rpc() method
|
||||
* @returns {jQuery.Deferred<>} call result
|
||||
*/
|
||||
call: function (method, args, kwargs) {
|
||||
call: function (method, args, kwargs, options) {
|
||||
args = args || [];
|
||||
kwargs = kwargs || {};
|
||||
if (!_.isArray(args)) {
|
||||
@@ -294,7 +295,7 @@ instance.web.Model = instance.web.Class.extend({
|
||||
method: method,
|
||||
args: args,
|
||||
kwargs: kwargs
|
||||
});
|
||||
}, options);
|
||||
},
|
||||
/**
|
||||
* Fetches a Query instance bound to this model, for searching
|
||||
@@ -358,17 +359,16 @@ instance.web.Model = instance.web.Class.extend({
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.DataSet = instance.web.CallbackEnabled.extend({
|
||||
instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
/**
|
||||
* Collection of OpenERP records, used to share records and the current selection between views.
|
||||
*
|
||||
* @constructs instance.web.DataSet
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {String} model the OpenERP model this dataset will manage
|
||||
*/
|
||||
init: function(parent, model, context) {
|
||||
this._super(parent);
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.model = model;
|
||||
this.context = context || {};
|
||||
this.index = null;
|
||||
@@ -722,10 +722,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
||||
this.last_default_get = {};
|
||||
},
|
||||
default_get: function(fields, options) {
|
||||
return this._super(fields, options).done(this.on_default_get);
|
||||
},
|
||||
on_default_get: function(res) {
|
||||
this.last_default_get = res;
|
||||
var self = this;
|
||||
return this._super(fields, options).done(function(res) {
|
||||
self.last_default_get = res;
|
||||
});
|
||||
},
|
||||
create: function(data) {
|
||||
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
|
||||
|
||||
@@ -83,6 +83,42 @@ instance.web.strip_raw_chars = function (value) {
|
||||
var normalize_format = function (format) {
|
||||
return Date.normalizeFormat(instance.web.strip_raw_chars(format));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check with a scary heuristic if the value is a bin_size or not.
|
||||
* If not, compute an approximate size out of the base64 encoded string.
|
||||
*
|
||||
* @param {String} value original format
|
||||
*/
|
||||
instance.web.binary_to_binsize = function (value) {
|
||||
if (!value) {
|
||||
return instance.web.human_size(0);
|
||||
}
|
||||
if (value.substr(0, 10).indexOf(' ') == -1) {
|
||||
// Computing approximate size out of base64 encoded string
|
||||
// http://en.wikipedia.org/wiki/Base64#MIME
|
||||
return instance.web.human_size(value.length / 1.37);
|
||||
} else {
|
||||
// already bin_size
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a human readable size
|
||||
*
|
||||
* @param {Number} numner of bytes
|
||||
*/
|
||||
instance.web.human_size = function(size) {
|
||||
var units = _t("Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb").split(',');
|
||||
var i = 0;
|
||||
while (size >= 1024) {
|
||||
size /= 1024;
|
||||
++i;
|
||||
}
|
||||
return size.toFixed(2) + ' ' + units[i];
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a single atomic value based on a field descriptor
|
||||
*
|
||||
|
||||
@@ -349,7 +349,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||
$.when(load_view)
|
||||
.then(function(r) {
|
||||
self.search_view_loaded(r)
|
||||
}).fail(function () {
|
||||
}, function () {
|
||||
self.ready.reject.apply(null, arguments);
|
||||
});
|
||||
}
|
||||
@@ -459,7 +459,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||
complete_global_search: function (req, resp) {
|
||||
$.when.apply(null, _(this.inputs).chain()
|
||||
.invoke('complete', req.term)
|
||||
.value()).done(function () {
|
||||
.value()).then(function () {
|
||||
resp(_(_(arguments).compact()).flatten(true));
|
||||
});
|
||||
},
|
||||
@@ -527,7 +527,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||
childView.on('blurred', self, self.proxy('childBlurred'));
|
||||
});
|
||||
|
||||
$.when.apply(null, started).done(function () {
|
||||
$.when.apply(null, started).then(function () {
|
||||
var input_to_focus;
|
||||
// options.at: facet inserted at given index, focus next input
|
||||
// otherwise just focus last input
|
||||
@@ -608,7 +608,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||
// add Filters to this.inputs, need view.controls filled
|
||||
(new instance.web.search.Filters(this));
|
||||
// add custom filters to this.inputs
|
||||
(new instance.web.search.CustomFilters(this));
|
||||
this.custom_filters = new instance.web.search.CustomFilters(this);
|
||||
// add Advanced to this.inputs
|
||||
(new instance.web.search.Advanced(this));
|
||||
},
|
||||
@@ -635,16 +635,41 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||
|
||||
// load defaults
|
||||
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
|
||||
'facet_for_defaults', this.defaults)).done(function () {
|
||||
self.query.reset(_(arguments).compact(), {preventSearch: true});
|
||||
});
|
||||
|
||||
'facet_for_defaults', this.defaults))
|
||||
.then(this.proxy('setup_default_query'));
|
||||
|
||||
return $.when(drawer_started, defaults_fetched)
|
||||
.done(function () {
|
||||
self.trigger("search_view_loaded", data);
|
||||
self.ready.resolve();
|
||||
});
|
||||
},
|
||||
setup_default_query: function () {
|
||||
// Hacky implementation of CustomFilters#facet_for_defaults ensure
|
||||
// CustomFilters will be ready (and CustomFilters#filters will be
|
||||
// correctly filled) by the time this method executes.
|
||||
var custom_filters = this.custom_filters.filters;
|
||||
if (!_(custom_filters).isEmpty()) {
|
||||
// Check for any is_default custom filter
|
||||
var personal_filter = _(custom_filters).find(function (filter) {
|
||||
return filter.user_id && filter.is_default;
|
||||
});
|
||||
if (personal_filter) {
|
||||
this.custom_filters.enable_filter(personal_filter, true);
|
||||
return;
|
||||
}
|
||||
|
||||
var global_filter = _(custom_filters).find(function (filter) {
|
||||
return !filter.user_id && filter.is_default;
|
||||
});
|
||||
if (global_filter) {
|
||||
this.custom_filters.enable_filter(global_filter, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No custom filter, or no is_default custom filter, apply view defaults
|
||||
this.query.reset(_(arguments).compact(), {preventSearch: true});
|
||||
},
|
||||
/**
|
||||
* Extract search data from the view's facets.
|
||||
*
|
||||
@@ -1460,10 +1485,15 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
|
||||
instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
||||
template: 'SearchView.CustomFilters',
|
||||
_in_drawer: true,
|
||||
init: function () {
|
||||
this.is_ready = $.Deferred();
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.model = new instance.web.Model('ir.filters');
|
||||
this.filters = {};
|
||||
this.$filters = {};
|
||||
this.view.query
|
||||
.on('remove', function (facet) {
|
||||
if (!facet.get('is_custom_filter')) {
|
||||
@@ -1479,24 +1509,78 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
||||
// FIXME: local eval of domain and context to get rid of special endpoint
|
||||
return this.rpc('/web/searchview/get_filters', {
|
||||
model: this.view.model
|
||||
}).then(this.proxy('set_filters'));
|
||||
})
|
||||
.then(this.proxy('set_filters'))
|
||||
.then(function () {
|
||||
self.is_ready.resolve(null);
|
||||
}, function () {
|
||||
self.is_ready.reject();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Special implementation delaying defaults until CustomFilters is loaded
|
||||
*/
|
||||
facet_for_defaults: function () {
|
||||
return this.is_ready;
|
||||
},
|
||||
/**
|
||||
* Generates a mapping key (in the filters and $filter mappings) for the
|
||||
* filter descriptor object provided (as returned by ``get_filters``).
|
||||
*
|
||||
* The mapping key is guaranteed to be unique for a given (user_id, name)
|
||||
* pair.
|
||||
*
|
||||
* @param {Object} filter
|
||||
* @param {String} filter.name
|
||||
* @param {Number|Pair<Number, String>} [filter.user_id]
|
||||
* @return {String} mapping key corresponding to the filter
|
||||
*/
|
||||
key_for: function (filter) {
|
||||
var user_id = filter.user_id;
|
||||
var uid = (user_id instanceof Array) ? user_id[0] : user_id;
|
||||
return _.str.sprintf('(%s)%s', uid, filter.name);
|
||||
},
|
||||
/**
|
||||
* Generates a :js:class:`~instance.web.search.Facet` descriptor from a
|
||||
* filter descriptor
|
||||
*
|
||||
* @param {Object} filter
|
||||
* @param {String} filter.name
|
||||
* @param {Object} [filter.context]
|
||||
* @param {Array} [filter.domain]
|
||||
* @return {Object}
|
||||
*/
|
||||
facet_for: function (filter) {
|
||||
return {
|
||||
category: _t("Custom Filter"),
|
||||
icon: 'M',
|
||||
field: {
|
||||
get_context: function () { return filter.context; },
|
||||
get_groupby: function () { return [filter.context]; },
|
||||
get_domain: function () { return filter.domain; }
|
||||
},
|
||||
is_custom_filter: true,
|
||||
values: [{label: filter.name, value: null}]
|
||||
};
|
||||
},
|
||||
clear_selection: function () {
|
||||
this.$('li.oe_selected').removeClass('oe_selected');
|
||||
},
|
||||
append_filter: function (filter) {
|
||||
var self = this;
|
||||
var key = _.str.sprintf('(%s)%s', filter.user_id, filter.name);
|
||||
var key = this.key_for(filter);
|
||||
|
||||
var $filter;
|
||||
if (key in this.filters) {
|
||||
$filter = this.filters[key];
|
||||
if (key in this.$filters) {
|
||||
$filter = this.$filters[key];
|
||||
} else {
|
||||
var id = filter.id;
|
||||
$filter = this.filters[key] = $('<li></li>')
|
||||
this.filters[key] = filter;
|
||||
$filter = this.$filters[key] = $('<li></li>')
|
||||
.appendTo(this.$('.oe_searchview_custom_list'))
|
||||
.addClass(filter.user_id ? 'oe_searchview_custom_private'
|
||||
: 'oe_searchview_custom_public')
|
||||
.toggleClass('oe_searchview_custom_default', filter.is_default)
|
||||
.text(filter.name);
|
||||
|
||||
$('<a class="oe_searchview_custom_delete">x</a>')
|
||||
@@ -1504,33 +1588,30 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
||||
e.stopPropagation();
|
||||
self.model.call('unlink', [id]).done(function () {
|
||||
$filter.remove();
|
||||
delete self.$filters[key];
|
||||
delete self.filters[key];
|
||||
});
|
||||
})
|
||||
.appendTo($filter);
|
||||
}
|
||||
|
||||
$filter.unbind('click').click(function () {
|
||||
self.view.query.reset([{
|
||||
category: _t("Custom Filter"),
|
||||
icon: 'M',
|
||||
field: {
|
||||
get_context: function () { return filter.context; },
|
||||
get_groupby: function () { return [filter.context]; },
|
||||
get_domain: function () { return filter.domain; }
|
||||
},
|
||||
is_custom_filter: true,
|
||||
values: [{label: filter.name, value: null}]
|
||||
}]);
|
||||
$filter.addClass('oe_selected');
|
||||
self.enable_filter(filter);
|
||||
});
|
||||
},
|
||||
enable_filter: function (filter, preventSearch) {
|
||||
this.view.query.reset([this.facet_for(filter)], {
|
||||
preventSearch: preventSearch || false});
|
||||
this.$filters[this.key_for(filter)].addClass('oe_selected');
|
||||
},
|
||||
set_filters: function (filters) {
|
||||
_(filters).map(_.bind(this.append_filter, this));
|
||||
},
|
||||
save_current: function () {
|
||||
var self = this;
|
||||
var $name = this.$('input:first');
|
||||
var private_filter = !this.$('input:last').prop('checked');
|
||||
var private_filter = !this.$('#oe_searchview_custom_public').prop('checked');
|
||||
var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
|
||||
|
||||
var search = this.view.build_search_data();
|
||||
this.rpc('/web/session/eval_domain_and_context', {
|
||||
@@ -1546,7 +1627,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
||||
user_id: private_filter ? instance.session.uid : false,
|
||||
model_id: self.view.model,
|
||||
context: results.context,
|
||||
domain: results.domain
|
||||
domain: results.domain,
|
||||
is_default: set_as_default
|
||||
};
|
||||
// FIXME: current context?
|
||||
return self.model.call('create_or_replace', [filter]).done(function (id) {
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
// Test support structures and methods for OpenERP
|
||||
openerp.testing = {};
|
||||
(function (testing) {
|
||||
var dependencies = {
|
||||
corelib: [],
|
||||
coresetup: ['corelib'],
|
||||
data: ['corelib', 'coresetup'],
|
||||
dates: [],
|
||||
formats: ['coresetup', 'dates'],
|
||||
chrome: ['corelib', 'coresetup'],
|
||||
views: ['corelib', 'coresetup', 'data', 'chrome'],
|
||||
search: ['data', 'coresetup', 'formats'],
|
||||
list: ['views', 'data'],
|
||||
form: ['data', 'views', 'list', 'formats'],
|
||||
list_editable: ['list', 'form', 'data'],
|
||||
};
|
||||
|
||||
testing.dependencies = window['oe_all_dependencies'] || [];
|
||||
testing.current_module = null;
|
||||
testing.templates = { };
|
||||
testing.add_template = function (name) {
|
||||
var xhr = QWeb2.Engine.prototype.get_xhr();
|
||||
xhr.open('GET', name, false);
|
||||
xhr.send(null);
|
||||
(testing.templates[testing.current_module] =
|
||||
testing.templates[testing.current_module] || [])
|
||||
.push(xhr.responseXML);
|
||||
};
|
||||
/**
|
||||
* Function which does not do anything
|
||||
*/
|
||||
testing.noop = function () { };
|
||||
/**
|
||||
* Alter provided instance's ``session`` attribute to make response
|
||||
* mockable:
|
||||
*
|
||||
* * The ``responses`` parameter can be used to provide a map of (RPC)
|
||||
* paths (e.g. ``/web/view/load``) to a function returning a response
|
||||
* to the query.
|
||||
* * ``instance.session`` grows a ``responses`` attribute which is
|
||||
* a map of the same (and is in fact initialized to the ``responses``
|
||||
* parameter if one is provided)
|
||||
*
|
||||
* Note that RPC requests to un-mocked URLs will be rejected with an
|
||||
* error message: only explicitly specified urls will get a response.
|
||||
*
|
||||
* Mocked sessions will *never* perform an actual RPC connection.
|
||||
*
|
||||
* @param instance openerp instance being initialized
|
||||
* @param {Object} [responses]
|
||||
*/
|
||||
testing.mockifyRPC = function (instance, responses) {
|
||||
var session = instance.session;
|
||||
session.responses = responses || {};
|
||||
session.rpc_function = function (url, payload) {
|
||||
var fn, params;
|
||||
var needle = payload.params.model + ':' + payload.params.method;
|
||||
if (url.url === '/web/dataset/call_kw'
|
||||
&& needle in this.responses) {
|
||||
fn = this.responses[needle];
|
||||
params = [
|
||||
payload.params.args || [],
|
||||
payload.params.kwargs || {}
|
||||
];
|
||||
} else {
|
||||
fn = this.responses[url.url];
|
||||
params = [payload];
|
||||
}
|
||||
|
||||
if (!fn) {
|
||||
return $.Deferred().reject({}, 'failed',
|
||||
_.str.sprintf("Url %s not found in mock responses, with arguments %s",
|
||||
url.url, JSON.stringify(payload.params))
|
||||
).promise();
|
||||
}
|
||||
try {
|
||||
return $.when(fn.apply(null, params)).then(function (result) {
|
||||
// Wrap for RPC layer unwrapper thingy
|
||||
return {result: result};
|
||||
});
|
||||
} catch (e) {
|
||||
// not sure why this looks like that
|
||||
return $.Deferred().reject({}, 'failed', String(e));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var StackProto = {
|
||||
execute: function (fn) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
// Warning: here be dragons
|
||||
var i = 0, setups = this.setups, teardowns = this.teardowns;
|
||||
var d = $.Deferred();
|
||||
|
||||
var succeeded, failed;
|
||||
var success = function () {
|
||||
succeeded = _.toArray(arguments);
|
||||
return teardown();
|
||||
};
|
||||
var failure = function () {
|
||||
// save first failure
|
||||
if (!failed) {
|
||||
failed = _.toArray(arguments);
|
||||
}
|
||||
// chain onto next teardown
|
||||
return teardown();
|
||||
};
|
||||
|
||||
var setup = function () {
|
||||
// if setup to execute
|
||||
if (i < setups.length) {
|
||||
var f = setups[i] || testing.noop;
|
||||
$.when(f.apply(null, args)).then(function () {
|
||||
++i;
|
||||
setup();
|
||||
}, failure);
|
||||
} else {
|
||||
$.when(fn.apply(null, args)).then(success, failure);
|
||||
}
|
||||
};
|
||||
var teardown = function () {
|
||||
// if teardown to execute
|
||||
if (i > 0) {
|
||||
var f = teardowns[--i] || testing.noop;
|
||||
$.when(f.apply(null, args)).then(teardown, failure);
|
||||
} else {
|
||||
if (failed) {
|
||||
d.reject.apply(d, failed);
|
||||
} else if (succeeded) {
|
||||
d.resolve.apply(d, succeeded);
|
||||
} else {
|
||||
throw new Error("Didn't succeed or fail?");
|
||||
}
|
||||
}
|
||||
};
|
||||
setup();
|
||||
|
||||
return d;
|
||||
},
|
||||
push: function (setup, teardown) {
|
||||
return _.extend(Object.create(StackProto), {
|
||||
setups: this.setups.concat([setup]),
|
||||
teardowns: this.teardowns.concat([teardown])
|
||||
});
|
||||
},
|
||||
unshift: function (setup, teardown) {
|
||||
return _.extend(Object.create(StackProto), {
|
||||
setups: [setup].concat(this.setups),
|
||||
teardowns: [teardown].concat(this.teardowns)
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {Function} [setup]
|
||||
* @param {Function} [teardown]
|
||||
* @return {*}
|
||||
*/
|
||||
testing.Stack = function (setup, teardown) {
|
||||
return _.extend(Object.create(StackProto), {
|
||||
setups: setup ? [setup] : [],
|
||||
teardowns: teardown ? [teardown] : []
|
||||
});
|
||||
};
|
||||
|
||||
var db = window['oe_db_info'];
|
||||
testing.section = function (name, options, body) {
|
||||
if (_.isFunction(options)) {
|
||||
body = options;
|
||||
options = {};
|
||||
}
|
||||
_.defaults(options, {
|
||||
setup: testing.noop,
|
||||
teardown: testing.noop
|
||||
});
|
||||
|
||||
QUnit.module(testing.current_module + '.' + name, {_oe: options});
|
||||
body(testing.case);
|
||||
};
|
||||
testing.case = function (name, options, callback) {
|
||||
if (_.isFunction(options)) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
var module = testing.current_module;
|
||||
var module_index = _.indexOf(testing.dependencies, module);
|
||||
var module_deps = testing.dependencies.slice(
|
||||
// If module not in deps (because only tests, no JS) -> indexOf
|
||||
// returns -1 -> index becomes 0 -> replace with ``undefined`` so
|
||||
// Array#slice returns a full copy
|
||||
0, module_index + 1 || undefined);
|
||||
|
||||
// Serialize options for this precise test case
|
||||
// WARNING: typo is from jquery, do not fix!
|
||||
var env = QUnit.config.currentModuleTestEnviroment;
|
||||
// section setup
|
||||
// case setup
|
||||
// test
|
||||
// case teardown
|
||||
// section teardown
|
||||
var case_stack = testing.Stack()
|
||||
.push(env._oe.setup, env._oe.teardown)
|
||||
.push(options.setup, options.teardown);
|
||||
var opts = _.defaults({}, options, env._oe);
|
||||
// FIXME: if this test is ignored, will still query
|
||||
if (opts.rpc === 'rpc' && !db) {
|
||||
QUnit.config.autostart = false;
|
||||
db = {
|
||||
source: null,
|
||||
supadmin: null,
|
||||
password: null
|
||||
};
|
||||
var $msg = $('<form style="margin: 0 1em 1em;">')
|
||||
.append('<h3>A test needs to clone a database</h3>')
|
||||
.append('<h4>Please provide the source clone information</h4>')
|
||||
.append(' Source DB: ').append('<input name="source">').append('<br>')
|
||||
.append(' DB Password: ').append('<input name="supadmin">').append('<br>')
|
||||
.append('Admin Password: ').append('<input name="password">').append('<br>')
|
||||
.append('<input type="submit" value="OK"/>')
|
||||
.submit(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
db.source = $msg.find('input[name=source]').val();
|
||||
db.supadmin = $msg.find('input[name=supadmin]').val();
|
||||
db.password = $msg.find('input[name=password]').val();
|
||||
QUnit.start();
|
||||
$.unblockUI();
|
||||
});
|
||||
$.blockUI({
|
||||
message: $msg,
|
||||
css: {
|
||||
fontFamily: 'monospace',
|
||||
textAlign: 'left',
|
||||
whiteSpace: 'pre-wrap',
|
||||
cursor: 'default'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.test(name, function () {
|
||||
var instance;
|
||||
if (!opts.dependencies) {
|
||||
instance = openerp.init(module_deps);
|
||||
} else {
|
||||
// empty-but-specified dependencies actually allow running
|
||||
// without loading any module into the instance
|
||||
|
||||
// TODO: clean up this mess
|
||||
var d = opts.dependencies.slice();
|
||||
// dependencies list should be in deps order, reverse to make
|
||||
// loading order from last
|
||||
d.reverse();
|
||||
var di = 0;
|
||||
while (di < d.length) {
|
||||
var m = /^web\.(\w+)$/.exec(d[di]);
|
||||
if (m) {
|
||||
d[di] = m[1];
|
||||
}
|
||||
d.splice.apply(d, [di+1, 0].concat(
|
||||
_(dependencies[d[di]]).reverse()));
|
||||
++di;
|
||||
}
|
||||
|
||||
instance = openerp.init("fuck your shit, don't load anything you cunt");
|
||||
_(d).chain()
|
||||
.reverse()
|
||||
.uniq()
|
||||
.each(function (module) {
|
||||
openerp.web[module](instance);
|
||||
});
|
||||
}
|
||||
if (_.isNumber(opts.asserts)) {
|
||||
expect(opts.asserts);
|
||||
}
|
||||
|
||||
if (opts.templates) {
|
||||
for(var i=0; i<module_deps.length; ++i) {
|
||||
var dep = module_deps[i];
|
||||
var templates = testing.templates[dep];
|
||||
if (_.isEmpty(templates)) { continue; }
|
||||
|
||||
for (var j=0; j < templates.length; ++j) {
|
||||
instance.web.qweb.add_template(templates[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var $fixture = $('#qunit-fixture');
|
||||
|
||||
var mock, async = false;
|
||||
switch (opts.rpc) {
|
||||
case 'mock':
|
||||
async = true;
|
||||
testing.mockifyRPC(instance);
|
||||
mock = function (spec, handler) {
|
||||
instance.session.responses[spec] = handler;
|
||||
};
|
||||
break;
|
||||
case 'rpc':
|
||||
async = true;
|
||||
(function () {
|
||||
// Bunch of random base36 characters
|
||||
var dbname = 'test_' + Math.random().toString(36).slice(2);
|
||||
// Add db setup/teardown at the start of the stack
|
||||
case_stack = case_stack.unshift(function (instance) {
|
||||
// FIXME hack: don't want the session to go through shitty loading process of everything
|
||||
instance.session.session_init = testing.noop;
|
||||
instance.session.load_modules = testing.noop;
|
||||
instance.session.session_bind();
|
||||
return instance.session.rpc('/web/database/duplicate', {
|
||||
fields: [
|
||||
{name: 'super_admin_pwd', value: db.supadmin},
|
||||
{name: 'db_original_name', value: db.source},
|
||||
{name: 'db_name', value: dbname}
|
||||
]
|
||||
}).then(function (result) {
|
||||
if (result.error) {
|
||||
return $.Deferred().reject(result.error).promise();
|
||||
}
|
||||
return instance.session.session_authenticate(
|
||||
dbname, 'admin', db.password, true);
|
||||
});
|
||||
}, function (instance) {
|
||||
return instance.session.rpc('/web/database/drop', {
|
||||
fields: [
|
||||
{name: 'drop_pwd', value: db.supadmin},
|
||||
{name: 'drop_db', value: dbname}
|
||||
]
|
||||
}).then(function (result) {
|
||||
if (result.error) {
|
||||
return $.Deferred().reject(result.error).promise();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
// Always execute tests asynchronously
|
||||
stop();
|
||||
var timeout;
|
||||
case_stack.execute(function () {
|
||||
var result = callback.apply(null, arguments);
|
||||
if (!(result && _.isFunction(result.then))) {
|
||||
if (async) {
|
||||
ok(false, "asynchronous test cases must return a promise");
|
||||
}
|
||||
} else {
|
||||
if (!_.isNumber(opts.asserts)) {
|
||||
ok(false, "asynchronous test cases must specify the "
|
||||
+ "number of assertions they expect");
|
||||
}
|
||||
}
|
||||
|
||||
return $.Deferred(function (d) {
|
||||
$.when(result).then(function () {
|
||||
d.resolve.apply(d, arguments)
|
||||
}, function () {
|
||||
d.reject.apply(d, arguments);
|
||||
});
|
||||
if (async || (result && result.then)) {
|
||||
// async test can be either implicit async (rpc) or
|
||||
// promise-returning
|
||||
timeout = setTimeout(function () {
|
||||
QUnit.config.semaphore = 1;
|
||||
d.reject({message: "Test timed out"});
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}, instance, $fixture, mock).always(function () {
|
||||
if (timeout) { clearTimeout(timeout); }
|
||||
start();
|
||||
}).fail(function (error) {
|
||||
if (options.fail_on_rejection === false) {
|
||||
return;
|
||||
}
|
||||
var message;
|
||||
if (typeof error !== 'object'
|
||||
|| typeof error.message !== 'string') {
|
||||
message = JSON.stringify([].slice.apply(arguments));
|
||||
} else {
|
||||
message = error.message;
|
||||
if (error.data && error.data.debug) {
|
||||
message += '\n\n' + error.data.debug;
|
||||
}
|
||||
}
|
||||
|
||||
ok(false, message);
|
||||
});
|
||||
});
|
||||
};
|
||||
})(openerp.testing);
|
||||
@@ -173,10 +173,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||
} else {
|
||||
this.$el.find('.oe_form_buttons').replaceWith(this.$buttons);
|
||||
}
|
||||
this.$buttons.on('click', '.oe_form_button_create', this.on_button_create);
|
||||
this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit);
|
||||
this.$buttons.on('click', '.oe_form_button_save', this.on_button_save);
|
||||
this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel);
|
||||
this.$buttons.on('click', '.oe_form_button_create',
|
||||
this.guard_active(this.on_button_create));
|
||||
this.$buttons.on('click', '.oe_form_button_edit',
|
||||
this.guard_active(this.on_button_edit));
|
||||
this.$buttons.on('click', '.oe_form_button_save',
|
||||
this.guard_active(this.on_button_save));
|
||||
this.$buttons.on('click', '.oe_form_button_cancel',
|
||||
this.guard_active(this.on_button_cancel));
|
||||
if (this.options.footer_to_buttons) {
|
||||
this.$el.find('footer').appendTo(this.$buttons);
|
||||
}
|
||||
@@ -618,6 +622,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||
return self._process_save(save_obj).then(function() {
|
||||
save_obj.ret = _.toArray(arguments);
|
||||
return iterate();
|
||||
}, function() {
|
||||
save_obj.error = true;
|
||||
});
|
||||
}
|
||||
return $.when();
|
||||
@@ -801,6 +807,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||
var save_obj = {prepend_on_create: prepend_on_create, ret: null};
|
||||
this.save_list.push(save_obj);
|
||||
return this._process_operations().then(function() {
|
||||
if (save_obj.error)
|
||||
return $.Deferred().reject();
|
||||
return $.when.apply($, save_obj.ret);
|
||||
});
|
||||
},
|
||||
@@ -1187,14 +1195,36 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
|
||||
});
|
||||
}
|
||||
},
|
||||
view_arch_to_dom_node: function(arch) {
|
||||
// Historic mess for views arch
|
||||
//
|
||||
// server:
|
||||
// -> got xml as string
|
||||
// -> parse to xml and manipulate domains and contexts
|
||||
// -> convert to json
|
||||
// client:
|
||||
// -> got view as json
|
||||
// -> convert back to xml as string
|
||||
// -> parse it as xml doc (manipulate button@type for IE)
|
||||
// -> convert back to string
|
||||
// -> parse it as dom element with jquery
|
||||
// -> for each widget, convert node to json
|
||||
//
|
||||
// Wow !!!
|
||||
var xml = instance.web.json_node_to_xml(arch);
|
||||
|
||||
var doc = $.parseXML('<div class="oe_form">' + xml + '</div>');
|
||||
$('button', doc).each(function() {
|
||||
$(this).attr('data-button-type', $(this).attr('type'));
|
||||
});
|
||||
xml = instance.web.xml_to_str(doc);
|
||||
return $(xml);
|
||||
},
|
||||
render_to: function($target) {
|
||||
var self = this;
|
||||
this.$target = $target;
|
||||
|
||||
// TODO: I know this will save the world and all the kitten for a moment,
|
||||
// but one day, we will have to get rid of xml2json
|
||||
var xml = instance.web.json_node_to_xml(this.fvg.arch);
|
||||
this.$form = $('<div class="oe_form">' + xml + '</div>');
|
||||
this.$form = this.view_arch_to_dom_node(this.fvg.arch);
|
||||
|
||||
this.process_version();
|
||||
|
||||
@@ -1863,6 +1893,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
|
||||
instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
|
||||
template: 'WidgetButton',
|
||||
init: function(field_manager, node) {
|
||||
node.attrs.type = node.attrs['data-button-type'];
|
||||
this._super(field_manager, node);
|
||||
this.force_disabled = false;
|
||||
this.string = (this.node.attrs.string || '').replace(/_/g, '');
|
||||
@@ -2152,17 +2183,10 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
|
||||
},
|
||||
|
||||
set_dimensions: function (height, width) {
|
||||
// remove width css property
|
||||
this.$el.css('width', '');
|
||||
// extract style (without width)
|
||||
var old_style = this.$el.attr('style');
|
||||
// jQuery doesn't understand/use !important
|
||||
var style = 'width:' + width + 'px !important;';
|
||||
if (old_style) {
|
||||
style += old_style
|
||||
}
|
||||
this.$el.attr('style', style);
|
||||
this.$el.css('minHeight', height);
|
||||
this.$el.css({
|
||||
width: width,
|
||||
minHeight: height
|
||||
});
|
||||
},
|
||||
commit_value: function() {
|
||||
return $.when();
|
||||
@@ -2386,7 +2410,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
|
||||
self.$input.focus();
|
||||
return;
|
||||
}
|
||||
self.picker('setDate', self.value ? instance.web.auto_str_to_date(self.value) : new Date());
|
||||
self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date());
|
||||
self.$input_picker.show();
|
||||
self.picker('show');
|
||||
self.$input_picker.hide();
|
||||
@@ -2639,22 +2663,18 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
The progressbar field expect a float from 0 to 100.
|
||||
*/
|
||||
instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
|
||||
template: 'FieldProgressBar',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
render_value: function() {
|
||||
this.$el.progressbar({
|
||||
value: this.get('value'),
|
||||
value: this.get('value') || 0,
|
||||
disabled: this.get("effective_readonly")
|
||||
});
|
||||
},
|
||||
render_value: function() {
|
||||
var show_value = Number(this.get('value'));
|
||||
if (isNaN(show_value)) {
|
||||
show_value = 0;
|
||||
}
|
||||
var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0');
|
||||
this.$el.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
|
||||
var formatted_value = instance.web.format_value(this.get('value') || 0, { type : 'float' });
|
||||
this.$('span').html(formatted_value + '%');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3106,7 +3126,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
||||
minLength: 0,
|
||||
delay: 0
|
||||
});
|
||||
this.$input.autocomplete("widget").addClass("openerp");
|
||||
this.$input.autocomplete("widget").openerpClass();
|
||||
// used to correct a bug when selecting an element by pushing 'enter' in an editable list
|
||||
this.$input.keyup(function(e) {
|
||||
if (e.which === 13) { // ENTER
|
||||
@@ -3817,26 +3837,13 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
do_delete: function (ids) {
|
||||
var self = this;
|
||||
var next = $.when();
|
||||
var _super = this._super;
|
||||
// handle deletion of an item which does not exist
|
||||
// TODO: better handle that in the editable list?
|
||||
var false_id_index = _(ids).indexOf(false);
|
||||
if (false_id_index !== -1) {
|
||||
ids.splice(false_id_index, 1);
|
||||
next = this.cancel_edition(true);
|
||||
var confirm = window.confirm;
|
||||
window.confirm = function () { return true; };
|
||||
try {
|
||||
return this._super(ids);
|
||||
} finally {
|
||||
window.confirm = confirm;
|
||||
}
|
||||
return next.then(function () {
|
||||
// wheeee
|
||||
var confirm = window.confirm;
|
||||
window.confirm = function () { return true; };
|
||||
try {
|
||||
return _super.call(self, ids);
|
||||
} finally {
|
||||
window.confirm = confirm;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({
|
||||
@@ -3999,13 +4006,18 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
|
||||
self._drop_shown = true;
|
||||
});
|
||||
self.tags = self.$text.textext()[0].tags();
|
||||
self.$text.focusout(function() {
|
||||
self.$text.trigger("setInputData", "");
|
||||
}).keydown(function(e) {
|
||||
if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
|
||||
self.$text.textext()[0].autocomplete().selectFromDropdown();
|
||||
}
|
||||
});
|
||||
self.$text
|
||||
.focusin(function () {
|
||||
self.trigger('focused');
|
||||
})
|
||||
.focusout(function() {
|
||||
self.$text.trigger("setInputData", "");
|
||||
self.trigger('blurred');
|
||||
}).keydown(function(e) {
|
||||
if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
|
||||
self.$text.textext()[0].autocomplete().selectFromDropdown();
|
||||
}
|
||||
});
|
||||
},
|
||||
set_value: function(value_) {
|
||||
value_ = value_ || [];
|
||||
@@ -4794,15 +4806,6 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
|
||||
this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as);
|
||||
this.$el.find('.oe_form_binary_file_clear').click(this.on_clear);
|
||||
},
|
||||
human_filesize : function(size) {
|
||||
var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
var i = 0;
|
||||
while (size >= 1024) {
|
||||
size /= 1024;
|
||||
++i;
|
||||
}
|
||||
return size.toFixed(2) + ' ' + units[i];
|
||||
},
|
||||
on_file_change: function(e) {
|
||||
var self = this;
|
||||
var file_node = e.target;
|
||||
@@ -4850,6 +4853,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
|
||||
link.href = "data:application/octet-stream;base64," + value;
|
||||
} else {
|
||||
instance.web.blockUI();
|
||||
var c = instance.webclient.crashmanager;
|
||||
this.session.get_file({
|
||||
url: '/web/binary/saveas_ajax',
|
||||
data: {data: JSON.stringify({
|
||||
@@ -4860,7 +4864,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
|
||||
context: this.view.dataset.get_context()
|
||||
})},
|
||||
complete: instance.web.unblockUI,
|
||||
error: instance.webclient.crashmanager.on_rpc_error
|
||||
error: c.rpc_error.bind(c)
|
||||
});
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
@@ -4919,7 +4923,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
|
||||
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
|
||||
this.binary_value = true;
|
||||
this.internal_set_value(file_base64);
|
||||
var show_value = name + " (" + this.human_filesize(size) + ")";
|
||||
var show_value = name + " (" + instance.web.human_size(size) + ")";
|
||||
this.$el.find('input').eq(0).val(show_value);
|
||||
this.set_filename(name);
|
||||
},
|
||||
@@ -4939,12 +4943,16 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
|
||||
if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) {
|
||||
url = 'data:image/png;base64,' + this.get('value');
|
||||
} else if (this.get('value')) {
|
||||
var id = escape(JSON.stringify(this.view.datarecord.id || null));
|
||||
var id = JSON.stringify(this.view.datarecord.id || null);
|
||||
var field = this.name;
|
||||
if (this.options.preview_image)
|
||||
field = this.options.preview_image;
|
||||
url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
|
||||
this.view.dataset.model +'&id=' + id + '&field=' + field + '&t=' + (new Date().getTime());
|
||||
url = this.session.url('/web/binary/image', {
|
||||
model: this.view.dataset.model,
|
||||
id: id,
|
||||
field: field,
|
||||
t: (new Date().getTime()),
|
||||
});
|
||||
} else {
|
||||
url = this.placeholder;
|
||||
}
|
||||
@@ -4983,14 +4991,14 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
|
||||
* Options on attribute ; "blockui" {Boolean} block the UI or not
|
||||
* during the file is uploading
|
||||
*/
|
||||
instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
|
||||
instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
|
||||
template: "FieldBinaryFileUploader",
|
||||
init: function(field_manager, node) {
|
||||
this._super(field_manager, node);
|
||||
this.field_manager = field_manager;
|
||||
this.node = node;
|
||||
if(this.field.type != "one2many" || this.field.relation != 'ir.attachment') {
|
||||
throw "The type of the field '"+this.field.string+"' must be a one2many field with a relation to 'ir.attachment' model.";
|
||||
if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') {
|
||||
throw "The type of the field '"+this.field.string+"' must be a many2many field with a relation to 'ir.attachment' model.";
|
||||
}
|
||||
this.ds_file = new instance.web.DataSetSearch(this, 'ir.attachment');
|
||||
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
|
||||
@@ -5000,21 +5008,85 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
|
||||
this._super(this);
|
||||
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
|
||||
},
|
||||
set_value: function(value_) {
|
||||
var value_ = value_ || [];
|
||||
var self = this;
|
||||
var ids = [];
|
||||
_.each(value_, function(command) {
|
||||
if (isNaN(command) && command.id == undefined) {
|
||||
switch (command[0]) {
|
||||
case commands.CREATE:
|
||||
ids = ids.concat(command[2]);
|
||||
return;
|
||||
case commands.REPLACE_WITH:
|
||||
ids = ids.concat(command[2]);
|
||||
return;
|
||||
case commands.UPDATE:
|
||||
ids = ids.concat(command[2]);
|
||||
return;
|
||||
case commands.LINK_TO:
|
||||
ids = ids.concat(command[1]);
|
||||
return;
|
||||
case commands.DELETE:
|
||||
ids = _.filter(ids, function (id) { return id != command[1];});
|
||||
return;
|
||||
case commands.DELETE_ALL:
|
||||
ids = [];
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ids.push(command);
|
||||
}
|
||||
});
|
||||
this._super( ids );
|
||||
},
|
||||
get_value: function() {
|
||||
return _.map(this.get('value'), function (value) { return commands.link_to( value.id ); });
|
||||
},
|
||||
get_file_url: function (attachment) {
|
||||
return instance.origin + '/web/binary/saveas?session_id=' + this.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id'];
|
||||
return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']});
|
||||
},
|
||||
read_name_values : function () {
|
||||
var self = this;
|
||||
// select the list of id for a get_name
|
||||
var values = [];
|
||||
_.each(this.get('value'), function (val) {
|
||||
if (typeof val != 'object') {
|
||||
values.push(val);
|
||||
}
|
||||
});
|
||||
// send request for get_name
|
||||
if (values.length) {
|
||||
return this.ds_file.call('read', [values, ['id', 'name', 'datas_fname']]).done(function (datas) {
|
||||
_.each(datas, function (data) {
|
||||
data.no_unlink = true;
|
||||
data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id});
|
||||
|
||||
_.each(self.get('value'), function (val, key) {
|
||||
if(val == data.id) {
|
||||
self.get('value')[key] = data;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return $.when(this.get('value'));
|
||||
}
|
||||
},
|
||||
render_value: function () {
|
||||
var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': this}));
|
||||
render.on('click', '.oe_delete', _.bind(this.on_file_delete, this));
|
||||
this.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
|
||||
var self = this;
|
||||
this.read_name_values().then(function (datas) {
|
||||
|
||||
// reinit input type file
|
||||
var $input = this.$('input.oe_form_binary_file');
|
||||
$input.after($input.clone(true)).remove();
|
||||
this.$(".oe_fileupload").show();
|
||||
var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self}));
|
||||
render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
|
||||
self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
|
||||
|
||||
// reinit input type file
|
||||
var $input = self.$('input.oe_form_binary_file');
|
||||
$input.after($input.clone(true)).remove();
|
||||
self.$(".oe_fileupload").show();
|
||||
|
||||
});
|
||||
},
|
||||
on_file_change: function (event) {
|
||||
event.stopPropagation();
|
||||
@@ -5030,7 +5102,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
|
||||
}
|
||||
|
||||
// block UI or not
|
||||
if(this.node.attrs.blockui) {
|
||||
if(this.node.attrs.blockui>0) {
|
||||
instance.web.blockUI();
|
||||
}
|
||||
|
||||
@@ -5064,7 +5136,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
|
||||
},
|
||||
on_file_loaded: function (event, result) {
|
||||
// unblock UI
|
||||
if(this.node.attrs.blockui) {
|
||||
if(this.node.attrs.blockui>0) {
|
||||
instance.web.unblockUI();
|
||||
}
|
||||
|
||||
@@ -5094,7 +5166,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
|
||||
if(file_id != this.get('value')[i].id){
|
||||
files.push(this.get('value')[i]);
|
||||
}
|
||||
else {
|
||||
else if(!this.get('value')[i].no_unlink) {
|
||||
this.ds_file.unlink([file_id]);
|
||||
}
|
||||
}
|
||||
@@ -5256,7 +5328,7 @@ instance.web.form.widgets = new instance.web.Registry({
|
||||
'progressbar': 'instance.web.form.FieldProgressBar',
|
||||
'image': 'instance.web.form.FieldBinaryImage',
|
||||
'binary': 'instance.web.form.FieldBinaryFile',
|
||||
'one2many_binary': 'instance.web.form.FieldOne2ManyBinaryMultiFiles',
|
||||
'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles',
|
||||
'statusbar': 'instance.web.form.FieldStatus',
|
||||
'monetary': 'instance.web.form.FieldMonetary',
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openerp.web.list = function (instance) {
|
||||
var _t = instance.web._t,
|
||||
_lt = instance.web._lt;
|
||||
_lt = instance.web._lt;
|
||||
var QWeb = instance.web.qweb;
|
||||
instance.web.views.add('list', 'instance.web.ListView');
|
||||
instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ {
|
||||
@@ -503,12 +503,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
|
||||
return this.reload_content();
|
||||
},
|
||||
reload_record: function (record) {
|
||||
var self = this;
|
||||
return this.dataset.read_ids(
|
||||
[record.get('id')],
|
||||
_.pluck(_(this.columns).filter(function (r) {
|
||||
return r.tag === 'field';
|
||||
}), 'name')
|
||||
).done(function (records) {
|
||||
if (!records[0]) {
|
||||
self.records.remove(record);
|
||||
return;
|
||||
}
|
||||
_(records[0]).each(function (value, key) {
|
||||
record.set(key, value, {silent: true});
|
||||
});
|
||||
@@ -1557,9 +1562,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
||||
}
|
||||
});
|
||||
|
||||
var DataGroup = instance.web.CallbackEnabled.extend({
|
||||
var DataGroup = instance.web.Class.extend({
|
||||
init: function(parent, model, domain, context, group_by, level) {
|
||||
this._super(parent, null);
|
||||
this.model = new instance.web.Model(model, context, domain);
|
||||
this.group_by = group_by;
|
||||
this.context = context;
|
||||
@@ -2152,7 +2156,7 @@ instance.web.list.Boolean = instance.web.list.Column.extend({
|
||||
* @private
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
|
||||
return _.str.sprintf('<input type="checkbox" %s readonly="readonly"/>',
|
||||
row_data[this.id].value ? 'checked="checked"' : '');
|
||||
}
|
||||
});
|
||||
@@ -2164,20 +2168,24 @@ instance.web.list.Binary = instance.web.list.Column.extend({
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
var text = _t("Download");
|
||||
var download_url = _.str.sprintf(
|
||||
'/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
|
||||
instance.session.session_id, options.model, this.id, options.id);
|
||||
if (this.filename) {
|
||||
download_url += '&filename_field=' + this.filename;
|
||||
if (row_data[this.filename]) {
|
||||
text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
|
||||
row_data[this.filename].value, {type: 'char'}));
|
||||
var value = row_data[this.id].value;
|
||||
var download_url;
|
||||
if (value && value.substr(0, 10).indexOf(' ') == -1) {
|
||||
download_url = "data:application/octet-stream;base64," + value;
|
||||
} else {
|
||||
download_url = this.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
|
||||
if (this.filename) {
|
||||
download_url += '&filename_field=' + this.filename;
|
||||
}
|
||||
}
|
||||
return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
|
||||
if (this.filename && row_data[this.filename]) {
|
||||
text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
|
||||
row_data[this.filename].value, {type: 'char'}));
|
||||
}
|
||||
return _.template('<a href="<%-href%>"><%-text%></a> (<%-size%>)', {
|
||||
text: text,
|
||||
href: download_url,
|
||||
size: row_data[this.id].value
|
||||
size: instance.web.binary_to_binsize(value),
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -2217,7 +2225,7 @@ instance.web.list.Many2OneButton = instance.web.list.Column.extend({
|
||||
});
|
||||
instance.web.list.Many2Many = instance.web.list.Column.extend({
|
||||
_format: function (row_data, options) {
|
||||
if (row_data[this.id].value) {
|
||||
if (!_.isEmpty(row_data[this.id].value)) {
|
||||
// If value, use __display version for printing
|
||||
row_data[this.id] = row_data[this.id + '__display'];
|
||||
}
|
||||
|
||||
@@ -77,6 +77,15 @@ openerp.web.list_editable = function (instance) {
|
||||
do_edit: function (index, id, dataset) {
|
||||
_.extend(this.dataset, dataset);
|
||||
},
|
||||
do_delete: function (ids) {
|
||||
var _super = this._super.bind(this);
|
||||
var next = this.editor.is_editing()
|
||||
? this.cancel_edition(true)
|
||||
: $.when();
|
||||
return next.then(function () {
|
||||
return _super(ids);
|
||||
});
|
||||
},
|
||||
editable: function () {
|
||||
return this.fields_view.arch.attrs.editable
|
||||
|| this._context_editable
|
||||
@@ -117,13 +126,6 @@ openerp.web.list_editable = function (instance) {
|
||||
e.preventDefault();
|
||||
self.cancel_edition();
|
||||
});
|
||||
this.$el
|
||||
.off('click', 'tbody td:not(.oe_list_field_cell)')
|
||||
.on('click', 'tbody td:not(.oe_list_field_cell)', function () {
|
||||
if (!self.editor.is_editing()) {
|
||||
self.start_edition();
|
||||
}
|
||||
});
|
||||
this.editor.destroy();
|
||||
// Editor is not restartable due to formview not being
|
||||
// restartable
|
||||
@@ -164,13 +166,23 @@ openerp.web.list_editable = function (instance) {
|
||||
* @returns {$.Deferred}
|
||||
*/
|
||||
ensure_saved: function () {
|
||||
var self = this;
|
||||
return this.saving_mutex.exec(function() {
|
||||
if (!self.editor.is_editing()) {
|
||||
return $.when();
|
||||
}
|
||||
return self.save_edition();
|
||||
});
|
||||
return this.save_edition();
|
||||
},
|
||||
/**
|
||||
* Builds a record with the provided id (``false`` for a creation),
|
||||
* setting all columns with ``false`` value so code which relies on
|
||||
* having an actual value behaves correctly
|
||||
*
|
||||
* @param {*} id
|
||||
* @return {instance.web.list.Record}
|
||||
*/
|
||||
make_empty_record: function (id) {
|
||||
var attrs = {id: id};
|
||||
_(this.columns).chain()
|
||||
.filter(function (x) { return x.tag === 'field'})
|
||||
.pluck('name')
|
||||
.each(function (field) { attrs[field] = false; });
|
||||
return new instance.web.list.Record(attrs);
|
||||
},
|
||||
/**
|
||||
* Set up the edition of a record of the list view "inline"
|
||||
@@ -186,12 +198,7 @@ openerp.web.list_editable = function (instance) {
|
||||
if (record) {
|
||||
item = record.attributes;
|
||||
} else {
|
||||
var attrs = {id: false};
|
||||
_(this.columns).chain()
|
||||
.filter(function (x) { return x.tag === 'field'})
|
||||
.pluck('name')
|
||||
.each(function (field) { attrs[field] = false; });
|
||||
record = new instance.web.list.Record(attrs);
|
||||
record = this.make_empty_record(false);
|
||||
this.records.add(record, {
|
||||
at: this.prepends_on_create() ? 0 : null});
|
||||
}
|
||||
@@ -269,29 +276,34 @@ openerp.web.list_editable = function (instance) {
|
||||
*/
|
||||
save_edition: function () {
|
||||
var self = this;
|
||||
return this.with_event('save', {
|
||||
editor: this.editor,
|
||||
form: this.editor.form,
|
||||
cancel: false
|
||||
}, function () {
|
||||
return this.editor.save().then(function (attrs) {
|
||||
var created = false;
|
||||
var record = self.records.get(attrs.id);
|
||||
if (!record) {
|
||||
// new record
|
||||
created = true;
|
||||
record = self.records.find(function (r) {
|
||||
return !r.get('id');
|
||||
}).set('id', attrs.id);
|
||||
}
|
||||
// onwrite callback could be altering & reloading the
|
||||
// record which has *just* been saved, so first perform all
|
||||
// onwrites then do a final reload of the record
|
||||
return self.handle_onwrite(record)
|
||||
.then(function () {
|
||||
return self.reload_record(record); })
|
||||
.then(function () {
|
||||
return { created: created, record: record }; });
|
||||
return self.saving_mutex.exec(function() {
|
||||
if (!self.editor.is_editing()) {
|
||||
return $.when();
|
||||
}
|
||||
return self.with_event('save', {
|
||||
editor: self.editor,
|
||||
form: self.editor.form,
|
||||
cancel: false
|
||||
}, function () {
|
||||
return self.editor.save().then(function (attrs) {
|
||||
var created = false;
|
||||
var record = self.records.get(attrs.id);
|
||||
if (!record) {
|
||||
// new record
|
||||
created = true;
|
||||
record = self.records.find(function (r) {
|
||||
return !r.get('id');
|
||||
}).set('id', attrs.id);
|
||||
}
|
||||
// onwrite callback could be altering & reloading the
|
||||
// record which has *just* been saved, so first perform all
|
||||
// onwrites then do a final reload of the record
|
||||
return self.handle_onwrite(record)
|
||||
.then(function () {
|
||||
return self.reload_record(record); })
|
||||
.then(function () {
|
||||
return { created: created, record: record }; });
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -391,7 +403,7 @@ openerp.web.list_editable = function (instance) {
|
||||
if (!record) {
|
||||
// insert after the source record
|
||||
var index = this.records.indexOf(source_record) + 1;
|
||||
record = new instance.web.list.Record({id: id});
|
||||
record = this.make_empty_record(id);
|
||||
this.records.add(record, {at: index});
|
||||
this.dataset.ids.splice(index, 0, id);
|
||||
}
|
||||
|
||||
@@ -213,8 +213,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||
if (run_action) {
|
||||
this.null_action();
|
||||
action_loaded = this.do_action(state.action);
|
||||
instance.webclient.menu.has_been_loaded.done(function() {
|
||||
instance.webclient.menu.open_action(state.action);
|
||||
$.when(action_loaded || null).done(function() {
|
||||
instance.webclient.menu.has_been_loaded.done(function() {
|
||||
if (self.inner_action && self.inner_action.id) {
|
||||
instance.webclient.menu.open_action(self.inner_action.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -306,15 +310,15 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||
}
|
||||
var widget = executor.widget();
|
||||
if (executor.action.target === 'new') {
|
||||
if (this.dialog_widget && ! this.dialog_widget.isDestroyed())
|
||||
if (this.dialog_widget && !this.dialog_widget.isDestroyed()) {
|
||||
this.dialog_widget.destroy();
|
||||
if (this.dialog === null || this.dialog.isDestroyed()) {
|
||||
this.dialog = new instance.web.Dialog(this, {
|
||||
dialogClass: executor.klass,
|
||||
});
|
||||
this.dialog.on("closing", null, options.on_close);
|
||||
this.dialog.init_dialog();
|
||||
}
|
||||
this.dialog_stop();
|
||||
this.dialog = new instance.web.Dialog(this, {
|
||||
dialogClass: executor.klass,
|
||||
});
|
||||
this.dialog.on("closing", null, options.on_close);
|
||||
this.dialog.init_dialog();
|
||||
this.dialog.dialog_title = executor.action.name;
|
||||
if (widget instanceof instance.web.ViewManager) {
|
||||
_.extend(widget.flags, {
|
||||
@@ -397,6 +401,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||
}).done(function(res) {
|
||||
action = _.clone(action);
|
||||
action.context = res.context;
|
||||
var c = instance.webclient.crashmanager;
|
||||
self.session.get_file({
|
||||
url: '/web/report',
|
||||
data: {action: JSON.stringify(action)},
|
||||
@@ -407,7 +412,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||
}
|
||||
self.dialog_stop();
|
||||
},
|
||||
error: instance.webclient.crashmanager.on_rpc_error
|
||||
error: c.rpc_error.bind(c)
|
||||
})
|
||||
});
|
||||
},
|
||||
@@ -504,7 +509,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
||||
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
|
||||
.parent().addClass('active');
|
||||
|
||||
r = $.when(view_promise).done(function () {
|
||||
return $.when(view_promise).done(function () {
|
||||
_.each(_.keys(self.views), function(view_name) {
|
||||
var controller = self.views[view_name].controller;
|
||||
if (controller) {
|
||||
@@ -520,7 +525,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
||||
});
|
||||
self.trigger('switch_mode', view_type, no_store, view_options);
|
||||
});
|
||||
return r;
|
||||
},
|
||||
do_create_view: function(view_type) {
|
||||
// Lazy loading of views
|
||||
@@ -785,8 +789,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
|
||||
name: "JS Tests",
|
||||
target: 'new',
|
||||
type : 'ir.actions.act_url',
|
||||
url: '/web/static/test/test.html'
|
||||
})
|
||||
url: '/web/tests?mod=*'
|
||||
});
|
||||
break;
|
||||
case 'perm_read':
|
||||
var ids = current_view.get_selected_ids();
|
||||
@@ -1098,11 +1102,11 @@ instance.web.Sidebar = instance.web.Widget.extend({
|
||||
on_attachments_loaded: function(attachments) {
|
||||
var self = this;
|
||||
var items = [];
|
||||
var prefix = this.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id=';
|
||||
var prefix = this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'name'});
|
||||
_.each(attachments,function(a) {
|
||||
a.label = a.name;
|
||||
if(a.type === "binary") {
|
||||
a.url = prefix + a.id + '&t=' + (new Date().getTime());
|
||||
a.url = prefix + '&id=' + a.id + '&t=' + (new Date().getTime());
|
||||
}
|
||||
});
|
||||
self.items['files'] = attachments;
|
||||
@@ -1235,11 +1239,12 @@ instance.web.View = instance.web.Widget.extend({
|
||||
});
|
||||
}, null);
|
||||
} else {
|
||||
self.do_action({"type":"ir.actions.act_window_close"});
|
||||
return result_handler();
|
||||
}
|
||||
};
|
||||
|
||||
if (action_data.special) {
|
||||
if (action_data.special === 'cancel') {
|
||||
return handler({"type":"ir.actions.act_window_close"});
|
||||
} else if (action_data.type=="object") {
|
||||
var args = [[record_id]], additional_args = [];
|
||||
@@ -1277,6 +1282,27 @@ instance.web.View = instance.web.Widget.extend({
|
||||
do_hide: function () {
|
||||
this.$el.hide();
|
||||
},
|
||||
is_active: function () {
|
||||
var manager = this.getParent();
|
||||
return !manager || !manager.active_view
|
||||
|| manager.views[manager.active_view].controller === this;
|
||||
}, /**
|
||||
* Wraps fn to only call it if the current view is the active one. If the
|
||||
* current view is not active, doesn't call fn.
|
||||
*
|
||||
* fn can not return anything, as a non-call to fn can't return anything
|
||||
* either
|
||||
*
|
||||
* @param {Function} fn function to wrap in the active guard
|
||||
*/
|
||||
guard_active: function (fn) {
|
||||
var self = this;
|
||||
return function () {
|
||||
if (self.is_active()) {
|
||||
fn.apply(self, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
do_push_state: function(state) {
|
||||
if (this.getParent() && this.getParent().do_push_state) {
|
||||
this.getParent().do_push_state(state);
|
||||
@@ -1384,14 +1410,16 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) {
|
||||
} else {
|
||||
return r + '/>';
|
||||
}
|
||||
}
|
||||
};
|
||||
instance.web.xml_to_str = function(node) {
|
||||
if (window.ActiveXObject) {
|
||||
if (window.XMLSerializer) {
|
||||
return (new XMLSerializer()).serializeToString(node);
|
||||
} else if (window.ActiveXObject) {
|
||||
return node.xml;
|
||||
} else {
|
||||
return (new XMLSerializer()).serializeToString(node);
|
||||
throw new Error("Could not serialize XML");
|
||||
}
|
||||
}
|
||||
};
|
||||
instance.web.str_to_xml = function(s) {
|
||||
if (window.DOMParser) {
|
||||
var dp = new DOMParser();
|
||||
|
||||
@@ -152,6 +152,32 @@
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<form id="db_duplicate" name="duplicate_db_form" style="display: none;">
|
||||
<div class="oe_view_manager oe_view_manager_current">
|
||||
<div class="oe_view_manager_header" style="padding: 8px;">
|
||||
<div class="oe_header_row">
|
||||
<h2 class="oe_view_title">
|
||||
<span class="oe_view_title_text oe_breadcrumb_title">Duplicate Database</span>
|
||||
</h2>
|
||||
<button type="submit" class="oe_button oe_highlight db_duplicate">Duplicate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table align="center" class="db_option_table">
|
||||
<tr>
|
||||
<td><label for="super_admin_pwd">Master password:</label></td>
|
||||
<td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="db_original_name">Original database name:</label></td>
|
||||
<td><input type="text" name="db_original_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" autofocus="true"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="db_name">New database name:</label></td>
|
||||
<td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<form id="db_drop" name="drop_db_form" style="display: none; ">
|
||||
<div class="oe_view_manager oe_view_manager_current">
|
||||
<div class="oe_view_manager_header" style="padding: 8px;">
|
||||
@@ -279,6 +305,7 @@
|
||||
<div class="oe_secondary_menu_section">Database Management</div>
|
||||
<ul class="oe_secondary_submenu">
|
||||
<li><a href="#db_create">Create</a></li>
|
||||
<li><a href="#db_duplicate">Duplicate</a></li>
|
||||
<li><a href="#db_drop">Drop</a></li>
|
||||
<li><a href="#db_backup">Backup</a></li>
|
||||
<li><a href="#db_restore">Restore</a></li>
|
||||
@@ -1182,26 +1209,28 @@
|
||||
</t>
|
||||
<t t-name="FieldBinaryFileUploader.files">
|
||||
<div class="oe_attachments">
|
||||
<t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
|
||||
<div class="oe_attachment">
|
||||
<span t-if="(file.upload or file.percent_loaded<100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
|
||||
<span class="oe_fileuploader_in_process">...Upload in progress...</span>
|
||||
<t t-raw="file.name || file.filename"/>
|
||||
</span>
|
||||
<a t-if="(!file.upload or file.percent_loaded>=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
|
||||
<t t-raw="file.name || file.filename"/>
|
||||
</a>
|
||||
<t t-if="(!file.upload or file.percent_loaded>=100)">
|
||||
<a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
|
||||
<div>
|
||||
<a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
|
||||
<t t-raw="file.name || file.filename"/>
|
||||
</a>
|
||||
</div>
|
||||
<t t-if="widget.get('value')">
|
||||
<t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
|
||||
<div class="oe_attachment">
|
||||
<span t-if="(file.upload or file.percent_loaded<100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
|
||||
<span class="oe_fileuploader_in_process">...Upload in progress...</span>
|
||||
<t t-raw="file.name || file.filename"/>
|
||||
</span>
|
||||
<a t-if="(!file.upload or file.percent_loaded>=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
|
||||
<t t-raw="file.name || file.filename"/>
|
||||
</a>
|
||||
<t t-if="(!file.upload or file.percent_loaded>=100)">
|
||||
<a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
|
||||
<div>
|
||||
<a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
|
||||
<t t-raw="file.name || file.filename"/>
|
||||
</a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
@@ -1512,17 +1541,19 @@
|
||||
<h3><span class="oe_i">M</span> Custom Filters</h3>
|
||||
<ul class="oe_searchview_custom_list"/>
|
||||
<div class="oe_searchview_custom">
|
||||
<h4>Save current filter</h4>
|
||||
<form>
|
||||
<p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
|
||||
<p><input id="oe_searchview_custom_public" type="checkbox"/>
|
||||
<label for="oe_searchview_custom_public">Share with all users</label></p>
|
||||
<button>Save</button>
|
||||
</form>
|
||||
<h4>Save current filter</h4>
|
||||
<form>
|
||||
<p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
|
||||
<p>
|
||||
<input id="oe_searchview_custom_public" type="checkbox"/>
|
||||
<label for="oe_searchview_custom_public">Share with all users</label>
|
||||
<input id="oe_searchview_custom_default" type="checkbox"/>
|
||||
<label for="oe_searchview_custom_default">Use by default</label>
|
||||
</p>
|
||||
<button>Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
|
||||
|
||||
@@ -1,32 +1,7 @@
|
||||
$(document).ready(function () {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var mod = {
|
||||
setup: function () {
|
||||
instance = window.openerp.init([]);
|
||||
window.openerp.web.corelib(instance);
|
||||
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
'<li t-foreach="5" t-as="counter" ' +
|
||||
't-attf-class="class-#{counter}">' +
|
||||
'<input/>' +
|
||||
'<t t-esc="counter"/>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</t>' +
|
||||
'<t t-name="test.widget.template-value">' +
|
||||
'<p><t t-esc="widget.value"/></p>' +
|
||||
'</t>' +
|
||||
'</no>');
|
||||
}
|
||||
};
|
||||
var instance;
|
||||
|
||||
module('Widget.proxy', mod);
|
||||
test('(String)', function () {
|
||||
openerp.testing.section('Widget.proxy', {
|
||||
dependencies: ['web.corelib']
|
||||
}, function (test) {
|
||||
test('(String)', function (instance) {
|
||||
var W = instance.web.Widget.extend({
|
||||
exec: function () {
|
||||
this.executed = true;
|
||||
@@ -37,7 +12,7 @@ $(document).ready(function () {
|
||||
fn();
|
||||
ok(w.executed, 'should execute the named method in the right context');
|
||||
});
|
||||
test('(String)(*args)', function () {
|
||||
test('(String)(*args)', function (instance) {
|
||||
var W = instance.web.Widget.extend({
|
||||
exec: function (arg) {
|
||||
this.executed = arg;
|
||||
@@ -49,7 +24,7 @@ $(document).ready(function () {
|
||||
ok(w.executed, "should execute the named method in the right context");
|
||||
equal(w.executed, 42, "should be passed the proxy's arguments");
|
||||
});
|
||||
test('(String), include', function () {
|
||||
test('(String), include', function (instance) {
|
||||
// the proxy function should handle methods being changed on the class
|
||||
// and should always proxy "by name", to the most recent one
|
||||
var W = instance.web.Widget.extend({
|
||||
@@ -67,23 +42,43 @@ $(document).ready(function () {
|
||||
equal(w.executed, 2, "should be lazily resolved");
|
||||
});
|
||||
|
||||
test('(Function)', function () {
|
||||
test('(Function)', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({ }));
|
||||
|
||||
var fn = w.proxy(function () { this.executed = true; });
|
||||
fn();
|
||||
ok(w.executed, "should set the function's context (like Function#bind)");
|
||||
});
|
||||
test('(Function)(*args)', function () {
|
||||
test('(Function)(*args)', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({ }));
|
||||
|
||||
var fn = w.proxy(function (arg) { this.executed = arg; });
|
||||
fn(42);
|
||||
equal(w.executed, 42, "should be passed the proxy's arguments");
|
||||
});
|
||||
|
||||
module('Widget.renderElement', mod);
|
||||
test('no template, default', function () {
|
||||
});
|
||||
openerp.testing.section('Widget.renderElement', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
'<li t-foreach="5" t-as="counter" ' +
|
||||
't-attf-class="class-#{counter}">' +
|
||||
'<input/>' +
|
||||
'<t t-esc="counter"/>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</t>' +
|
||||
'<t t-name="test.widget.template-value">' +
|
||||
'<p><t t-esc="widget.value"/></p>' +
|
||||
'</t>' +
|
||||
'</no>');
|
||||
}
|
||||
}, function (test) {
|
||||
test('no template, default', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({ }));
|
||||
|
||||
var $original = w.$el;
|
||||
@@ -98,7 +93,7 @@ $(document).ready(function () {
|
||||
equal(w.el.attributes.length, 0, "should not have generated any attribute");
|
||||
ok(_.isEmpty(w.$el.html(), "should not have generated any content"));
|
||||
});
|
||||
test('no template, custom tag', function () {
|
||||
test('no template, custom tag', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
tagName: 'ul'
|
||||
}));
|
||||
@@ -106,7 +101,7 @@ $(document).ready(function () {
|
||||
|
||||
equal(w.el.nodeName, 'UL', "should have generated the custom element tag");
|
||||
});
|
||||
test('no template, @id', function () {
|
||||
test('no template, @id', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
id: 'foo'
|
||||
}));
|
||||
@@ -116,7 +111,7 @@ $(document).ready(function () {
|
||||
equal(w.$el.attr('id'), 'foo', "should have generated the id attribute");
|
||||
equal(w.el.id, 'foo', "should also be available via property");
|
||||
});
|
||||
test('no template, @className', function () {
|
||||
test('no template, @className', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
className: 'oe_some_class'
|
||||
}));
|
||||
@@ -125,7 +120,7 @@ $(document).ready(function () {
|
||||
equal(w.el.className, 'oe_some_class', "should have the right property");
|
||||
equal(w.$el.attr('class'), 'oe_some_class', "should have the right attribute");
|
||||
});
|
||||
test('no template, bunch of attributes', function () {
|
||||
test('no template, bunch of attributes', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
attributes: {
|
||||
'id': 'some_id',
|
||||
@@ -152,7 +147,7 @@ $(document).ready(function () {
|
||||
equal(w.$el.attr('spoiler'), 'snape kills dumbledore');
|
||||
});
|
||||
|
||||
test('template', function () {
|
||||
test('template', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template'
|
||||
}));
|
||||
@@ -162,9 +157,41 @@ $(document).ready(function () {
|
||||
equal(w.$el.children().length, 5);
|
||||
equal(w.el.textContent, '01234');
|
||||
});
|
||||
|
||||
module('Widget.$', mod);
|
||||
test('basic-alias', function () {
|
||||
test('repeated', { asserts: 4 }, function (instance, $fix) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template-value'
|
||||
}));
|
||||
w.value = 42;
|
||||
return w.appendTo($fix)
|
||||
.done(function () {
|
||||
equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
|
||||
equal(w.$el.text(), '42', "should set initial value");
|
||||
w.value = 36;
|
||||
w.renderElement();
|
||||
equal($fix.find('p').text(), '36', "DOM fixture should use new value");
|
||||
equal(w.$el.text(), '36', "should set new value");
|
||||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('Widget.$', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
'<li t-foreach="5" t-as="counter" ' +
|
||||
't-attf-class="class-#{counter}">' +
|
||||
'<input/>' +
|
||||
'<t t-esc="counter"/>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</t>' +
|
||||
'</no>');
|
||||
}
|
||||
}, function (test) {
|
||||
test('basic-alias', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template'
|
||||
}));
|
||||
@@ -173,9 +200,26 @@ $(document).ready(function () {
|
||||
ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')),
|
||||
"should do the same thing as calling find on the widget root");
|
||||
});
|
||||
|
||||
module('Widget.events', mod);
|
||||
test('delegate', function () {
|
||||
});
|
||||
openerp.testing.section('Widget.events', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
'<li t-foreach="5" t-as="counter" ' +
|
||||
't-attf-class="class-#{counter}">' +
|
||||
'<input/>' +
|
||||
'<t t-esc="counter"/>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</t>' +
|
||||
'</no>');
|
||||
}
|
||||
}, function (test) {
|
||||
test('delegate', function (instance) {
|
||||
var a = [];
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template',
|
||||
@@ -199,7 +243,7 @@ $(document).ready(function () {
|
||||
ok(a[i], "should pass test " + i);
|
||||
}
|
||||
});
|
||||
test('undelegate', function () {
|
||||
test('undelegate', function (instance) {
|
||||
var clicked = false, newclicked = false;
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template',
|
||||
@@ -218,22 +262,4 @@ $(document).ready(function () {
|
||||
ok(!clicked, "undelegate should unbind events delegated");
|
||||
ok(newclicked, "undelegate should only unbind events it created");
|
||||
});
|
||||
|
||||
module('Widget.renderElement', mod);
|
||||
asyncTest('repeated', 4, function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template-value'
|
||||
}));
|
||||
w.value = 42;
|
||||
w.appendTo($fix)
|
||||
.always(start)
|
||||
.done(function () {
|
||||
equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
|
||||
equal(w.$el.text(), '42', "should set initial value");
|
||||
w.value = 36;
|
||||
w.renderElement();
|
||||
equal($fix.find('p').text(), '36', "DOM fixture should use new value");
|
||||
equal(w.$el.text(), '36', "should set new value");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,30 +1,25 @@
|
||||
$(document).ready(function () {
|
||||
var openerp;
|
||||
module('web-class', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
}
|
||||
});
|
||||
test('Basic class creation', function () {
|
||||
var C = openerp.web.Class.extend({
|
||||
openerp.testing.section('class', {
|
||||
dependencies: ['web.corelib']
|
||||
}, function (test) {
|
||||
test('Basic class creation', function (instance) {
|
||||
var C = instance.web.Class.extend({
|
||||
foo: function () {
|
||||
return this.somevar;
|
||||
}
|
||||
});
|
||||
var instance = new C();
|
||||
instance.somevar = 3;
|
||||
var i = new C();
|
||||
i.somevar = 3;
|
||||
|
||||
ok(instance instanceof C);
|
||||
strictEqual(instance.foo(), 3);
|
||||
ok(i instanceof C);
|
||||
strictEqual(i.foo(), 3);
|
||||
});
|
||||
test('Class initialization', function () {
|
||||
var C1 = openerp.web.Class.extend({
|
||||
test('Class initialization', function (instance) {
|
||||
var C1 = instance.web.Class.extend({
|
||||
init: function () {
|
||||
this.foo = 3;
|
||||
}
|
||||
});
|
||||
var C2 = openerp.web.Class.extend({
|
||||
var C2 = instance.web.Class.extend({
|
||||
init: function (arg) {
|
||||
this.foo = arg;
|
||||
}
|
||||
@@ -36,8 +31,8 @@ $(document).ready(function () {
|
||||
strictEqual(i1.foo, 3);
|
||||
strictEqual(i2.foo, 42);
|
||||
});
|
||||
test('Inheritance', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
test('Inheritance', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () {
|
||||
return 1;
|
||||
}
|
||||
@@ -57,8 +52,8 @@ $(document).ready(function () {
|
||||
strictEqual(new C1().foo(), 2);
|
||||
strictEqual(new C2().foo(), 3);
|
||||
});
|
||||
test('In-place extension', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
test('In-place extension', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () {
|
||||
return 3;
|
||||
},
|
||||
@@ -83,8 +78,8 @@ $(document).ready(function () {
|
||||
strictEqual(new C0().foo(), 5);
|
||||
strictEqual(new C0().qux(), 5);
|
||||
});
|
||||
test('In-place extension and inheritance', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
test('In-place extension and inheritance', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
@@ -101,24 +96,24 @@ $(document).ready(function () {
|
||||
strictEqual(new C1().foo(), 4);
|
||||
strictEqual(new C1().bar(), 2);
|
||||
});
|
||||
test('In-place extensions alter existing instances', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
test('In-place extensions alter existing instances', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var instance = new C0();
|
||||
strictEqual(instance.foo(), 1);
|
||||
strictEqual(instance.bar(), 1);
|
||||
var i = new C0();
|
||||
strictEqual(i.foo(), 1);
|
||||
strictEqual(i.bar(), 1);
|
||||
|
||||
C0.include({
|
||||
foo: function () { return 2; },
|
||||
bar: function () { return 2 + this._super(); }
|
||||
});
|
||||
strictEqual(instance.foo(), 2);
|
||||
strictEqual(instance.bar(), 3);
|
||||
strictEqual(i.foo(), 2);
|
||||
strictEqual(i.bar(), 3);
|
||||
});
|
||||
test('In-place extension of subclassed types', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
test('In-place extension of subclassed types', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
@@ -126,13 +121,13 @@ $(document).ready(function () {
|
||||
foo: function () { return 1 + this._super(); },
|
||||
bar: function () { return 1 + this._super(); }
|
||||
});
|
||||
var instance = new C1();
|
||||
strictEqual(instance.foo(), 2);
|
||||
var i = new C1();
|
||||
strictEqual(i.foo(), 2);
|
||||
C0.include({
|
||||
foo: function () { return 2; },
|
||||
bar: function () { return 2 + this._super(); }
|
||||
});
|
||||
strictEqual(instance.foo(), 3);
|
||||
strictEqual(instance.bar(), 4);
|
||||
strictEqual(i.foo(), 3);
|
||||
strictEqual(i.bar(), 4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
$(document).ready(function () {
|
||||
var openerp;
|
||||
|
||||
module("eval.contexts", {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
}
|
||||
});
|
||||
test('context_sequences', function () {
|
||||
openerp.testing.section('eval.contexts', {
|
||||
dependencies: ['web.coresetup']
|
||||
}, function (test) {
|
||||
test('context_sequences', function (instance) {
|
||||
// Context n should have base evaluation context + all of contexts
|
||||
// 0..n-1 in its own evaluation context
|
||||
var active_id = 4;
|
||||
var result = openerp.session.test_eval_contexts([
|
||||
var result = instance.session.test_eval_contexts([
|
||||
{
|
||||
"__contexts": [
|
||||
{
|
||||
@@ -55,8 +48,8 @@ $(document).ready(function () {
|
||||
record_id: active_id
|
||||
});
|
||||
});
|
||||
test('non-literal_eval_contexts', function () {
|
||||
var result = openerp.session.test_eval_contexts([{
|
||||
test('non-literal_eval_contexts', function (instance) {
|
||||
var result = instance.session.test_eval_contexts([{
|
||||
"__ref": "compound_context",
|
||||
"__contexts": [
|
||||
{"__ref": "context", "__debug": "{'type':parent.type}",
|
||||
@@ -133,17 +126,15 @@ $(document).ready(function () {
|
||||
}]);
|
||||
deepEqual(result, {type: 'out_invoice'});
|
||||
});
|
||||
module('eval.domains', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.testing.instanceFor('coresetup');
|
||||
window.openerp.web.dates(openerp);
|
||||
}
|
||||
});
|
||||
test('current_date', function () {
|
||||
var current_date = openerp.web.date_to_str(new Date());
|
||||
var result = openerp.session.test_eval_domains(
|
||||
});
|
||||
openerp.testing.section('eval.contexts', {
|
||||
dependencies: ['web.coresetup', 'web.dates']
|
||||
}, function (test) {
|
||||
test('current_date', function (instance) {
|
||||
var current_date = instance.web.date_to_str(new Date());
|
||||
var result = instance.session.test_eval_domains(
|
||||
[[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
|
||||
openerp.session.test_eval_get_context());
|
||||
instance.session.test_eval_get_context());
|
||||
deepEqual(result, [
|
||||
['name', '>=', current_date],
|
||||
['name', '<=', current_date]
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
$(document).ready(function () {
|
||||
var openerp;
|
||||
module("form.widget", {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.web.data(openerp);
|
||||
window.openerp.web.views(openerp);
|
||||
window.openerp.web.list(openerp);
|
||||
window.openerp.web.form(openerp);
|
||||
}
|
||||
});
|
||||
test("compute_domain", function () {
|
||||
openerp.testing.section('compute_domain', {
|
||||
dependencies: ['web.form']
|
||||
}, function (test) {
|
||||
test("basic", function (instance) {
|
||||
var fields = {
|
||||
'a': {value: 3},
|
||||
'group_method': {value: 'line'},
|
||||
'select1': {value: 'day'},
|
||||
'rrule_type': {value: 'monthly'}
|
||||
};
|
||||
ok(openerp.web.form.compute_domain(
|
||||
ok(instance.web.form.compute_domain(
|
||||
[['a', '=', 3]], fields));
|
||||
ok(openerp.web.form.compute_domain(
|
||||
ok(instance.web.form.compute_domain(
|
||||
[['group_method','!=','count']], fields));
|
||||
ok(openerp.web.form.compute_domain(
|
||||
ok(instance.web.form.compute_domain(
|
||||
[['select1','=','day'], ['rrule_type','=','monthly']], fields));
|
||||
});
|
||||
test("compute_domain or", function () {
|
||||
test("or", function (instance) {
|
||||
var web = {
|
||||
'section_id': {value: null},
|
||||
'user_id': {value: null},
|
||||
@@ -38,22 +26,22 @@ $(document).ready(function () {
|
||||
'|', ['user_id','=',3],
|
||||
['member_ids', 'in', [3]]];
|
||||
|
||||
ok(openerp.web.form.compute_domain(domain, _.extend(
|
||||
ok(instance.web.form.compute_domain(domain, _.extend(
|
||||
{}, web, {'section_id': {value: 42}})));
|
||||
ok(openerp.web.form.compute_domain(domain, _.extend(
|
||||
ok(instance.web.form.compute_domain(domain, _.extend(
|
||||
{}, web, {'user_id': {value: 3}})));
|
||||
|
||||
ok(openerp.web.form.compute_domain(domain, _.extend(
|
||||
ok(instance.web.form.compute_domain(domain, _.extend(
|
||||
{}, web, {'member_ids': {value: 3}})));
|
||||
});
|
||||
test("compute_domain not", function () {
|
||||
test("not", function (instance) {
|
||||
var fields = {
|
||||
'a': {value: 5},
|
||||
'group_method': {value: 'line'}
|
||||
};
|
||||
ok(openerp.web.form.compute_domain(
|
||||
ok(instance.web.form.compute_domain(
|
||||
['!', ['a', '=', 3]], fields));
|
||||
ok(openerp.web.form.compute_domain(
|
||||
ok(instance.web.form.compute_domain(
|
||||
['!', ['group_method','=','count']], fields));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
$(document).ready(function () {
|
||||
var openerp;
|
||||
|
||||
module('server-formats', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.dates(openerp);
|
||||
}
|
||||
});
|
||||
test('Parse server datetime', function () {
|
||||
var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
openerp.testing.section('server-formats', {
|
||||
dependencies: ['web.coresetup', 'web.dates']
|
||||
}, function (test) {
|
||||
test('Parse server datetime', function (instance) {
|
||||
var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
deepEqual(
|
||||
[date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
|
||||
date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()],
|
||||
@@ -20,92 +12,86 @@ $(document).ready(function () {
|
||||
date.getHours(), date.getMinutes(), date.getSeconds()],
|
||||
[2009, 5 - 1, 4, 12 - (date.getTimezoneOffset() / 60), 34, 23]);
|
||||
|
||||
var date2 = openerp.web.str_to_datetime('2011-12-10 00:00:00');
|
||||
var date2 = instance.web.str_to_datetime('2011-12-10 00:00:00');
|
||||
deepEqual(
|
||||
[date2.getUTCFullYear(), date2.getUTCMonth(), date2.getUTCDate(),
|
||||
date2.getUTCHours(), date2.getUTCMinutes(), date2.getUTCSeconds()],
|
||||
[2011, 12 - 1, 10, 0, 0, 0]);
|
||||
});
|
||||
test('Parse server date', function () {
|
||||
var date = openerp.web.str_to_date("2009-05-04");
|
||||
test('Parse server date', function (instance) {
|
||||
var date = instance.web.str_to_date("2009-05-04");
|
||||
deepEqual(
|
||||
[date.getFullYear(), date.getMonth(), date.getDate()],
|
||||
[2009, 5 - 1, 4]);
|
||||
});
|
||||
test('Parse server time', function () {
|
||||
var date = openerp.web.str_to_time("12:34:23");
|
||||
test('Parse server time', function (instance) {
|
||||
var date = instance.web.str_to_time("12:34:23");
|
||||
deepEqual(
|
||||
[date.getHours(), date.getMinutes(), date.getSeconds()],
|
||||
[12, 34, 23]);
|
||||
});
|
||||
|
||||
module('web-formats', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.dates(openerp);
|
||||
window.openerp.web.formats(openerp);
|
||||
}
|
||||
});
|
||||
test("format_datetime", function () {
|
||||
var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
var str = openerp.web.format_value(date, {type:"datetime"});
|
||||
});
|
||||
openerp.testing.section('web-formats', {
|
||||
dependencies: ['web.formats']
|
||||
}, function (test) {
|
||||
test("format_datetime", function (instance) {
|
||||
var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
var str = instance.web.format_value(date, {type:"datetime"});
|
||||
equal(str, date.toString("MM/dd/yyyy HH:mm:ss"));
|
||||
});
|
||||
test("format_date", function () {
|
||||
var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
var str = openerp.web.format_value(date, {type:"date"});
|
||||
test("format_date", function (instance) {
|
||||
var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
var str = instance.web.format_value(date, {type:"date"});
|
||||
equal(str, date.toString("MM/dd/yyyy"));
|
||||
});
|
||||
test("format_time", function () {
|
||||
var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
var str = openerp.web.format_value(date, {type:"time"});
|
||||
test("format_time", function (instance) {
|
||||
var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
|
||||
var str = instance.web.format_value(date, {type:"time"});
|
||||
equal(str, date.toString("HH:mm:ss"));
|
||||
});
|
||||
test("format_float_time", function () {
|
||||
test("format_float_time", function (instance) {
|
||||
strictEqual(
|
||||
openerp.web.format_value(1.0, {type:'float', widget:'float_time'}),
|
||||
instance.web.format_value(1.0, {type:'float', widget:'float_time'}),
|
||||
'01:00');
|
||||
strictEqual(
|
||||
openerp.web.format_value(0.9853, {type:'float', widget:'float_time'}),
|
||||
instance.web.format_value(0.9853, {type:'float', widget:'float_time'}),
|
||||
'00:59');
|
||||
strictEqual(
|
||||
openerp.web.format_value(0.0085, {type:'float', widget:'float_time'}),
|
||||
instance.web.format_value(0.0085, {type:'float', widget:'float_time'}),
|
||||
'00:01');
|
||||
strictEqual(
|
||||
openerp.web.format_value(-1.0, {type:'float', widget:'float_time'}),
|
||||
instance.web.format_value(-1.0, {type:'float', widget:'float_time'}),
|
||||
'-01:00');
|
||||
strictEqual(
|
||||
openerp.web.format_value(-0.9853, {type:'float', widget:'float_time'}),
|
||||
instance.web.format_value(-0.9853, {type:'float', widget:'float_time'}),
|
||||
'-00:59');
|
||||
strictEqual(
|
||||
openerp.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
|
||||
instance.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
|
||||
'-00:01');
|
||||
});
|
||||
test("format_float", function () {
|
||||
test("format_float", function (instance) {
|
||||
var fl = 12.1234;
|
||||
var str = openerp.web.format_value(fl, {type:"float"});
|
||||
var str = instance.web.format_value(fl, {type:"float"});
|
||||
equal(str, "12.12");
|
||||
equal(openerp.web.format_value(12.02, {type: 'float'}),
|
||||
equal(instance.web.format_value(12.02, {type: 'float'}),
|
||||
'12.02');
|
||||
equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 3]}),
|
||||
equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 3]}),
|
||||
'0.000');
|
||||
equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 4]}),
|
||||
equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 4]}),
|
||||
'0.0002');
|
||||
equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 6]}),
|
||||
equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 6]}),
|
||||
'0.000200');
|
||||
equal(openerp.web.format_value(1, {type: 'float', digits: [1, 6]}),
|
||||
equal(instance.web.format_value(1, {type: 'float', digits: [1, 6]}),
|
||||
'1.000000');
|
||||
equal(openerp.web.format_value(1, {type: 'float'}),
|
||||
equal(instance.web.format_value(1, {type: 'float'}),
|
||||
'1.00');
|
||||
equal(openerp.web.format_value(-11.25, {type: 'float'}),
|
||||
equal(instance.web.format_value(-11.25, {type: 'float'}),
|
||||
"-11.25");
|
||||
openerp.web._t.database.parameters.grouping = [1, 2, -1];
|
||||
equal(openerp.web.format_value(1111111.25, {type: 'float'}),
|
||||
instance.web._t.database.parameters.grouping = [1, 2, -1];
|
||||
equal(instance.web.format_value(1111111.25, {type: 'float'}),
|
||||
"1111,11,1.25");
|
||||
openerp.web._t.database.parameters.grouping = [1, 0];
|
||||
equal(openerp.web.format_value(-11.25, {type: 'float'}),
|
||||
instance.web._t.database.parameters.grouping = [1, 0];
|
||||
equal(instance.web.format_value(-11.25, {type: 'float'}),
|
||||
"-1,1.25");
|
||||
});
|
||||
// test("parse_datetime", function () {
|
||||
@@ -123,29 +109,29 @@ $(document).ready(function () {
|
||||
// var res = openerp.web.parse_value(val.toString("HH:mm:ss"), {type:"time"});
|
||||
// equal(val.toString("HH:mm:ss"), res.toString("HH:mm:ss"));
|
||||
// });
|
||||
test('parse_integer', function () {
|
||||
var val = openerp.web.parse_value('123,456', {type: 'integer'});
|
||||
test('parse_integer', function (instance) {
|
||||
var val = instance.web.parse_value('123,456', {type: 'integer'});
|
||||
equal(val, 123456);
|
||||
openerp.web._t.database.parameters.thousands_sep = '|';
|
||||
var val2 = openerp.web.parse_value('123|456', {type: 'integer'});
|
||||
instance.web._t.database.parameters.thousands_sep = '|';
|
||||
var val2 = instance.web.parse_value('123|456', {type: 'integer'});
|
||||
equal(val2, 123456);
|
||||
});
|
||||
test("parse_float", function () {
|
||||
test("parse_float", function (instance) {
|
||||
var str = "134,112.1234";
|
||||
var val = openerp.web.parse_value(str, {type:"float"});
|
||||
var val = instance.web.parse_value(str, {type:"float"});
|
||||
equal(val, 134112.1234);
|
||||
var str = "-134,112.1234";
|
||||
var val = openerp.web.parse_value(str, {type:"float"});
|
||||
var val = instance.web.parse_value(str, {type:"float"});
|
||||
equal(val, -134112.1234);
|
||||
_.extend(openerp.web._t.database.parameters, {
|
||||
_.extend(instance.web._t.database.parameters, {
|
||||
decimal_point: ',',
|
||||
thousands_sep: '.'
|
||||
});
|
||||
var val3 = openerp.web.parse_value('123.456,789', {type: 'float'});
|
||||
var val3 = instance.web.parse_value('123.456,789', {type: 'float'});
|
||||
equal(val3, 123456.789);
|
||||
});
|
||||
test('intersperse', function () {
|
||||
var g = openerp.web.intersperse;
|
||||
test('intersperse', function (instance) {
|
||||
var g = instance.web.intersperse;
|
||||
equal(g("", []), "");
|
||||
equal(g("0", []), "0");
|
||||
equal(g("012", []), "012");
|
||||
@@ -176,60 +162,61 @@ $(document).ready(function () {
|
||||
equal(g("12345678", [3,3,3,3], '.'), '12.345.678');
|
||||
equal(g("12345678", [3,0], '.'), '12.345.678');
|
||||
});
|
||||
test('format_integer', function () {
|
||||
openerp.web._t.database.parameters.grouping = [3, 3, 3, 3];
|
||||
equal(openerp.web.format_value(1000000, {type: 'integer'}),
|
||||
test('format_integer', function (instance) {
|
||||
instance.web._t.database.parameters.grouping = [3, 3, 3, 3];
|
||||
equal(instance.web.format_value(1000000, {type: 'integer'}),
|
||||
'1,000,000');
|
||||
openerp.web._t.database.parameters.grouping = [3, 2, -1];
|
||||
equal(openerp.web.format_value(106500, {type: 'integer'}),
|
||||
instance.web._t.database.parameters.grouping = [3, 2, -1];
|
||||
equal(instance.web.format_value(106500, {type: 'integer'}),
|
||||
'1,06,500');
|
||||
openerp.web._t.database.parameters.grouping = [1, 2, -1];
|
||||
equal(openerp.web.format_value(106500, {type: 'integer'}),
|
||||
instance.web._t.database.parameters.grouping = [1, 2, -1];
|
||||
equal(instance.web.format_value(106500, {type: 'integer'}),
|
||||
'106,50,0');
|
||||
});
|
||||
test('format_float', function () {
|
||||
openerp.web._t.database.parameters.grouping = [3, 3, 3, 3];
|
||||
equal(openerp.web.format_value(1000000, {type: 'float'}),
|
||||
test('format_float', function (instance) {
|
||||
instance.web._t.database.parameters.grouping = [3, 3, 3, 3];
|
||||
equal(instance.web.format_value(1000000, {type: 'float'}),
|
||||
'1,000,000.00');
|
||||
openerp.web._t.database.parameters.grouping = [3, 2, -1];
|
||||
equal(openerp.web.format_value(106500, {type: 'float'}),
|
||||
instance.web._t.database.parameters.grouping = [3, 2, -1];
|
||||
equal(instance.web.format_value(106500, {type: 'float'}),
|
||||
'1,06,500.00');
|
||||
openerp.web._t.database.parameters.grouping = [1, 2, -1];
|
||||
equal(openerp.web.format_value(106500, {type: 'float'}),
|
||||
instance.web._t.database.parameters.grouping = [1, 2, -1];
|
||||
equal(instance.web.format_value(106500, {type: 'float'}),
|
||||
'106,50,0.00');
|
||||
|
||||
_.extend(openerp.web._t.database.parameters, {
|
||||
_.extend(instance.web._t.database.parameters, {
|
||||
grouping: [3, 0],
|
||||
decimal_point: ',',
|
||||
thousands_sep: '.'
|
||||
});
|
||||
equal(openerp.web.format_value(6000, {type: 'float'}),
|
||||
equal(instance.web.format_value(6000, {type: 'float'}),
|
||||
'6.000,00');
|
||||
});
|
||||
module('custom-date-formats', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.dates(openerp);
|
||||
window.openerp.web.formats(openerp);
|
||||
}
|
||||
});
|
||||
openerp.testing.section('web-formats', {
|
||||
dependencies: ['web.formats']
|
||||
}, function (test) {
|
||||
test('format stripper', function (instance) {
|
||||
strictEqual(instance.web.strip_raw_chars('%a, %Y %b %d'),
|
||||
'%a, %Y %b %d');
|
||||
strictEqual(instance.web.strip_raw_chars('%a, %Y.eko %bren %da'),
|
||||
'%a, %Y. %b %d');
|
||||
});
|
||||
test('format stripper', function () {
|
||||
strictEqual(openerp.web.strip_raw_chars('%a, %Y %b %d'), '%a, %Y %b %d');
|
||||
strictEqual(openerp.web.strip_raw_chars('%a, %Y.eko %bren %da'), '%a, %Y. %b %d');
|
||||
test('ES date format', function (instance) {
|
||||
instance.web._t.database.parameters.date_format = '%a, %Y %b %d';
|
||||
var date = instance.web.str_to_date("2009-05-04");
|
||||
strictEqual(instance.web.format_value(date, {type:"date"}),
|
||||
'Mon, 2009 May 04');
|
||||
strictEqual(instance.web.parse_value('Mon, 2009 May 04', {type: 'date'}),
|
||||
'2009-05-04');
|
||||
});
|
||||
test('ES date format', function () {
|
||||
openerp.web._t.database.parameters.date_format = '%a, %Y %b %d';
|
||||
var date = openerp.web.str_to_date("2009-05-04");
|
||||
strictEqual(openerp.web.format_value(date, {type:"date"}), 'Mon, 2009 May 04');
|
||||
strictEqual(openerp.web.parse_value('Mon, 2009 May 04', {type: 'date'}), '2009-05-04');
|
||||
});
|
||||
test('extended ES date format', function () {
|
||||
openerp.web._t.database.parameters.date_format = '%a, %Y.eko %bren %da';
|
||||
var date = openerp.web.str_to_date("2009-05-04");
|
||||
strictEqual(openerp.web.format_value(date, {type:"date"}), 'Mon, 2009. May 04');
|
||||
strictEqual(openerp.web.parse_value('Mon, 2009. May 04', {type: 'date'}), '2009-05-04');
|
||||
test('extended ES date format', function (instance) {
|
||||
instance.web._t.database.parameters.date_format = '%a, %Y.eko %bren %da';
|
||||
var date = instance.web.str_to_date("2009-05-04");
|
||||
strictEqual(instance.web.format_value(date, {type:"date"}),
|
||||
'Mon, 2009. May 04');
|
||||
strictEqual(instance.web.parse_value('Mon, 2009. May 04', {type: 'date'}),
|
||||
'2009-05-04');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
$(document).ready(function () {
|
||||
var $fix = $('#qunit-fixture');
|
||||
|
||||
var instance;
|
||||
var baseSetup = function () {
|
||||
instance = openerp.testing.instanceFor('list_editable');
|
||||
|
||||
openerp.testing.loadTemplate(instance);
|
||||
|
||||
openerp.testing.mockifyRPC(instance);
|
||||
};
|
||||
|
||||
|
||||
openerp.testing.section('editor', {
|
||||
dependencies: ['web.list_editable'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
setup: function (instance, $s, mock) {
|
||||
mock('test.model:create', function () {
|
||||
return 42;
|
||||
});
|
||||
}
|
||||
}, function (test) {
|
||||
/**
|
||||
*
|
||||
* @param {String} name
|
||||
@@ -30,7 +27,7 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} fields
|
||||
* @param {Array} [fields]
|
||||
* @return {Object}
|
||||
*/
|
||||
function makeFormView(fields) {
|
||||
@@ -67,46 +64,37 @@ $(document).ready(function () {
|
||||
};
|
||||
}
|
||||
|
||||
module('editor', {
|
||||
setup: baseSetup
|
||||
});
|
||||
asyncTest('base-state', 2, function () {
|
||||
test('base-state', {asserts: 2}, function (instance, $fix) {
|
||||
var e = new instance.web.list.Editor({
|
||||
dataset: {ids: []},
|
||||
edition_view: function () {
|
||||
return makeFormView();
|
||||
}
|
||||
});
|
||||
e.appendTo($fix)
|
||||
.always(start)
|
||||
.fail(function (error) { ok(false, error && error.message); })
|
||||
return e.appendTo($fix)
|
||||
.done(function () {
|
||||
ok(!e.is_editing(), "should not be editing");
|
||||
ok(e.form instanceof instance.web.FormView,
|
||||
"should use default form type");
|
||||
});
|
||||
});
|
||||
asyncTest('toggle-edition-save', 4, function () {
|
||||
instance.session.responses['/web/dataset/call_kw:create'] = function () {
|
||||
return { result: 42 };
|
||||
};
|
||||
instance.session.responses['/web/dataset/call_kw:read'] = function () {
|
||||
return { result: [{
|
||||
id: 42,
|
||||
a: false,
|
||||
b: false,
|
||||
c: false
|
||||
}]};
|
||||
};
|
||||
test('toggle-edition-save', {
|
||||
asserts: 4,
|
||||
setup: function (instance, $s, mock) {
|
||||
mock('test.model:read', function () {
|
||||
return [{id: 42, a: false, b: false, c: false}];
|
||||
});
|
||||
}
|
||||
}, function (instance, $fix) {
|
||||
var e = new instance.web.list.Editor({
|
||||
dataset: new instance.web.DataSetSearch(),
|
||||
dataset: new instance.web.DataSetSearch(null, 'test.model'),
|
||||
prepends_on_create: function () { return false; },
|
||||
edition_view: function () {
|
||||
return makeFormView([ field('a'), field('b'), field('c') ]);
|
||||
}
|
||||
});
|
||||
var counter = 0;
|
||||
e.appendTo($fix)
|
||||
return e.appendTo($fix)
|
||||
.then(function () {
|
||||
return e.edit({}, function () {
|
||||
++counter;
|
||||
@@ -117,26 +105,21 @@ $(document).ready(function () {
|
||||
equal(counter, 3, "should have configured all fields");
|
||||
return e.save();
|
||||
})
|
||||
.always(start)
|
||||
.fail(function (error) { ok(false, error && error.message); })
|
||||
.done(function (record) {
|
||||
ok(!e.is_editing(), "should have stopped editing");
|
||||
equal(record.id, 42, "should have newly created id");
|
||||
})
|
||||
});
|
||||
asyncTest('toggle-edition-cancel', 2, function () {
|
||||
instance.session.responses['/web/dataset/call_kw:create'] = function () {
|
||||
return { result: 42 };
|
||||
};
|
||||
test('toggle-edition-cancel', { asserts: 2 }, function (instance, $fix) {
|
||||
var e = new instance.web.list.Editor({
|
||||
dataset: new instance.web.DataSetSearch(),
|
||||
dataset: new instance.web.DataSetSearch(null, 'test.model'),
|
||||
prepends_on_create: function () { return false; },
|
||||
edition_view: function () {
|
||||
return makeFormView([ field('a'), field('b'), field('c') ]);
|
||||
}
|
||||
});
|
||||
var counter = 0;
|
||||
e.appendTo($fix)
|
||||
return e.appendTo($fix)
|
||||
.then(function () {
|
||||
return e.edit({}, function () {
|
||||
++counter;
|
||||
@@ -145,22 +128,20 @@ $(document).ready(function () {
|
||||
.then(function (form) {
|
||||
return e.cancel();
|
||||
})
|
||||
.always(start)
|
||||
.fail(function (error) { ok(false, error && error.message); })
|
||||
.done(function (record) {
|
||||
ok(!e.is_editing(), "should have stopped editing");
|
||||
ok(!record.id, "should have no id");
|
||||
})
|
||||
});
|
||||
asyncTest('toggle-save-required', 2, function () {
|
||||
instance.session.responses['/web/dataset/call_kw:create'] = function () {
|
||||
return { result: 42 };
|
||||
};
|
||||
test('toggle-save-required', {
|
||||
asserts: 2,
|
||||
fail_on_rejection: false
|
||||
}, function (instance, $fix) {
|
||||
var e = new instance.web.list.Editor({
|
||||
do_warn: function () {
|
||||
warnings++;
|
||||
},
|
||||
dataset: new instance.web.DataSetSearch(),
|
||||
dataset: new instance.web.DataSetSearch(null, 'test.model'),
|
||||
prepends_on_create: function () { return false; },
|
||||
edition_view: function () {
|
||||
return makeFormView([
|
||||
@@ -169,7 +150,7 @@ $(document).ready(function () {
|
||||
});
|
||||
var counter = 0;
|
||||
var warnings = 0;
|
||||
e.appendTo($fix)
|
||||
return e.appendTo($fix)
|
||||
.then(function () {
|
||||
return e.edit({}, function () {
|
||||
++counter;
|
||||
@@ -178,78 +159,73 @@ $(document).ready(function () {
|
||||
.then(function (form) {
|
||||
return e.save();
|
||||
})
|
||||
.always(start)
|
||||
.done(function () { ok(false, "cancel should not succeed"); })
|
||||
.fail(function () {
|
||||
equal(warnings, 1, "should have been warned");
|
||||
ok(e.is_editing(), "should have kept editing");
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
module('list-edition', {
|
||||
setup: function () {
|
||||
baseSetup();
|
||||
|
||||
var records = {};
|
||||
_.extend(instance.session.responses, {
|
||||
'/web/view/load': function () {
|
||||
return {result: {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: {
|
||||
tag: 'tree',
|
||||
attrs: {},
|
||||
children: [
|
||||
{tag: 'field', attrs: {name: 'a'}},
|
||||
{tag: 'field', attrs: {name: 'b'}},
|
||||
{tag: 'field', attrs: {name: 'c'}}
|
||||
]
|
||||
}
|
||||
}};
|
||||
});
|
||||
openerp.testing.section('list.edition', {
|
||||
dependencies: ['web.list_editable'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
setup: function (instance, $s, mock) {
|
||||
var records = {};
|
||||
mock('demo:create', function (args) {
|
||||
records[42] = _.extend({}, args[0]);
|
||||
return 42;
|
||||
});
|
||||
mock('demo:read', function (args) {
|
||||
var id = args[0][0];
|
||||
if (id in records) {
|
||||
return [records[id]];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
mock('/web/view/load', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
'/web/dataset/call_kw:create': function (params) {
|
||||
records[42] = _.extend({}, params.params.args[0]);
|
||||
return {result: 42};
|
||||
},
|
||||
'/web/dataset/call_kw:read': function (params) {
|
||||
var id = params.params.args[0][0];
|
||||
if (id in records) {
|
||||
return {result: [records[id]]};
|
||||
}
|
||||
return {result: []};
|
||||
arch: {
|
||||
tag: 'tree',
|
||||
attrs: {},
|
||||
children: [
|
||||
{tag: 'field', attrs: {name: 'a'}},
|
||||
{tag: 'field', attrs: {name: 'b'}},
|
||||
{tag: 'field', attrs: {name: 'c'}}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
asyncTest('newrecord', 6, function () {
|
||||
};
|
||||
});
|
||||
}
|
||||
}, function (test) {
|
||||
test('newrecord', {asserts: 6}, function (instance, $fix, mock) {
|
||||
var got_defaults = false;
|
||||
instance.session.responses['/web/dataset/call_kw:default_get'] = function (params) {
|
||||
var fields = params.params.args[0];
|
||||
mock('demo:default_get', function (args) {
|
||||
var fields = args[0];
|
||||
deepEqual(
|
||||
fields, ['a', 'b', 'c'],
|
||||
"should ask defaults for all fields");
|
||||
got_defaults = true;
|
||||
return {result: {
|
||||
a: "qux",
|
||||
b: "quux"
|
||||
}};
|
||||
};
|
||||
return { a: "qux", b: "quux" };
|
||||
});
|
||||
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
|
||||
l.appendTo($fix)
|
||||
return l.appendTo($fix)
|
||||
.then(l.proxy('reload_content'))
|
||||
.then(function () {
|
||||
return l.start_edition();
|
||||
})
|
||||
.always(start)
|
||||
.then(function () {
|
||||
ok(got_defaults, "should have fetched default values for form");
|
||||
|
||||
return l.save_edition();
|
||||
})
|
||||
.then(function (result) {
|
||||
@@ -260,45 +236,39 @@ $(document).ready(function () {
|
||||
"should have used default values");
|
||||
ok(!result.record.get('c'),
|
||||
"should have no value if there was no default");
|
||||
})
|
||||
.fail(function (e) { ok(false, e && e.message || e); });
|
||||
});
|
||||
|
||||
module('list-edition-events', {
|
||||
setup: function () {
|
||||
baseSetup();
|
||||
_.extend(instance.session.responses, {
|
||||
'/web/view/load': function () {
|
||||
return {result: {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: {
|
||||
tag: 'tree',
|
||||
attrs: {},
|
||||
children: [
|
||||
{tag: 'field', attrs: {name: 'a'}},
|
||||
{tag: 'field', attrs: {name: 'b'}},
|
||||
{tag: 'field', attrs: {name: 'c'}}
|
||||
]
|
||||
}
|
||||
}};
|
||||
},
|
||||
'/web/dataset/call_kw:read': function (params) {
|
||||
return {result: [{
|
||||
id: 1,
|
||||
a: 'foo',
|
||||
b: 'bar',
|
||||
c: 'baz'
|
||||
}]};
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
asyncTest('edition events', 4, function () {
|
||||
});
|
||||
openerp.testing.section('list.edition.events', {
|
||||
dependencies: ['web.list_editable'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
setup: function (instance, $s, mock) {
|
||||
mock('demo:read', function () {
|
||||
return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
|
||||
});
|
||||
mock('/web/view/load', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: {
|
||||
tag: 'tree',
|
||||
attrs: {},
|
||||
children: [
|
||||
{tag: 'field', attrs: {name: 'a'}},
|
||||
{tag: 'field', attrs: {name: 'b'}},
|
||||
{tag: 'field', attrs: {name: 'c'}}
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}, function (test) {
|
||||
test('edition events', {asserts: 4}, function (instance, $fix) {
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||
var o = {
|
||||
counter: 0,
|
||||
@@ -306,9 +276,8 @@ $(document).ready(function () {
|
||||
};
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
l.on('edit:before edit:after', o, o.onEvent);
|
||||
l.appendTo($fix)
|
||||
return l.appendTo($fix)
|
||||
.then(l.proxy('reload_content'))
|
||||
.always(start)
|
||||
.then(function () {
|
||||
ok(l.options.editable, "should be editable");
|
||||
equal(o.counter, 0, "should have seen no event yet");
|
||||
@@ -317,11 +286,10 @@ $(document).ready(function () {
|
||||
.then(function () {
|
||||
ok(l.editor.is_editing(), "should be editing");
|
||||
equal(o.counter, 2, "should have seen two edition events");
|
||||
})
|
||||
.fail(function (e) { ok(false, e && e.message); });
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('edition events: cancelling', 3, function () {
|
||||
test('edition events: cancelling', {asserts: 3}, function (instance, $fix) {
|
||||
var edit_after = false;
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
@@ -331,9 +299,8 @@ $(document).ready(function () {
|
||||
l.on('edit:after', {}, function () {
|
||||
edit_after = true;
|
||||
});
|
||||
l.appendTo($fix)
|
||||
return l.appendTo($fix)
|
||||
.then(l.proxy('reload_content'))
|
||||
.always(start)
|
||||
.then(function () {
|
||||
ok(l.options.editable, "should be editable");
|
||||
return l.start_edition();
|
||||
@@ -343,7 +310,71 @@ $(document).ready(function () {
|
||||
ok(!l.editor.is_editing(), "should not be editing");
|
||||
ok(!edit_after, "should not have fired the edit:after event");
|
||||
return $.when();
|
||||
})
|
||||
.fail(function (e) { ok(false, e && e.message || e); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
openerp.testing.section('list.edition.onwrite', {
|
||||
dependencies: ['web.list_editable'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
}, function (test) {
|
||||
test('record-to-read', {asserts: 4}, function (instance, $fix, mock) {
|
||||
mock('/web/view/load', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"}
|
||||
},
|
||||
arch: {
|
||||
tag: 'tree',
|
||||
attrs: { on_write: 'on_write', colors: 'red:a == "foo"' },
|
||||
children: [
|
||||
{tag: 'field', attrs: {name: 'a'}}
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
mock('demo:read', function (args, kwargs) {
|
||||
if (_.isEmpty(args[0])) {
|
||||
return [];
|
||||
} else if (_.isEqual(args[0], [1])) {
|
||||
return [
|
||||
{id: 1, a: 'some value'}
|
||||
];
|
||||
} else if (_.isEqual(args[0], [42])) {
|
||||
return [ {id: 42, a: 'foo'} ];
|
||||
}
|
||||
throw new Error(JSON.stringify(_.toArray(arguments)));
|
||||
});
|
||||
mock('demo:default_get', function () { return {}; });
|
||||
mock('demo:create', function () { return 1; });
|
||||
mock('demo:on_write', function () { return [42]; });
|
||||
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, []);
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
return l.appendTo($fix)
|
||||
.then(l.proxy('reload_content'))
|
||||
.then(function () {
|
||||
return l.start_edition();
|
||||
})
|
||||
.then(function () {
|
||||
$fix.find('.oe_form_field input').val("some value").change();
|
||||
})
|
||||
.then(function () {
|
||||
return l.save_edition();
|
||||
})
|
||||
.then(function () {
|
||||
strictEqual(ds.ids.length, 2,
|
||||
'should have id of created + on_write');
|
||||
strictEqual(l.records.length, 2,
|
||||
'should have record of created + on_write');
|
||||
strictEqual(
|
||||
$fix.find('tbody tr:eq(1)').css('color'), 'rgb(255, 0, 0)',
|
||||
'shoud have color applied');
|
||||
strictEqual(
|
||||
$fix.find('tbody tr:eq(2)').css('color'), 'rgb(0, 0, 0)',
|
||||
'should have default color applied');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,45 +1,34 @@
|
||||
$(document).ready(function () {
|
||||
var openerp,
|
||||
create = function (o) {
|
||||
if (typeof Object.create === 'function') {
|
||||
return Object.create(o);
|
||||
}
|
||||
function Cls() {}
|
||||
Cls.prototype = o;
|
||||
return new Cls;
|
||||
};
|
||||
module('list-events', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.web.data(openerp);
|
||||
window.openerp.web.views(openerp);
|
||||
window.openerp.web.list(openerp);
|
||||
openerp.testing.section('list.events', {
|
||||
dependencies: ['web.list']
|
||||
}, function (test) {
|
||||
var create = function (o) {
|
||||
if (typeof Object.create === 'function') {
|
||||
return Object.create(o);
|
||||
}
|
||||
});
|
||||
test('Simple event triggering', function () {
|
||||
var e = create(openerp.web.list.Events), passed = false;
|
||||
function Cls() {}
|
||||
Cls.prototype = o;
|
||||
return new Cls;
|
||||
};
|
||||
test('Simple event triggering', function (instance) {
|
||||
var e = create(instance.web.list.Events), passed = false;
|
||||
e.bind('foo', function () { passed = true; });
|
||||
e.trigger('foo');
|
||||
ok(passed);
|
||||
});
|
||||
test('Bind all', function () {
|
||||
var e = create(openerp.web.list.Events), event = null;
|
||||
test('Bind all', function (instance) {
|
||||
var e = create(instance.web.list.Events), event = null;
|
||||
e.bind(null, function (ev) { event = ev; });
|
||||
e.trigger('foo');
|
||||
strictEqual(event, 'foo');
|
||||
});
|
||||
test('Propagate trigger params', function () {
|
||||
var e = create(openerp.web.list.Events), p = false;
|
||||
test('Propagate trigger params', function (instance) {
|
||||
var e = create(instance.web.list.Events), p = false;
|
||||
e.bind(null, function (_, param) { p = param });
|
||||
e.trigger('foo', true);
|
||||
strictEqual(p, true)
|
||||
});
|
||||
test('Bind multiple callbacks', function () {
|
||||
var e = create(openerp.web.list.Events), count;
|
||||
test('Bind multiple callbacks', function (instance) {
|
||||
var e = create(instance.web.list.Events), count;
|
||||
e.bind('foo', function () { count++; })
|
||||
.bind('bar', function () { count++; })
|
||||
.bind(null, function () { count++; })
|
||||
@@ -59,20 +48,20 @@ $(document).ready(function () {
|
||||
e.trigger('baz');
|
||||
strictEqual(count, 3);
|
||||
});
|
||||
test('Mixin events', function () {
|
||||
var cls = openerp.web.Class.extend({
|
||||
test('Mixin events', function (instance) {
|
||||
var cls = instance.web.Class.extend({
|
||||
method: function () { this.trigger('e'); }
|
||||
});
|
||||
cls.include(openerp.web.list.Events);
|
||||
var instance = new cls, triggered = false;
|
||||
cls.include(instance.web.list.Events);
|
||||
var i = new cls, triggered = false;
|
||||
|
||||
instance.bind('e', function () { triggered = true; });
|
||||
instance.method();
|
||||
i.bind('e', function () { triggered = true; });
|
||||
i.method();
|
||||
|
||||
ok(triggered);
|
||||
});
|
||||
test('Unbind all handlers', function () {
|
||||
var e = create(openerp.web.list.Events), passed = 0;
|
||||
test('Unbind all handlers', function (instance) {
|
||||
var e = create(instance.web.list.Events), passed = 0;
|
||||
e.bind('foo', function () { passed++; });
|
||||
e.trigger('foo');
|
||||
strictEqual(passed, 1);
|
||||
@@ -80,8 +69,8 @@ $(document).ready(function () {
|
||||
e.trigger('foo');
|
||||
strictEqual(passed, 1);
|
||||
});
|
||||
test('Unbind one handler', function () {
|
||||
var e = create(openerp.web.list.Events), p1 = 0, p2 = 0,
|
||||
test('Unbind one handler', function (instance) {
|
||||
var e = create(instance.web.list.Events), p1 = 0, p2 = 0,
|
||||
h1 = function () { p1++; }, h2 = function () { p2++; };
|
||||
e.bind('foo', h1);
|
||||
e.bind('foo', h2);
|
||||
@@ -93,29 +82,20 @@ $(document).ready(function () {
|
||||
strictEqual(p1, 1);
|
||||
strictEqual(p2, 2);
|
||||
});
|
||||
|
||||
module('list-records', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.web.data(openerp);
|
||||
window.openerp.web.views(openerp);
|
||||
window.openerp.web.list(openerp);
|
||||
}
|
||||
});
|
||||
test('Basic record initialization', function () {
|
||||
var r = new openerp.web.list.Record({qux: 3});
|
||||
});
|
||||
openerp.testing.section('list.records', {
|
||||
dependencies: ['web.list']
|
||||
}, function (test) {
|
||||
test('Basic record initialization', function (instance) {
|
||||
var r = new instance.web.list.Record({qux: 3});
|
||||
r.set('foo', 1);
|
||||
r.set('bar', 2);
|
||||
strictEqual(r.get('foo'), 1);
|
||||
strictEqual(r.get('bar'), 2);
|
||||
strictEqual(r.get('qux'), 3);
|
||||
});
|
||||
test('Change all the things', function () {
|
||||
var r = new openerp.web.list.Record(), changed = false, field;
|
||||
test('Change all the things', function (instance) {
|
||||
var r = new instance.web.list.Record(), changed = false, field;
|
||||
r.bind('change', function () { changed = true; });
|
||||
r.bind(null, function (e) { field = field || e.split(':')[1]});
|
||||
r.set('foo', 1);
|
||||
@@ -123,8 +103,8 @@ $(document).ready(function () {
|
||||
ok(changed);
|
||||
strictEqual(field, 'foo');
|
||||
});
|
||||
test('Change single field', function () {
|
||||
var r = new openerp.web.list.Record(), changed = 0;
|
||||
test('Change single field', function (instance) {
|
||||
var r = new instance.web.list.Record(), changed = 0;
|
||||
r.bind('change:foo', function () { changed++; });
|
||||
r.set('foo', 1);
|
||||
r.set('bar', 1);
|
||||
@@ -132,21 +112,12 @@ $(document).ready(function () {
|
||||
strictEqual(r.get('bar'), 1);
|
||||
strictEqual(changed, 1);
|
||||
});
|
||||
|
||||
module('list-collections', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.web.data(openerp);
|
||||
window.openerp.web.views(openerp);
|
||||
window.openerp.web.list(openerp);
|
||||
}
|
||||
});
|
||||
test('degenerate-fetch', function () {
|
||||
var c = new openerp.web.list.Collection();
|
||||
});
|
||||
openerp.testing.section('list.collections', {
|
||||
dependencies: ['web.list']
|
||||
}, function (test) {
|
||||
test('degenerate-fetch', function (instance) {
|
||||
var c = new instance.web.list.Collection();
|
||||
strictEqual(c.length, 0);
|
||||
c.add({id: 1, value: 2});
|
||||
c.add({id: 2, value: 3});
|
||||
@@ -155,16 +126,16 @@ $(document).ready(function () {
|
||||
strictEqual(c.length, 4);
|
||||
var r = c.at(2), r2 = c.get(1);
|
||||
|
||||
ok(r instanceof openerp.web.list.Record);
|
||||
ok(r instanceof instance.web.list.Record);
|
||||
strictEqual(r.get('id'), 3);
|
||||
strictEqual(r.get('value'), 5);
|
||||
|
||||
ok(r2 instanceof openerp.web.list.Record);
|
||||
ok(r2 instanceof instance.web.list.Record);
|
||||
strictEqual(r2.get('id'), 1);
|
||||
strictEqual(r2.get('value'), 2);
|
||||
});
|
||||
test('degenerate-indexed-add', function () {
|
||||
var c = new openerp.web.list.Collection([
|
||||
test('degenerate-indexed-add', function (instance) {
|
||||
var c = new instance.web.list.Collection([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
{id: 3, value: 20}
|
||||
@@ -175,8 +146,8 @@ $(document).ready(function () {
|
||||
strictEqual(c.at(1).get('value'), 55);
|
||||
strictEqual(c.at(3).get('value'), 20);
|
||||
});
|
||||
test('degenerate-remove', function () {
|
||||
var c = new openerp.web.list.Collection([
|
||||
test('degenerate-remove', function (instance) {
|
||||
var c = new instance.web.list.Collection([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
{id: 3, value: 20}
|
||||
@@ -188,9 +159,9 @@ $(document).ready(function () {
|
||||
equal(c.get(2), undefined);
|
||||
strictEqual(c.at(1).get('value'), 20);
|
||||
});
|
||||
test('degenerate-remove-bound', function () {
|
||||
test('degenerate-remove-bound', function (instance) {
|
||||
var changed = false,
|
||||
c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
|
||||
c = new instance.web.list.Collection([ {id: 1, value: 5} ]);
|
||||
c.bind('change', function () { changed = true; });
|
||||
var record = c.get(1);
|
||||
c.remove(record);
|
||||
@@ -198,8 +169,8 @@ $(document).ready(function () {
|
||||
ok(!changed, 'removed records should not trigger events in their ' +
|
||||
'parent collection');
|
||||
});
|
||||
test('degenerate-reset', function () {
|
||||
var event, obj, c = new openerp.web.list.Collection([
|
||||
test('degenerate-reset', function (instance) {
|
||||
var event, obj, c = new instance.web.list.Collection([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
{id: 3, value: 20}
|
||||
@@ -218,9 +189,9 @@ $(document).ready(function () {
|
||||
strictEqual(c.length, 1);
|
||||
strictEqual(c.get(42).get('value'), 55);
|
||||
});
|
||||
test('degenerate-reset-bound', function () {
|
||||
test('degenerate-reset-bound', function (instance) {
|
||||
var changed = false,
|
||||
c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
|
||||
c = new instance.web.list.Collection([ {id: 1, value: 5} ]);
|
||||
c.bind('change', function () { changed = true; });
|
||||
var record = c.get(1);
|
||||
c.reset();
|
||||
@@ -229,9 +200,9 @@ $(document).ready(function () {
|
||||
'parent collection');
|
||||
});
|
||||
|
||||
test('degenerate-propagations', function () {
|
||||
test('degenerate-propagations', function (instance) {
|
||||
var values = [];
|
||||
var c = new openerp.web.list.Collection([
|
||||
var c = new instance.web.list.Collection([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
{id: 3, value: 20}
|
||||
@@ -244,8 +215,8 @@ $(document).ready(function () {
|
||||
c.get(3).set('value', 21);
|
||||
deepEqual(values, [6, 11, 21]);
|
||||
});
|
||||
test('BTree', function () {
|
||||
var root = new openerp.web.list.Collection(),
|
||||
test('BTree', function (instance) {
|
||||
var root = new instance.web.list.Collection(),
|
||||
c = root.proxy('admin'),
|
||||
total = 0;
|
||||
c.add({id: 1, name: "Administrator", login: 'admin'});
|
||||
@@ -260,8 +231,8 @@ $(document).ready(function () {
|
||||
c.at(1).set('wealth', 5);
|
||||
strictEqual(total, 47);
|
||||
});
|
||||
test('degenerate-successor', function () {
|
||||
var root = new openerp.web.list.Collection([
|
||||
test('degenerate-successor', function (instance) {
|
||||
var root = new instance.web.list.Collection([
|
||||
{id: 1, value: 1},
|
||||
{id: 2, value: 2},
|
||||
{id: 3, value: 3},
|
||||
@@ -282,8 +253,8 @@ $(document).ready(function () {
|
||||
root.at(3).attributes,
|
||||
"wraparound should have no effect if not succ(last_record)");
|
||||
});
|
||||
test('successor', function () {
|
||||
var root = new openerp.web.list.Collection();
|
||||
test('successor', function (instance) {
|
||||
var root = new instance.web.list.Collection();
|
||||
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
|
||||
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
|
||||
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
|
||||
@@ -298,8 +269,8 @@ $(document).ready(function () {
|
||||
root.get(3).attributes,
|
||||
"should wraparound within a collection");
|
||||
});
|
||||
test('degenerate-predecessor', function () {
|
||||
var root = new openerp.web.list.Collection([
|
||||
test('degenerate-predecessor', function (instance) {
|
||||
var root = new instance.web.list.Collection([
|
||||
{id: 1, value: 1},
|
||||
{id: 2, value: 2},
|
||||
{id: 3, value: 3},
|
||||
@@ -320,8 +291,8 @@ $(document).ready(function () {
|
||||
root.at(0).attributes,
|
||||
"wraparound should have no effect if not pred(first_record)");
|
||||
});
|
||||
test('predecessor', function () {
|
||||
var root = new openerp.web.list.Collection();
|
||||
test('predecessor', function (instance) {
|
||||
var root = new instance.web.list.Collection();
|
||||
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
|
||||
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
|
||||
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
|
||||
@@ -336,21 +307,12 @@ $(document).ready(function () {
|
||||
root.get(4).attributes,
|
||||
"should wraparound within a collection");
|
||||
});
|
||||
|
||||
module('list-hofs', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.web.data(openerp);
|
||||
window.openerp.web.views(openerp);
|
||||
window.openerp.web.list(openerp);
|
||||
}
|
||||
});
|
||||
test('each, degenerate', function () {
|
||||
var c = new openerp.web.list.Collection([
|
||||
});
|
||||
openerp.testing.section('list.collections.hom', {
|
||||
dependencies: ['web.list']
|
||||
}, function (test) {
|
||||
test('each, degenerate', function (instance) {
|
||||
var c = new instance.web.list.Collection([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
{id: 3, value: 20}
|
||||
@@ -362,8 +324,8 @@ $(document).ready(function () {
|
||||
ids, [1, 2, 3],
|
||||
'degenerate collections should be iterated in record order');
|
||||
});
|
||||
test('each, deep', function () {
|
||||
var root = new openerp.web.list.Collection(),
|
||||
test('each, deep', function (instance) {
|
||||
var root = new instance.web.list.Collection(),
|
||||
ids = [];
|
||||
root.proxy('foo').add([
|
||||
{id: 1, value: 5},
|
||||
@@ -382,8 +344,8 @@ $(document).ready(function () {
|
||||
ids, [1, 2, 3, 10, 20, 30],
|
||||
'tree collections should be deeply iterated');
|
||||
});
|
||||
test('map, degenerate', function () {
|
||||
var c = new openerp.web.list.Collection([
|
||||
test('map, degenerate', function (instance) {
|
||||
var c = new instance.web.list.Collection([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
{id: 3, value: 20}
|
||||
@@ -395,8 +357,8 @@ $(document).ready(function () {
|
||||
ids, [1, 2, 3],
|
||||
'degenerate collections should be iterated in record order');
|
||||
});
|
||||
test('map, deep', function () {
|
||||
var root = new openerp.web.list.Collection();
|
||||
test('map, deep', function (instance) {
|
||||
var root = new instance.web.list.Collection();
|
||||
root.proxy('foo').add([
|
||||
{id: 1, value: 5},
|
||||
{id: 2, value: 10},
|
||||
@@ -414,29 +376,20 @@ $(document).ready(function () {
|
||||
ids, [1, 2, 3, 10, 20, 30],
|
||||
'tree collections should be deeply iterated');
|
||||
});
|
||||
|
||||
module("list-weirds", {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.web.data(openerp);
|
||||
window.openerp.web.views(openerp);
|
||||
window.openerp.web.list(openerp);
|
||||
}
|
||||
});
|
||||
test('set-from-noid', function () {
|
||||
var root = new openerp.web.list.Collection();
|
||||
});
|
||||
openerp.testing.section('list.collection.weirdoes', {
|
||||
dependencies: ['web.list']
|
||||
}, function (test) {
|
||||
test('set-from-noid', function (instance) {
|
||||
var root = new instance.web.list.Collection();
|
||||
root.add({v: 3});
|
||||
root.at(0).set('id', 42);
|
||||
var record = root.get(42);
|
||||
equal(root.length, 1);
|
||||
equal(record.get('v'), 3, "should have fetched the original record");
|
||||
});
|
||||
test('set-from-previd', function () {
|
||||
var root = new openerp.web.list.Collection();
|
||||
test('set-from-previd', function (instance) {
|
||||
var root = new instance.web.list.Collection();
|
||||
root.add({id: 1, v: 2});
|
||||
root.get(1).set('id', 42);
|
||||
var record = root.get(42);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
openerp.testing.section('list.buttons', {
|
||||
dependencies: ['web.list', 'web.form'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
}, function (test) {
|
||||
test('record-deletion', {asserts: 2}, function (instance, $fix, mock) {
|
||||
mock('/web/view/load', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"}
|
||||
},
|
||||
arch: {
|
||||
tag: 'tree',
|
||||
attrs: { },
|
||||
children: [
|
||||
{tag: 'field', attrs: {name: 'a'}},
|
||||
{tag: 'button', attrs: {type: 'object', name: 'foo'}}
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
mock('demo:read', function (args, kwargs) {
|
||||
if (_.isEqual(args[0], [1, 2, 3])) {
|
||||
return [
|
||||
{id: 1, a: 'foo'}, {id: 2, a: 'bar'}, {id: 3, a: 'baz'}
|
||||
];
|
||||
} else if (_.isEqual(args[0], [2])) {
|
||||
// button action virtually removed record
|
||||
return [];
|
||||
}
|
||||
throw new Error(JSON.stringify(_.toArray(arguments)));
|
||||
});
|
||||
mock('/web/dataset/call_button', function () { return false; });
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1, 2, 3]);
|
||||
var l = new instance.web.ListView({
|
||||
do_action: openerp.testing.noop
|
||||
}, ds, false, {editable: 'top'});
|
||||
return l.appendTo($fix)
|
||||
.then(l.proxy('reload_content'))
|
||||
.then(function () {
|
||||
var d = $.Deferred();
|
||||
l.records.bind('remove', function () {
|
||||
d.resolve();
|
||||
});
|
||||
$fix.find('table tbody tr:eq(1) button').click();
|
||||
return d.promise();
|
||||
})
|
||||
.then(function () {
|
||||
strictEqual(l.records.length, 2,
|
||||
"should have 2 records left");
|
||||
strictEqual($fix.find('table tbody tr[data-id]').length, 2,
|
||||
"should have 2 rows left");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
openerp.testing.section('mutex', {
|
||||
dependencies: ['web.coresetup'],
|
||||
setup: function (instance) {
|
||||
}
|
||||
}, function (test) {
|
||||
test('simpleScheduling', function (instance) {
|
||||
var m = new $.Mutex();
|
||||
var def1 = $.Deferred();
|
||||
var def2 = $.Deferred();
|
||||
var p1 = m.exec(function() { return def1; });
|
||||
var p2 = m.exec(function() { return def2; });
|
||||
equal(p1.state(), "pending");
|
||||
equal(p2.state(), "pending");
|
||||
def1.resolve();
|
||||
equal(p1.state(), "resolved");
|
||||
equal(p2.state(), "pending");
|
||||
def2.resolve();
|
||||
equal(p1.state(), "resolved");
|
||||
equal(p2.state(), "resolved");
|
||||
});
|
||||
test('simpleScheduling2', function (instance) {
|
||||
var m = new $.Mutex();
|
||||
var def1 = $.Deferred();
|
||||
var def2 = $.Deferred();
|
||||
var p1 = m.exec(function() { return def1; });
|
||||
var p2 = m.exec(function() { return def2; });
|
||||
equal(p1.state(), "pending");
|
||||
equal(p2.state(), "pending");
|
||||
def2.resolve();
|
||||
equal(p1.state(), "pending");
|
||||
equal(p2.state(), "pending");
|
||||
def1.resolve();
|
||||
equal(p1.state(), "resolved");
|
||||
equal(p2.state(), "resolved");
|
||||
});
|
||||
test('reject', function (instance) {
|
||||
var m = new $.Mutex();
|
||||
var def1 = $.Deferred();
|
||||
var def2 = $.Deferred();
|
||||
var def3 = $.Deferred();
|
||||
var p1 = m.exec(function() {return def1;});
|
||||
var p2 = m.exec(function() {return def2;});
|
||||
var p3 = m.exec(function() {return def3;});
|
||||
equal(p1.state(), "pending");
|
||||
equal(p2.state(), "pending");
|
||||
equal(p3.state(), "pending");
|
||||
def1.resolve();
|
||||
equal(p1.state(), "resolved");
|
||||
equal(p2.state(), "pending");
|
||||
equal(p3.state(), "pending");
|
||||
def2.reject();
|
||||
equal(p1.state(), "resolved");
|
||||
equal(p2.state(), "rejected");
|
||||
equal(p3.state(), "pending");
|
||||
def3.resolve();
|
||||
equal(p1.state(), "resolved");
|
||||
equal(p2.state(), "rejected");
|
||||
equal(p3.state(), "resolved");
|
||||
});
|
||||
});
|
||||
@@ -1,58 +1,55 @@
|
||||
$(document).ready(function () {
|
||||
var openerp;
|
||||
module('Registry', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
openerp.web.Foo = {};
|
||||
openerp.web.Bar = {};
|
||||
openerp.web.Foo2 = {};
|
||||
}
|
||||
});
|
||||
test('key set', function () {
|
||||
var reg = new openerp.web.Registry();
|
||||
openerp.testing.section('registry', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.Foo = {};
|
||||
instance.web.Bar = {};
|
||||
instance.web.Foo2 = {};
|
||||
}
|
||||
}, function (test) {
|
||||
test('key set', function (instance) {
|
||||
var reg = new instance.web.Registry();
|
||||
|
||||
reg.add('foo', 'openerp.web.Foo')
|
||||
.add('bar', 'openerp.web.Bar');
|
||||
strictEqual(reg.get_object('bar'), openerp.web.Bar);
|
||||
reg.add('foo', 'instance.web.Foo')
|
||||
.add('bar', 'instance.web.Bar');
|
||||
strictEqual(reg.get_object('bar'), instance.web.Bar);
|
||||
});
|
||||
test('extension', function () {
|
||||
var reg = new openerp.web.Registry({
|
||||
foo: 'openerp.web.Foo',
|
||||
bar: 'openerp.web.Bar'
|
||||
test('extension', function (instance) {
|
||||
var reg = new instance.web.Registry({
|
||||
foo: 'instance.web.Foo',
|
||||
bar: 'instance.web.Bar'
|
||||
});
|
||||
|
||||
var reg2 = reg.extend({ 'foo': 'openerp.web.Foo2' });
|
||||
strictEqual(reg.get_object('foo'), openerp.web.Foo);
|
||||
strictEqual(reg2.get_object('foo'), openerp.web.Foo2);
|
||||
var reg2 = reg.extend({ 'foo': 'instance.web.Foo2' });
|
||||
strictEqual(reg.get_object('foo'), instance.web.Foo);
|
||||
strictEqual(reg2.get_object('foo'), instance.web.Foo2);
|
||||
});
|
||||
test('remain-linked', function () {
|
||||
var reg = new openerp.web.Registry({
|
||||
foo: 'openerp.web.Foo',
|
||||
bar: 'openerp.web.Bar'
|
||||
test('remain-linked', function (instance) {
|
||||
var reg = new instance.web.Registry({
|
||||
foo: 'instance.web.Foo',
|
||||
bar: 'instance.web.Bar'
|
||||
});
|
||||
|
||||
var reg2 = reg.extend();
|
||||
reg.add('foo2', 'openerp.web.Foo2');
|
||||
strictEqual(reg.get_object('foo2'), openerp.web.Foo2);
|
||||
strictEqual(reg2.get_object('foo2'), openerp.web.Foo2);
|
||||
reg.add('foo2', 'instance.web.Foo2');
|
||||
strictEqual(reg.get_object('foo2'), instance.web.Foo2);
|
||||
strictEqual(reg2.get_object('foo2'), instance.web.Foo2);
|
||||
});
|
||||
test('multiget', function () {
|
||||
var reg = new openerp.web.Registry({
|
||||
foo: 'openerp.web.Foo',
|
||||
bar: 'openerp.web.Bar'
|
||||
test('multiget', function (instance) {
|
||||
var reg = new instance.web.Registry({
|
||||
foo: 'instance.web.Foo',
|
||||
bar: 'instance.web.Bar'
|
||||
});
|
||||
|
||||
strictEqual(reg.get_any(['qux', 'grault', 'bar', 'foo']),
|
||||
openerp.web.Bar);
|
||||
instance.web.Bar);
|
||||
});
|
||||
test('extended-multiget', function () {
|
||||
var reg = new openerp.web.Registry({
|
||||
foo: 'openerp.web.Foo',
|
||||
bar: 'openerp.web.Bar'
|
||||
test('extended-multiget', function (instance) {
|
||||
var reg = new instance.web.Registry({
|
||||
foo: 'instance.web.Foo',
|
||||
bar: 'instance.web.Bar'
|
||||
});
|
||||
var reg2 = reg.extend();
|
||||
strictEqual(reg2.get_any(['qux', 'grault', 'bar', 'foo']),
|
||||
openerp.web.Bar);
|
||||
instance.web.Bar);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
$(document).ready(function () {
|
||||
var openerp;
|
||||
|
||||
module('Misordered resolution management', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init([]);
|
||||
window.openerp.web.corelib(openerp);
|
||||
window.openerp.web.coresetup(openerp);
|
||||
window.openerp.web.data(openerp);
|
||||
}
|
||||
});
|
||||
test('Resolve all correctly ordered, sync', function () {
|
||||
var dm = new openerp.web.DropMisordered(), flag = false;
|
||||
openerp.testing.section('misordered resolution managemeng', {
|
||||
dependencies: ['web.data']
|
||||
}, function (test) {
|
||||
test('Resolve all correctly ordered, sync', function (instance) {
|
||||
var dm = new instance.web.DropMisordered(), flag = false;
|
||||
|
||||
var d1 = $.Deferred(), d2 = $.Deferred(),
|
||||
r1 = dm.add(d1), r2 = dm.add(d2);
|
||||
@@ -23,8 +15,8 @@ $(document).ready(function () {
|
||||
|
||||
ok(flag);
|
||||
});
|
||||
test("Don't resolve mis-ordered, sync", function () {
|
||||
var dm = new openerp.web.DropMisordered(),
|
||||
test("Don't resolve mis-ordered, sync", function (instance) {
|
||||
var dm = new instance.web.DropMisordered(),
|
||||
done1 = false, done2 = false,
|
||||
fail1 = false, fail2 = false;
|
||||
|
||||
@@ -44,8 +36,8 @@ $(document).ready(function () {
|
||||
ok(done2);
|
||||
ok(!fail2);
|
||||
});
|
||||
test('Fail mis-ordered flag, sync', function () {
|
||||
var dm = new openerp.web.DropMisordered(true),
|
||||
test('Fail mis-ordered flag, sync', function (instance) {
|
||||
var dm = new instance.web.DropMisordered(true),
|
||||
done1 = false, done2 = false,
|
||||
fail1 = false, fail2 = false;
|
||||
|
||||
@@ -66,8 +58,8 @@ $(document).ready(function () {
|
||||
ok(!fail2);
|
||||
});
|
||||
|
||||
asyncTest('Resolve all correctly ordered, async', 1, function () {
|
||||
var dm = new openerp.web.DropMisordered();
|
||||
test('Resolve all correctly ordered, async', {asserts: 1}, function (instance) {
|
||||
var dm = new instance.web.DropMisordered();
|
||||
|
||||
var d1 = $.Deferred(), d2 = $.Deferred(),
|
||||
r1 = dm.add(d1), r2 = dm.add(d2);
|
||||
@@ -75,13 +67,12 @@ $(document).ready(function () {
|
||||
setTimeout(function () { d1.resolve(); }, 100);
|
||||
setTimeout(function () { d2.resolve(); }, 200);
|
||||
|
||||
$.when(r1, r2).done(function () {
|
||||
start();
|
||||
return $.when(r1, r2).done(function () {
|
||||
ok(true);
|
||||
});
|
||||
});
|
||||
asyncTest("Don't resolve mis-ordered, async", 4, function () {
|
||||
var dm = new openerp.web.DropMisordered(),
|
||||
test("Don't resolve mis-ordered, async", {asserts: 4}, function (instance) {
|
||||
var dm = new instance.web.DropMisordered(),
|
||||
done1 = false, done2 = false,
|
||||
fail1 = false, fail2 = false;
|
||||
|
||||
@@ -94,18 +85,20 @@ $(document).ready(function () {
|
||||
setTimeout(function () { d1.resolve(); }, 200);
|
||||
setTimeout(function () { d2.resolve(); }, 100);
|
||||
|
||||
var done = $.Deferred();
|
||||
setTimeout(function () {
|
||||
start();
|
||||
// d1 is in limbo
|
||||
ok(!done1);
|
||||
ok(!fail1);
|
||||
// d2 is resolved
|
||||
ok(done2);
|
||||
ok(!fail2);
|
||||
done.resolve();
|
||||
}, 400);
|
||||
return $.when(d1, d2, done);
|
||||
});
|
||||
asyncTest('Fail mis-ordered flag, async', 4, function () {
|
||||
var dm = new openerp.web.DropMisordered(true),
|
||||
test('Fail mis-ordered flag, async', {asserts: 4}, function (instance) {
|
||||
var dm = new instance.web.DropMisordered(true),
|
||||
done1 = false, done2 = false,
|
||||
fail1 = false, fail2 = false;
|
||||
|
||||
@@ -118,6 +111,7 @@ $(document).ready(function () {
|
||||
setTimeout(function () { d1.resolve(); }, 200);
|
||||
setTimeout(function () { d2.resolve(); }, 100);
|
||||
|
||||
var done = $.Deferred();
|
||||
setTimeout(function () {
|
||||
start();
|
||||
// d1 is failed
|
||||
@@ -126,6 +120,8 @@ $(document).ready(function () {
|
||||
// d2 is resolved
|
||||
ok(done2);
|
||||
ok(!fail2);
|
||||
done.resolve();
|
||||
}, 400);
|
||||
return $.when(d1, d2, done)
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user