[MERGE] Merge trunk.

bzr revid: jra@tinyerp.com-20121119053320-to7uhn8mk3xpvq8r
This commit is contained in:
Jiten (OpenERP) 2012-11-19 11:03:20 +05:30
commit df184ad1f9
327 changed files with 4780 additions and 2478 deletions

View File

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

View File

@ -1,6 +1,7 @@
{
'name': 'Web',
'category': 'Hidden',
'version': '7.0.1.0',
'description':
"""
OpenERP Web core module.
@ -40,6 +41,7 @@ This module provides the core of the OpenERP Web Client.
"static/lib/cleditor/jquery.cleditor.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,5 +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,
}

View File

@ -1 +1,2 @@
from . import main
from . import testing

View File

@ -258,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:
@ -278,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):
@ -637,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,
@ -691,12 +700,10 @@ 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:
@ -770,15 +777,31 @@ class Database(openerpweb.Controller):
@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):
@ -886,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"}
@ -1455,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))
@ -1949,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:

View File

@ -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">&lt;&lt; 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, '/')

View File

@ -1,9 +1,6 @@
Asynchronous Operations
=======================
This documentation is outdated as then() is now pipe. Please never copy api
from other libraries just link to it.
As a language (and runtime), javascript is fundamentally
single-threaded. This means any blocking request or computation will
blocks the whole page (and, in older browsers, the software itself
@ -46,8 +43,8 @@ directly to asynchronous methods is the ability to :ref:`compose them
Using deferreds
~~~~~~~~~~~~~~~
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
@ -179,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
@ -196,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
@ -218,7 +208,7 @@ callback is called,
.. code-block:: javascript
promise.pipe(function () {
promise.then(function () {
console.log('called');
});
@ -235,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);
});
@ -244,12 +234,109 @@ 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
~~~~~~~~~~~~~~~~~~~
.. js:function:: when(deferreds…)
:param deferreds: deferred objects to multiplex
:returns: a multiplexed deferred
:rtype: :js:class:`Deferred`
.. js:class:: Deferred
.. js:function:: Deferred.then(doneCallback[, failCallback])
Attaches new callbacks to the resolution or rejection of the
deferred object. Callbacks are executed in the order they are
attached to the deferred.
To provide only a failure callback, pass ``null`` as the
``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
:type failCallback: Function
:returns: the deferred object on which it was called
:rtype: :js:class:`Deferred`
.. js:function:: Deferred.done(doneCallback)
Attaches a new success callback to the deferred, shortcut for
``deferred.then(doneCallback)``.
This is a jQuery extension to `CommonJS Promises/A`_ providing
little value over calling :js:func:`~Deferred.then` directly,
it should be avoided.
:param doneCallback: function called when the deferred is resolved
:type doneCallback: Function
:returns: the deferred object on which it was called
:rtype: :js:class:`Deferred`
.. js:function:: Deferred.fail(failCallback)
Attaches a new failure callback to the deferred, shortcut for
``deferred.then(null, failCallback)``.
A second jQuery extension to `Promises/A <CommonJS
Promises/A>`_. Although it provides more value than
:js:func:`~Deferred.done`, it still is not much and should be
avoided as well.
:param failCallback: function called when the deferred is rejected
:type failCallback: Function
:returns: the deferred object on which it was called
:rtype: :js:class:`Deferred`
.. js:function:: Deferred.promise()
Returns a read-only view of the deferred object, with all
mutators (resolve and reject) methods removed.
.. js:function:: Deferred.resolve(value…)
Called to resolve a deferred, any value provided will be
passed onto the success handlers of the deferred object.
Resolving a deferred which has already been resolved or
rejected has no effect.
.. js:function:: Deferred.reject(value…)
Called to reject (fail) a deferred, any value provided will be
passed onto the failure handler of the deferred object.
Rejecting a deferred which has already been resolved or
rejected has no effect.
.. [#] or simply calling :js:class:`Deferred` as a function, the
result is the same
.. [#] or not-promises, the `CommonJS Promises/B`_ role of
:js:func:`when` is to be able to treat values and promises
uniformly: :js:func:`when` will pass promises through directly,
but non-promise values and objects will be transformed into a
resolved promise (resolving themselves with the value itself).
jQuery's :js:func:`when` keeps this behavior making deferreds
easy to build from "static" values, or allowing defensive code
where expected promises are wrapped in :js:func:`when` just in
case.
.. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
.. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
.. _CommonJS Promises/A: http://wiki.commonjs.org/wiki/Promises/A

View File

@ -15,7 +15,7 @@ DataSet -> Model
----------------
The 6.1 ``DataSet`` API has been deprecated in favor of the smaller
and more orthogonal :doc:`Model </dev_rpc>` API, which more closely
and more orthogonal :doc:`Model </rpc>` API, which more closely
matches the API in OpenERP Web's Python side and in OpenObject addons
and removes most stateful behavior of DataSet.

View File

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

View File

@ -14,18 +14,18 @@ Contents:
presentation
async
dev_rpc
dev_widget
dev_qweb
dev_client_action
testing
internal_form
internal_list
internal_search
widget
qweb
rpc
client_action
form_view
search_view
list_view
changelog-7.0
Indices and tables
==================

View File

@ -451,7 +451,7 @@ formview, delegating instead to its
created.
The result should be a valid form view, see :doc:`Form Notes
<internal_form>` 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

View File

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

693
addons/web/doc/testing.rst Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(), {

View File

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

View File

@ -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;
}
@ -1153,6 +1146,7 @@
height: 40px;
width: 157px;
margin: 14px 0;
border: 0;
}
.openerp .oe_footer {
position: fixed;
@ -2323,16 +2317,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;
@ -2378,6 +2372,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;
}
@ -2391,6 +2456,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;
@ -2595,32 +2663,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;
}
@ -2685,6 +2754,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;
@ -2752,6 +2824,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;
@ -2899,78 +2975,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;
@ -3081,8 +3085,8 @@ div.res {
.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;

View File

@ -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
@ -943,6 +938,7 @@ $sheet-padding: 16px
height: 40px
width: 157px
margin: 14px 0
border: 0
.oe_footer
position: fixed
bottom: 0
@ -1852,13 +1848,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
@ -1901,6 +1897,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
@ -1914,6 +1968,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
@ -2056,26 +2112,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
// }}}
@ -2134,6 +2190,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
@ -2183,6 +2241,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)
@ -2297,67 +2357,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)
@ -2367,11 +2366,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
@ -2398,6 +2399,7 @@ div.ui-widget-overlay
.ui-corner-all
@include radius(3px)
// }}}
.resolution
width: 350px
@ -2443,11 +2445,11 @@ div.res
font-size: 10px
padding-right: 5px
@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
@ -2473,5 +2475,7 @@ div.res
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:

View File

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

View File

@ -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;
};
@ -190,7 +190,14 @@ instance.web.Dialog = instance.web.Widget.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.Class.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.Class.extend({
});
},
show_error: function(error) {
var self = this;
if (!this.active) {
return;
}
var buttons = {};
buttons[_t("Ok")] = function() {
$(this).dialog("close");
@ -410,7 +422,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
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);
},
@ -433,6 +445,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 });
@ -518,6 +531,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),
@ -618,6 +643,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);
@ -719,9 +747,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 || {};
@ -737,8 +791,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");
@ -748,7 +802,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");
@ -756,11 +810,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({
@ -987,7 +1040,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);
});
};
@ -999,7 +1052,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);
});
@ -1030,6 +1083,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();
});
@ -1264,10 +1318,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 {

View File

@ -774,10 +774,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, {
}
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() {
@ -1231,12 +1231,14 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
*
* @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 };
@ -1251,10 +1253,12 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
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);
@ -1268,7 +1272,8 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
}
).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,
@ -1276,7 +1281,7 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
};
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()) {
@ -1309,9 +1314,18 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
// 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',
@ -1326,7 +1340,7 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
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');
@ -1364,11 +1378,20 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
});
// 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;
},
});

View File

@ -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
@ -198,7 +201,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
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'
}));
@ -207,11 +210,11 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
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;
@ -457,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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -174,10 +174,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);
}
@ -634,6 +638,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();
@ -817,6 +823,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);
});
},
@ -1203,14 +1211,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();
@ -1879,6 +1909,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, '');
@ -2168,17 +2199,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();
@ -2402,7 +2426,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();
@ -3118,7 +3142,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
@ -3829,26 +3853,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({
@ -4011,13 +4022,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_ || [];
@ -4806,15 +4822,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;
@ -4862,6 +4869,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({
@ -4872,7 +4880,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;
@ -4931,7 +4939,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);
},
@ -4951,12 +4959,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;
}
@ -4995,14 +5007,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');
@ -5012,21 +5024,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();
@ -5042,7 +5118,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();
}
@ -5076,7 +5152,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();
}
@ -5106,7 +5182,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]);
}
}
@ -5268,7 +5344,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',
});

View File

@ -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});
});
@ -2151,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"' : '');
}
});
@ -2163,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),
});
}
});

View File

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

View File

@ -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();

View File

@ -189,6 +189,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;">
@ -316,6 +342,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>
@ -1219,26 +1246,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&lt;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&gt;=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&gt;=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&lt;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&gt;=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&gt;=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>
@ -1549,17 +1578,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">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More