[MERGE] from trunk

bzr revid: xmo@openerp.com-20120322143331-iajlifomw1hohbht
This commit is contained in:
Xavier Morel 2012-03-22 15:33:31 +01:00
commit 7353e02e2e
333 changed files with 34045 additions and 27405 deletions

View File

@ -1,23 +1,12 @@
*.pyc
.*.swp
.bzrignore
openerp/addons/*
openerp/filestore*
.Python
include
lib
bin/activate
bin/activate_this.py
bin/easy_install
bin/easy_install-2.6
bin/pip
bin/python
bin/python2.6
*.pyc
*.pyo
.*
*.egg-info
*.orig
*.vim
build/
bin/yolk
bin/pil*.py
.project
.pydevproject
.settings
RE:^bin/
RE:^dist/
RE:^include/
RE:^share/
RE:^man/
RE:^lib/

View File

@ -7,7 +7,7 @@
This module provides the core of the OpenERP web client.
""",
"depends" : [],
'active': True,
'auto_install': True,
'post_load' : 'wsgi_postload',
'js' : [
"static/lib/datejs/globalization/en-US.js",
@ -15,7 +15,8 @@
"static/lib/datejs/parser.js",
"static/lib/datejs/sugarpak.js",
"static/lib/datejs/extras.js",
"static/lib/jquery/jquery-1.6.4.js",
#"static/lib/jquery/jquery-1.6.4.js",
"static/lib/jquery/jquery-1.7.2b1.js",
"static/lib/jquery.MD5/jquery.md5.js",
"static/lib/jquery.form/jquery.form.js",
"static/lib/jquery.validate/jquery.validate.js",
@ -35,7 +36,8 @@
"static/lib/underscore/underscore.js",
"static/lib/underscore/underscore.string.js",
"static/lib/labjs/LAB.src.js",
"static/lib/py.parse/lib/py.js",
"static/lib/py.js/lib/py.js",
"static/lib/novajs/src/nova.js",
"static/src/js/boot.js",
"static/src/js/core.js",
"static/src/js/dates.js",
@ -59,6 +61,7 @@
"static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/lib/jquery.tipsy/tipsy.css",
"static/src/css/base_old.css",
"static/src/css/base.css",
"static/src/css/data_export.css",
"static/src/css/data_import.css",

View File

@ -98,19 +98,72 @@ html_template = """<!DOCTYPE html>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>OpenERP</title>
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="/web/static/src/css/full.css" />
%(css)s
%(js)s
<script type="text/javascript">
$(function() {
var s = new openerp.init(%(modules)s);
%(init)s
var wc = new s.web.WebClient();
wc.appendTo($(document.body));
});
</script>
</head>
<body class="openerp" id="oe"></body>
<body></body>
</html>
"""
def sass2scss(src):
# Validated by diff -u of sass2scss against:
# sass-convert -F sass -T scss openerp.sass openerp.scss
block = []
sass = ('', block)
reComment = re.compile(r'//.*$')
reIndent = re.compile(r'^\s+')
reIgnore = re.compile(r'^\s*(//.*)?$')
reFixes = { re.compile(r'\(\((.*)\)\)') : r'(\1)', }
lastLevel = 0
prevBlocks = {}
for l in src.split('\n'):
l = l.rstrip()
if reIgnore.search(l): continue
l = reComment.sub('', l)
l = l.rstrip()
indent = reIndent.match(l)
level = indent.end() if indent else 0
l = l[level:]
if level>lastLevel:
prevBlocks[lastLevel] = block
newBlock = []
block[-1] = (block[-1], newBlock)
block = newBlock
elif level<lastLevel:
block = prevBlocks[level]
lastLevel = level
if not l: continue
# Fixes
for ereg, repl in reFixes.items():
l = ereg.sub(repl if type(repl)==str else repl(), l)
block.append(l)
def write(sass, level=-1):
out = ""
indent = ' '*level
if type(sass)==tuple:
if level>=0:
out += indent+sass[0]+" {\n"
for e in sass[1]:
out += write(e, level+1)
if level>=0:
out = out.rstrip(" \n")
out += ' }\n'
if level==0:
out += "\n"
else:
out += indent+sass+";\n"
return out
return write(sass)
class WebClient(openerpweb.Controller):
_cp_path = "/web/webclient"
@ -270,7 +323,6 @@ class WebClient(openerpweb.Controller):
'js': js,
'css': css,
'modules': simplejson.dumps(self.server_wide_modules(req)),
'init': 'new s.web.WebClient().start();',
}
return r
@ -351,19 +403,14 @@ class Database(openerpweb.Controller):
def get_list(self, req):
proxy = req.session.proxy("db")
dbs = proxy.list()
h = req.httprequest.headers['Host'].split(':')[0]
h = req.httprequest.environ['HTTP_HOST'].split(':')[0]
d = h.split('.')[0]
r = req.config.dbfilter.replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)]
return {"db_list": dbs}
@openerpweb.jsonrequest
def progress(self, req, password, id):
return req.session.proxy('db').get_progress(password, id)
@openerpweb.jsonrequest
def create(self, req, fields):
params = dict(map(operator.itemgetter('name', 'value'), fields))
create_attrs = (
params['super_admin_pwd'],
@ -373,17 +420,7 @@ class Database(openerpweb.Controller):
params['create_admin_pwd']
)
try:
return req.session.proxy("db").create(*create_attrs)
except xmlrpclib.Fault, e:
if e.faultCode and isinstance(e.faultCode, str)\
and e.faultCode.split(':')[0] == 'AccessDenied':
return {'error': e.faultCode, 'title': 'Database creation error'}
return {
'error': "Could not create database '%s': %s" % (
params['db_name'], e.faultString),
'title': 'Database creation error'
}
return req.session.proxy("db").create_database(*create_attrs)
@openerpweb.jsonrequest
def drop(self, req, fields):
@ -435,6 +472,50 @@ class Database(openerpweb.Controller):
return {'error': e.faultCode, 'title': 'Change Password'}
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
def topological_sort(modules):
""" Return a list of module names sorted so that their dependencies of the
modules are listed before the module itself
modules is a dict of {module_name: dependencies}
:param modules: modules to sort
:type modules: dict
:returns: list(str)
"""
dependencies = set(itertools.chain.from_iterable(modules.itervalues()))
# incoming edge: dependency on other module (if a depends on b, a has an
# incoming edge from b, aka there's an edge from b to a)
# outgoing edge: other module depending on this one
# [Tarjan 1976], http://en.wikipedia.org/wiki/Topological_sorting#Algorithms
#L ← Empty list that will contain the sorted nodes
L = []
#S ← Set of all nodes with no outgoing edges (modules on which no other
# module depends)
S = set(module for module in modules if module not in dependencies)
visited = set()
#function visit(node n)
def visit(n):
#if n has not been visited yet then
if n not in visited:
#mark n as visited
visited.add(n)
#change: n not web module, can not be resolved, ignore
if n not in modules: return
#for each node m with an edge from m to n do (dependencies of n)
for m in modules[n]:
#visit(m)
visit(m)
#add n to L
L.append(n)
#for each node n in S do
for n in S:
#visit(n)
visit(n)
return L
class Session(openerpweb.Controller):
_cp_path = "/web/session"
@ -502,20 +583,32 @@ class Session(openerpweb.Controller):
def modules(self, req):
# Compute available candidates module
loadable = openerpweb.addons_manifest
loaded = req.config.server_wide_modules
loaded = set(req.config.server_wide_modules)
candidates = [mod for mod in loadable if mod not in loaded]
# Compute active true modules that might be on the web side only
active = set(name for name in candidates
if openerpweb.addons_manifest[name].get('active'))
# already installed modules have no dependencies
modules = dict.fromkeys(loaded, [])
# Compute auto_install modules that might be on the web side only
modules.update((name, openerpweb.addons_manifest[name].get('depends', []))
for name in candidates
if openerpweb.addons_manifest[name].get('auto_install'))
# Retrieve database installed modules
Modules = req.session.model('ir.module.module')
installed = set(module['name'] for module in Modules.search_read(
[('state','=','installed'), ('name','in', candidates)], ['name']))
for module in Modules.search_read(
[('state','=','installed'), ('name','in', candidates)],
['name', 'dependencies_id']):
deps = module.get('dependencies_id')
if deps:
dependencies = map(
operator.itemgetter('name'),
req.session.model('ir.module.module.dependency').read(deps, ['name']))
modules[module['name']] = list(
set(modules.get(module['name'], []) + dependencies))
# Merge both
return list(active | installed)
sorted_modules = topological_sort(modules)
return [module for module in sorted_modules if module not in loaded]
@openerpweb.jsonrequest
def eval_domain_and_context(self, req, contexts, domains,
@ -762,11 +855,13 @@ class Menu(openerpweb.Controller):
Menus = s.model('ir.ui.menu')
# If a menu action is defined use its domain to get the root menu items
user_menu_id = s.model('res.users').read([s._uid], ['menu_id'], context)[0]['menu_id']
menu_domain = [('parent_id', '=', False)]
if user_menu_id:
menu_domain = s.model('ir.actions.act_window').read([user_menu_id[0]], ['domain'], context)[0]['domain']
menu_domain = ast.literal_eval(menu_domain)
else:
menu_domain = [('parent_id', '=', False)]
domain_string = s.model('ir.actions.act_window').read([user_menu_id[0]], ['domain'], context)[0]['domain']
if domain_string:
menu_domain = ast.literal_eval(domain_string)
return Menus.search(menu_domain, 0, False, False, context)
def do_load(self, req):
@ -780,13 +875,13 @@ class Menu(openerpweb.Controller):
context = req.session.eval_context(req.context)
Menus = req.session.model('ir.ui.menu')
menu_roots = Menus.read(self.do_get_user_roots(req), ['name', 'sequence', 'parent_id'], context)
menu_roots = Menus.read(self.do_get_user_roots(req), ['name', 'sequence', 'parent_id', 'action'], context)
menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, ''], 'children' : menu_roots}
# menus are loaded fully unlike a regular tree view, cause there are a
# limited number of items (752 when all 6.1 addons are installed)
menu_ids = Menus.search([], 0, False, False, context)
menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id', 'action'], context)
# adds roots at the end of the sequence, so that they will overwrite
# equivalent menu items from full menu read when put into id:item
# mapping, resulting in children being correctly set on the roots.
@ -1179,10 +1274,15 @@ class SearchView(View):
filters = Model.get_filters(model)
for filter in filters:
try:
filter["context"] = req.session.eval_context(
parse_context(filter["context"], req.session))
filter["domain"] = req.session.eval_domain(
parse_domain(filter["domain"], req.session))
parsed_context = parse_context(filter["context"], req.session)
filter["context"] = (parsed_context
if not isinstance(parsed_context, common.nonliterals.BaseContext)
else req.session.eval_context(parsed_context))
parsed_domain = parse_domain(filter["domain"], req.session)
filter["domain"] = (parsed_domain
if not isinstance(parsed_domain, common.nonliterals.BaseDomain)
else req.session.eval_domain(parsed_domain))
except Exception:
logger.exception("Failed to parse custom filter %s in %s",
filter['name'], model)
@ -1215,6 +1315,7 @@ class SearchView(View):
ctx = common.nonliterals.CompoundContext(context_to_save)
ctx.session = req.session
ctx = ctx.evaluate()
ctx['dashboard_merge_domains_contexts'] = False # TODO: replace this 6.1 workaround by attribute on <action/>
domain = common.nonliterals.CompoundDomain(domain)
domain.session = req.session
domain = domain.evaluate()
@ -1230,7 +1331,7 @@ class SearchView(View):
if board and 'arch' in board:
xml = ElementTree.fromstring(board['arch'])
column = xml.find('./board/column')
if column:
if column is not None:
new_action = ElementTree.Element('action', {
'name' : str(action_id),
'string' : name,

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-15 07:48+0000\n"
"Last-Translator: Jorge L Tupac-Yupanqui <Unknown>\n"
"PO-Revision-Date: 2012-02-16 11:00+0000\n"
"Last-Translator: Vicente <jviares@gmail.com>\n"
"Language-Team: Spanish <es@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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -657,7 +657,7 @@ msgstr "Traducciones"
#. openerp-web
#: addons/web/static/src/xml/base.xml:44 addons/web/static/src/xml/base.xml:315
msgid "Powered by"
msgstr "Desarrollado por"
msgstr "Con tecnoloxía de"
#. openerp-web
#: addons/web/static/src/xml/base.xml:44 addons/web/static/src/xml/base.xml:315
@ -1118,7 +1118,7 @@ msgstr "Limpiar"
#: addons/web/static/src/xml/base.xml:1172
#: addons/web/static/src/xml/base.xml:1223
msgid "Uploading ..."
msgstr "Subiendo ..."
msgstr "Cargando..."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1200

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-10 20:39+0000\n"
"PO-Revision-Date: 2012-02-16 19:06+0000\n"
"Last-Translator: Freddy Gonzalez <freddy.gonzalez@clearcorp.co.cr>\n"
"Language-Team: Spanish <es@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
"Language: es\n"
#. openerp-web
@ -194,7 +194,7 @@ msgstr "Descargar \"%s\""
#. openerp-web
#: addons/web/static/src/js/search.js:191
msgid "Filter disabled due to invalid syntax"
msgstr ""
msgstr "Filtro desactivado debido a sintaxis inválida"
#. openerp-web
#: addons/web/static/src/js/search.js:237
@ -384,12 +384,12 @@ msgstr "Ver Editor de%d -%s"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:367
msgid "Inherited View"
msgstr ""
msgstr "Vista heredada"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:371
msgid "Do you really wants to create an inherited view here?"
msgstr ""
msgstr "¿Realmente desea crear una vista heredada aquí?"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:381

1458
addons/web/i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-13 23:41+0000\n"
"Last-Translator: Amós Oviedo <Unknown>\n"
"PO-Revision-Date: 2012-02-16 10:02+0000\n"
"Last-Translator: Vicente <jviares@gmail.com>\n"
"Language-Team: Galician <gl@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -193,7 +193,7 @@ msgstr "Descargar \"%s\""
#. openerp-web
#: addons/web/static/src/js/search.js:191
msgid "Filter disabled due to invalid syntax"
msgstr ""
msgstr "Filtro desactivado debido a sintaxis inválida"
#. openerp-web
#: addons/web/static/src/js/search.js:237
@ -383,12 +383,12 @@ msgstr "Ver Editor %d - %s"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:367
msgid "Inherited View"
msgstr ""
msgstr "Vista Herdada"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:371
msgid "Do you really wants to create an inherited view here?"
msgstr ""
msgstr "¿Realmente quere crear unha vista herdada aquí?"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:381
@ -427,7 +427,7 @@ msgstr "Persoalizar"
#: addons/web/static/src/js/view_form.js:123
#: addons/web/static/src/js/view_form.js:686
msgid "Set Default"
msgstr ""
msgstr "Estabelecer como predeterminado"
#. openerp-web
#: addons/web/static/src/js/view_form.js:469
@ -438,7 +438,7 @@ msgstr "Advertencia, o rexistro modificouse, os cambios serán descartados."
#. openerp-web
#: addons/web/static/src/js/view_form.js:693
msgid "Save default"
msgstr ""
msgstr "Gardar como predeterminado"
#. openerp-web
#: addons/web/static/src/js/view_form.js:754
@ -904,47 +904,47 @@ msgstr "Vista"
#. openerp-web
#: addons/web/static/src/xml/base.xml:484
msgid "Edit SearchView"
msgstr ""
msgstr "Editar SearchView"
#. openerp-web
#: addons/web/static/src/xml/base.xml:485
msgid "Edit Action"
msgstr ""
msgstr "Editar a acción"
#. openerp-web
#: addons/web/static/src/xml/base.xml:486
msgid "Edit Workflow"
msgstr ""
msgstr "Editar Fluxo de Traballo"
#. openerp-web
#: addons/web/static/src/xml/base.xml:491
msgid "ID:"
msgstr ""
msgstr "ID:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:494
msgid "XML ID:"
msgstr ""
msgstr "XML ID:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:497
msgid "Creation User:"
msgstr ""
msgstr "Usuario de Creación:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:500
msgid "Creation Date:"
msgstr ""
msgstr "Data de Creación:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:503
msgid "Latest Modification by:"
msgstr ""
msgstr "Última Modificación por:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:506
msgid "Latest Modification Date:"
msgstr ""
msgstr "Última data de modificación:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:542
@ -966,27 +966,27 @@ msgstr "Duplicar"
#. openerp-web
#: addons/web/static/src/xml/base.xml:775
msgid "Add attachment"
msgstr ""
msgstr "Engadir anexo"
#. openerp-web
#: addons/web/static/src/xml/base.xml:801
msgid "Default:"
msgstr ""
msgstr "Predeterminado:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:818
msgid "Condition:"
msgstr ""
msgstr "Estado:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:837
msgid "Only you"
msgstr ""
msgstr "So vostede"
#. openerp-web
#: addons/web/static/src/xml/base.xml:844
msgid "All users"
msgstr ""
msgstr "Todos os usuarios"
#. openerp-web
#: addons/web/static/src/xml/base.xml:851
@ -996,88 +996,88 @@ msgstr "Widget non controlado"
#. openerp-web
#: addons/web/static/src/xml/base.xml:900
msgid "Notebook Page \""
msgstr ""
msgstr "Páxina de Caderno \""
#. openerp-web
#: addons/web/static/src/xml/base.xml:905
#: addons/web/static/src/xml/base.xml:964
msgid "Modifiers:"
msgstr ""
msgstr "Modificadores:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:931
msgid "(nolabel)"
msgstr ""
msgstr "(sin etiqueta)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:936
msgid "Field:"
msgstr ""
msgstr "Campo:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:940
msgid "Object:"
msgstr ""
msgstr "Obxecto:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:944
msgid "Type:"
msgstr ""
msgstr "Tipo:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:948
msgid "Widget:"
msgstr ""
msgstr "Widget:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:952
msgid "Size:"
msgstr ""
msgstr "Tamaño:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:956
msgid "Context:"
msgstr ""
msgstr "Contexto:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:960
msgid "Domain:"
msgstr ""
msgstr "Dominio:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:968
msgid "Change default:"
msgstr ""
msgstr "Cambiar predeterminado:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:972
msgid "On change:"
msgstr ""
msgstr "Cando cambie:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:976
msgid "Relation:"
msgstr ""
msgstr "Relación:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:980
msgid "Selection:"
msgstr ""
msgstr "Selección:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1020
msgid "Send an e-mail with your default e-mail client"
msgstr ""
msgstr "Envíe un correo electrónico co seu cliente de correo predefinido"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1034
msgid "Open this resource"
msgstr ""
msgstr "Abrir este recurso"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1056
msgid "Select date"
msgstr ""
msgstr "Seleccionar data"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1090
@ -1103,7 +1103,7 @@ msgstr "…"
#: addons/web/static/src/xml/base.xml:1155
#: addons/web/static/src/xml/base.xml:1198
msgid "Set Image"
msgstr ""
msgstr "Estabelecer imaxe"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1163
@ -1134,57 +1134,57 @@ msgstr "Gardar como"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1238
msgid "Button"
msgstr ""
msgstr "Botón"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1241
msgid "(no string)"
msgstr ""
msgstr "(sin cadena)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1248
msgid "Special:"
msgstr ""
msgstr "Especial:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1253
msgid "Button Type:"
msgstr ""
msgstr "Tipo de Botón"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1257
msgid "Method:"
msgstr ""
msgstr "Método:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1261
msgid "Action ID:"
msgstr ""
msgstr "Acción ID:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1271
msgid "Search"
msgstr ""
msgstr "Busca"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1279
msgid "Filters"
msgstr ""
msgstr "Filtros"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1280
msgid "-- Filters --"
msgstr ""
msgstr "-- Filtros --"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1289
msgid "-- Actions --"
msgstr ""
msgstr "-- Accions --"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1290
msgid "Add Advanced Filter"
msgstr ""
msgstr "Engadir Filtro Avanzado"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1291
@ -1209,17 +1209,17 @@ msgstr "(Calquer filtro existente co mesmo nome será reemplazado)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1305
msgid "Select Dashboard to add this filter to:"
msgstr ""
msgstr "Seleccionar tableiro para engadirlle este filtro:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1309
msgid "Title of new Dashboard item:"
msgstr ""
msgstr "Título de novo elemento de Tableiro:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1416
msgid "Advanced Filters"
msgstr ""
msgstr "Filtros Avanzados"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1426
@ -1411,6 +1411,8 @@ msgid ""
"For use if CSV files have titles on multiple lines, skips more than a single "
"line during import"
msgstr ""
"Para o seu uso se os ficheiros CSV teñen títulos en varias liñas, omitense "
"mais de una soa liña durante a importación"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1803
@ -1425,7 +1427,7 @@ msgstr "Esta é a vista previa do arquivo que non se pode importar:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1812
msgid "Activate the developper mode"
msgstr ""
msgstr "Activar modo de desenvolvedor"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1814
@ -1435,7 +1437,7 @@ msgstr "Versión"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1815
msgid "Copyright © 2004-TODAY OpenERP SA. All Rights Reserved."
msgstr ""
msgstr "Copyright © 2004-HOY OpenERP SA. Todos os dereitos reservados."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1816
@ -1460,9 +1462,9 @@ msgstr "GNU Affero General Public License"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1822
msgid "For more information visit"
msgstr ""
msgstr "Para mais información visite"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1823
msgid "OpenERP.com"
msgstr ""
msgstr "OpenERP.com"

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-11 21:36+0000\n"
"PO-Revision-Date: 2012-03-14 07:11+0000\n"
"Last-Translator: Masaki Yamaya <Unknown>\n"
"Language-Team: Japanese <ja@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-03-15 04:53+0000\n"
"X-Generator: Launchpad (build 14933)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -24,8 +24,10 @@ msgstr ""
#: addons/web/static/src/js/view_form.js:419
#: addons/web/static/src/js/view_form.js:1233
#: addons/web/static/src/xml/base.xml:1695
#: addons/web/static/src/js/view_form.js:424
#: addons/web/static/src/js/view_form.js:1239
msgid "Ok"
msgstr ""
msgstr "正常"
#. openerp-web
#: addons/web/static/src/js/chrome.js:180
@ -35,18 +37,18 @@ msgstr "OpenERPエンタープライズレポートを送る"
#. openerp-web
#: addons/web/static/src/js/chrome.js:194
msgid "Dont send"
msgstr ""
msgstr "送らない"
#. openerp-web
#: addons/web/static/src/js/chrome.js:256
#, python-format
msgid "Loading (%d)"
msgstr ""
msgstr "ロード中 (%d)"
#. openerp-web
#: addons/web/static/src/js/chrome.js:288
msgid "Invalid database name"
msgstr ""
msgstr "無効なデータベース名"
#. openerp-web
#: addons/web/static/src/js/chrome.js:483
@ -56,29 +58,29 @@ msgstr ""
#. openerp-web
#: addons/web/static/src/js/chrome.js:484
msgid "Database backed up successfully"
msgstr ""
msgstr "データベースは正常にバックアップされました"
#. openerp-web
#: addons/web/static/src/js/chrome.js:527
msgid "Restored"
msgstr ""
msgstr "リストアされました"
#. openerp-web
#: addons/web/static/src/js/chrome.js:527
msgid "Database restored successfully"
msgstr ""
msgstr "データベースは正常にリストアされました"
#. openerp-web
#: addons/web/static/src/js/chrome.js:708
#: addons/web/static/src/xml/base.xml:359
msgid "About"
msgstr ""
msgstr "について"
#. openerp-web
#: addons/web/static/src/js/chrome.js:787
#: addons/web/static/src/xml/base.xml:356
msgid "Preferences"
msgstr ""
msgstr "個人設定"
#. openerp-web
#: addons/web/static/src/js/chrome.js:790
@ -92,44 +94,49 @@ msgstr ""
#: addons/web/static/src/xml/base.xml:1496
#: addons/web/static/src/xml/base.xml:1506
#: addons/web/static/src/xml/base.xml:1515
#: addons/web/static/src/js/search.js:293
#: addons/web/static/src/js/view_form.js:1234
msgid "Cancel"
msgstr ""
msgstr "キャンセル"
#. openerp-web
#: addons/web/static/src/js/chrome.js:791
msgid "Change password"
msgstr ""
msgstr "パスワードの変更"
#. openerp-web
#: addons/web/static/src/js/chrome.js:792
#: addons/web/static/src/js/view_editor.js:73
#: addons/web/static/src/js/views.js:962 addons/web/static/src/xml/base.xml:737
#: addons/web/static/src/js/views.js:962
#: addons/web/static/src/xml/base.xml:737
#: addons/web/static/src/xml/base.xml:1500
#: addons/web/static/src/xml/base.xml:1514
msgid "Save"
msgstr ""
msgstr "保存"
#. openerp-web
#: addons/web/static/src/js/chrome.js:811
#: addons/web/static/src/xml/base.xml:226
#: addons/web/static/src/xml/base.xml:1729
msgid "Change Password"
msgstr ""
msgstr "パスワードの変更"
#. openerp-web
#: addons/web/static/src/js/chrome.js:1096
#: addons/web/static/src/js/chrome.js:1100
msgid "OpenERP - Unsupported/Community Version"
msgstr ""
msgstr "OpenERP - サポート無し/コミュニティバージョン"
#. openerp-web
#: addons/web/static/src/js/chrome.js:1131
#: addons/web/static/src/js/chrome.js:1135
msgid "Client Error"
msgstr ""
msgstr "クライアントのエラー"
#. openerp-web
#: addons/web/static/src/js/data_export.js:6
msgid "Export Data"
msgstr ""
msgstr "データのエクスポート"
#. openerp-web
#: addons/web/static/src/js/data_export.js:19
@ -139,13 +146,15 @@ msgstr ""
#: addons/web/static/src/js/view_form.js:692
#: addons/web/static/src/js/view_form.js:3044
#: addons/web/static/src/js/views.js:963
#: addons/web/static/src/js/view_form.js:698
#: addons/web/static/src/js/view_form.js:3067
msgid "Close"
msgstr ""
msgstr "閉じる"
#. openerp-web
#: addons/web/static/src/js/data_export.js:20
msgid "Export To File"
msgstr ""
msgstr "ファイルにエクスポート"
#. openerp-web
#: addons/web/static/src/js/data_export.js:125
@ -165,29 +174,32 @@ msgstr ""
#. openerp-web
#: addons/web/static/src/js/data_import.js:34
msgid "Import Data"
msgstr ""
msgstr "データをインポート"
#. openerp-web
#: addons/web/static/src/js/data_import.js:70
msgid "Import File"
msgstr ""
msgstr "ファイルをインポート"
#. openerp-web
#: addons/web/static/src/js/data_import.js:105
msgid "External ID"
msgstr ""
msgstr "外部ID"
#. openerp-web
#: addons/web/static/src/js/formats.js:300
#: addons/web/static/src/js/view_page.js:245
#: addons/web/static/src/js/formats.js:322
#: addons/web/static/src/js/view_page.js:251
msgid "Download"
msgstr ""
msgstr "ダウンロード"
#. openerp-web
#: addons/web/static/src/js/formats.js:305
#: addons/web/static/src/js/formats.js:327
#, python-format
msgid "Download \"%s\""
msgstr ""
msgstr "ダウンロード \"%s\""
#. openerp-web
#: addons/web/static/src/js/search.js:191
@ -197,66 +209,77 @@ msgstr ""
#. openerp-web
#: addons/web/static/src/js/search.js:237
msgid "Filter Entry"
msgstr ""
msgstr "フィルター項目"
#. openerp-web
#: addons/web/static/src/js/search.js:242
#: addons/web/static/src/js/search.js:291
#: addons/web/static/src/js/search.js:296
msgid "OK"
msgstr ""
msgstr "はい"
#. openerp-web
#: addons/web/static/src/js/search.js:286
#: addons/web/static/src/xml/base.xml:1292
#: addons/web/static/src/js/search.js:291
msgid "Add to Dashboard"
msgstr ""
msgstr "ダッシュボードに追加"
#. openerp-web
#: addons/web/static/src/js/search.js:415
#: addons/web/static/src/js/search.js:420
msgid "Invalid Search"
msgstr ""
msgstr "無効な検索"
#. openerp-web
#: addons/web/static/src/js/search.js:415
#: addons/web/static/src/js/search.js:420
msgid "triggered from search view"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/search.js:503
#: addons/web/static/src/js/search.js:508
#, python-format
msgid "Incorrect value for field %(fieldname)s: [%(value)s] is %(message)s"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/search.js:839
#: addons/web/static/src/js/search.js:844
msgid "not a valid integer"
msgstr ""
msgstr "無効な整数"
#. openerp-web
#: addons/web/static/src/js/search.js:853
#: addons/web/static/src/js/search.js:858
msgid "not a valid number"
msgstr ""
msgstr "無効な数値"
#. openerp-web
#: addons/web/static/src/js/search.js:931
#: addons/web/static/src/xml/base.xml:968
#: addons/web/static/src/js/search.js:936
msgid "Yes"
msgstr ""
msgstr "はい"
#. openerp-web
#: addons/web/static/src/js/search.js:932
#: addons/web/static/src/js/search.js:937
msgid "No"
msgstr ""
msgstr "いいえ"
#. openerp-web
#: addons/web/static/src/js/search.js:1290
#: addons/web/static/src/js/search.js:1295
msgid "contains"
msgstr ""
msgstr "次を含む"
#. openerp-web
#: addons/web/static/src/js/search.js:1291
#: addons/web/static/src/js/search.js:1296
msgid "doesn't contain"
msgstr ""
msgstr "含まない"
#. openerp-web
#: addons/web/static/src/js/search.js:1292
@ -264,8 +287,13 @@ msgstr ""
#: addons/web/static/src/js/search.js:1325
#: addons/web/static/src/js/search.js:1344
#: addons/web/static/src/js/search.js:1365
#: addons/web/static/src/js/search.js:1297
#: addons/web/static/src/js/search.js:1311
#: addons/web/static/src/js/search.js:1330
#: addons/web/static/src/js/search.js:1349
#: addons/web/static/src/js/search.js:1370
msgid "is equal to"
msgstr ""
msgstr "は次と一致する"
#. openerp-web
#: addons/web/static/src/js/search.js:1293
@ -273,8 +301,13 @@ msgstr ""
#: addons/web/static/src/js/search.js:1326
#: addons/web/static/src/js/search.js:1345
#: addons/web/static/src/js/search.js:1366
#: addons/web/static/src/js/search.js:1298
#: addons/web/static/src/js/search.js:1312
#: addons/web/static/src/js/search.js:1331
#: addons/web/static/src/js/search.js:1350
#: addons/web/static/src/js/search.js:1371
msgid "is not equal to"
msgstr ""
msgstr "は次と一致しない"
#. openerp-web
#: addons/web/static/src/js/search.js:1294
@ -282,8 +315,13 @@ msgstr ""
#: addons/web/static/src/js/search.js:1327
#: addons/web/static/src/js/search.js:1346
#: addons/web/static/src/js/search.js:1367
#: addons/web/static/src/js/search.js:1299
#: addons/web/static/src/js/search.js:1313
#: addons/web/static/src/js/search.js:1332
#: addons/web/static/src/js/search.js:1351
#: addons/web/static/src/js/search.js:1372
msgid "greater than"
msgstr ""
msgstr "次より大きい"
#. openerp-web
#: addons/web/static/src/js/search.js:1295
@ -291,8 +329,13 @@ msgstr ""
#: addons/web/static/src/js/search.js:1328
#: addons/web/static/src/js/search.js:1347
#: addons/web/static/src/js/search.js:1368
#: addons/web/static/src/js/search.js:1300
#: addons/web/static/src/js/search.js:1314
#: addons/web/static/src/js/search.js:1333
#: addons/web/static/src/js/search.js:1352
#: addons/web/static/src/js/search.js:1373
msgid "less than"
msgstr ""
msgstr "次より小さい"
#. openerp-web
#: addons/web/static/src/js/search.js:1296
@ -300,8 +343,13 @@ msgstr ""
#: addons/web/static/src/js/search.js:1329
#: addons/web/static/src/js/search.js:1348
#: addons/web/static/src/js/search.js:1369
#: addons/web/static/src/js/search.js:1301
#: addons/web/static/src/js/search.js:1315
#: addons/web/static/src/js/search.js:1334
#: addons/web/static/src/js/search.js:1353
#: addons/web/static/src/js/search.js:1374
msgid "greater or equal than"
msgstr ""
msgstr "次より大きいか等しい"
#. openerp-web
#: addons/web/static/src/js/search.js:1297
@ -309,27 +357,37 @@ msgstr ""
#: addons/web/static/src/js/search.js:1330
#: addons/web/static/src/js/search.js:1349
#: addons/web/static/src/js/search.js:1370
#: addons/web/static/src/js/search.js:1302
#: addons/web/static/src/js/search.js:1316
#: addons/web/static/src/js/search.js:1335
#: addons/web/static/src/js/search.js:1354
#: addons/web/static/src/js/search.js:1375
msgid "less or equal than"
msgstr ""
msgstr "次より小さいか等しい"
#. openerp-web
#: addons/web/static/src/js/search.js:1360
#: addons/web/static/src/js/search.js:1383
#: addons/web/static/src/js/search.js:1365
#: addons/web/static/src/js/search.js:1388
msgid "is"
msgstr ""
msgstr "が次である"
#. openerp-web
#: addons/web/static/src/js/search.js:1384
#: addons/web/static/src/js/search.js:1389
msgid "is not"
msgstr ""
msgstr "は次ではない"
#. openerp-web
#: addons/web/static/src/js/search.js:1396
#: addons/web/static/src/js/search.js:1401
msgid "is true"
msgstr ""
msgstr "は正しい"
#. openerp-web
#: addons/web/static/src/js/search.js:1397
#: addons/web/static/src/js/search.js:1402
msgid "is false"
msgstr ""
@ -424,51 +482,60 @@ msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:123
#: addons/web/static/src/js/view_form.js:686
#: addons/web/static/src/js/view_form.js:692
msgid "Set Default"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:469
#: addons/web/static/src/js/view_form.js:475
msgid ""
"Warning, the record has been modified, your changes will be discarded."
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:693
#: addons/web/static/src/js/view_form.js:699
msgid "Save default"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:754
#: addons/web/static/src/js/view_form.js:760
msgid "Attachments"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:792
#: addons/web/static/src/js/view_form.js:798
#, python-format
msgid "Do you really want to delete the attachment %s?"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:822
#: addons/web/static/src/js/view_form.js:828
#, python-format
msgid "Unknown operator %s in domain %s"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:830
#: addons/web/static/src/js/view_form.js:836
#, python-format
msgid "Unknown field %s in domain %s"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:868
#: addons/web/static/src/js/view_form.js:874
#, python-format
msgid "Unsupported operator %s in domain %s"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:1225
#: addons/web/static/src/js/view_form.js:1231
msgid "Confirm"
msgstr ""
@ -476,34 +543,43 @@ msgstr ""
#: addons/web/static/src/js/view_form.js:1921
#: addons/web/static/src/js/view_form.js:2578
#: addons/web/static/src/js/view_form.js:2741
#: addons/web/static/src/js/view_form.js:1933
#: addons/web/static/src/js/view_form.js:2590
#: addons/web/static/src/js/view_form.js:2760
msgid "Open: "
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:2049
#: addons/web/static/src/js/view_form.js:2061
msgid "<em>   Search More...</em>"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:2062
#: addons/web/static/src/js/view_form.js:2074
#, python-format
msgid "<em>   Create \"<strong>%s</strong>\"</em>"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:2068
#: addons/web/static/src/js/view_form.js:2080
msgid "<em>   Create and Edit...</em>"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:2101
#: addons/web/static/src/js/views.js:675
#: addons/web/static/src/js/view_form.js:2113
msgid "Search: "
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:2101
#: addons/web/static/src/js/view_form.js:2550
#: addons/web/static/src/js/view_form.js:2113
#: addons/web/static/src/js/view_form.js:2562
msgid "Create: "
msgstr ""
@ -512,11 +588,13 @@ msgstr ""
#: addons/web/static/src/xml/base.xml:750
#: addons/web/static/src/xml/base.xml:772
#: addons/web/static/src/xml/base.xml:1646
#: addons/web/static/src/js/view_form.js:2680
msgid "Add"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_form.js:2721
#: addons/web/static/src/js/view_form.js:2740
msgid "Add: "
msgstr ""
@ -532,22 +610,26 @@ msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_list.js:305
#: addons/web/static/src/js/view_list.js:309
#, python-format
msgid "[%(first_record)d to %(last_record)d] of %(records_count)d"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_list.js:524
#: addons/web/static/src/js/view_list.js:528
msgid "Do you really want to remove these records?"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_list.js:1230
#: addons/web/static/src/js/view_list.js:1232
msgid "Undefined"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/view_list.js:1327
#: addons/web/static/src/js/view_list.js:1331
#, python-format
msgid "%(page)d/%(page_count)d"
msgstr ""
@ -568,7 +650,8 @@ msgid "Tree"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/views.js:565 addons/web/static/src/xml/base.xml:480
#: addons/web/static/src/js/views.js:565
#: addons/web/static/src/xml/base.xml:480
msgid "Fields View Get"
msgstr ""
@ -585,7 +668,8 @@ msgid "Model %s fields"
msgstr ""
#. openerp-web
#: addons/web/static/src/js/views.js:610 addons/web/static/src/xml/base.xml:482
#: addons/web/static/src/js/views.js:610
#: addons/web/static/src/xml/base.xml:482
msgid "Manage Views"
msgstr ""
@ -652,12 +736,14 @@ msgid "Translations"
msgstr ""
#. openerp-web
#: addons/web/static/src/xml/base.xml:44 addons/web/static/src/xml/base.xml:315
#: addons/web/static/src/xml/base.xml:44
#: addons/web/static/src/xml/base.xml:315
msgid "Powered by"
msgstr ""
#. openerp-web
#: addons/web/static/src/xml/base.xml:44 addons/web/static/src/xml/base.xml:315
#: addons/web/static/src/xml/base.xml:44
#: addons/web/static/src/xml/base.xml:315
#: addons/web/static/src/xml/base.xml:1813
msgid "OpenERP"
msgstr ""
@ -673,12 +759,14 @@ msgid "CREATE DATABASE"
msgstr ""
#. openerp-web
#: addons/web/static/src/xml/base.xml:68 addons/web/static/src/xml/base.xml:211
#: addons/web/static/src/xml/base.xml:68
#: addons/web/static/src/xml/base.xml:211
msgid "Master password:"
msgstr ""
#. openerp-web
#: addons/web/static/src/xml/base.xml:72 addons/web/static/src/xml/base.xml:191
#: addons/web/static/src/xml/base.xml:72
#: addons/web/static/src/xml/base.xml:191
msgid "New database name:"
msgstr ""

1548
addons/web/i18n/ka.po Normal file

File diff suppressed because it is too large Load Diff

1544
addons/web/i18n/nb.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-15 14:37+0000\n"
"PO-Revision-Date: 2012-02-16 10:56+0000\n"
"Last-Translator: Erwin <Unknown>\n"
"Language-Team: Dutch <nl@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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -534,7 +534,7 @@ msgstr "Onbeperkt"
#: addons/web/static/src/js/view_list.js:305
#, python-format
msgid "[%(first_record)d to %(last_record)d] of %(records_count)d"
msgstr "[%(first_record)d tot %(last_record)d] van %(records_count)d"
msgstr "[%(first_record)d t/m %(last_record)d] van %(records_count)d"
#. openerp-web
#: addons/web/static/src/js/view_list.js:524
@ -592,7 +592,7 @@ msgstr "Weergaven beheren"
#. openerp-web
#: addons/web/static/src/js/views.js:611
msgid "Could not find current view declaration"
msgstr ""
msgstr "Kan huidige weergave declaratie niet vinden"
#. openerp-web
#: addons/web/static/src/js/views.js:805
@ -989,7 +989,7 @@ msgstr "Alle gebruikers"
#. openerp-web
#: addons/web/static/src/xml/base.xml:851
msgid "Unhandled widget"
msgstr ""
msgstr "Niet-verwerkte widget"
#. openerp-web
#: addons/web/static/src/xml/base.xml:900

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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172

1548
addons/web/i18n/ro.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-08 07:26+0000\n"
"PO-Revision-Date: 2012-02-20 07:27+0000\n"
"Last-Translator: Aleksei Motsik <Unknown>\n"
"Language-Team: Russian <ru@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-21 06:10+0000\n"
"X-Generator: Launchpad (build 14838)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -150,12 +150,12 @@ msgstr "Экспортировать в файл"
#. openerp-web
#: addons/web/static/src/js/data_export.js:125
msgid "Please enter save field list name"
msgstr ""
msgstr "Введите имя для сохраняемого списка полей"
#. openerp-web
#: addons/web/static/src/js/data_export.js:360
msgid "Please select fields to save export list..."
msgstr ""
msgstr "Выберите поля для сохранения в списке экспорта..."
#. openerp-web
#: addons/web/static/src/js/data_export.js:373
@ -192,7 +192,7 @@ msgstr "Загрузка \"%s\""
#. openerp-web
#: addons/web/static/src/js/search.js:191
msgid "Filter disabled due to invalid syntax"
msgstr ""
msgstr "Фильтр отключен так-как имеет неверный синтаксис"
#. openerp-web
#: addons/web/static/src/js/search.js:237
@ -219,7 +219,7 @@ msgstr "Ошибка поиска"
#. openerp-web
#: addons/web/static/src/js/search.js:415
msgid "triggered from search view"
msgstr ""
msgstr "вызвано из поиска"
#. openerp-web
#: addons/web/static/src/js/search.js:503
@ -382,12 +382,12 @@ msgstr "Редактор Вида %d - %s"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:367
msgid "Inherited View"
msgstr ""
msgstr "Унаследованный Вид"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:371
msgid "Do you really wants to create an inherited view here?"
msgstr ""
msgstr "Вы действительно хотите создать наследующий вид?"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:381
@ -426,7 +426,7 @@ msgstr "Настроить"
#: addons/web/static/src/js/view_form.js:123
#: addons/web/static/src/js/view_form.js:686
msgid "Set Default"
msgstr ""
msgstr "Установить по умолчанию"
#. openerp-web
#: addons/web/static/src/js/view_form.js:469
@ -437,7 +437,7 @@ msgstr "Внимание. Эта запись была изменена. Ваш
#. openerp-web
#: addons/web/static/src/js/view_form.js:693
msgid "Save default"
msgstr ""
msgstr "Сохранить как По Умолчанию"
#. openerp-web
#: addons/web/static/src/js/view_form.js:754
@ -551,7 +551,7 @@ msgstr "Не определено"
#: addons/web/static/src/js/view_list.js:1327
#, python-format
msgid "%(page)d/%(page_count)d"
msgstr ""
msgstr "%(page)d/%(page_count)d"
#. openerp-web
#: addons/web/static/src/js/view_page.js:8
@ -571,7 +571,7 @@ msgstr "Дерево"
#. openerp-web
#: addons/web/static/src/js/views.js:565 addons/web/static/src/xml/base.xml:480
msgid "Fields View Get"
msgstr ""
msgstr "Получить Поля Просмотра"
#. openerp-web
#: addons/web/static/src/js/views.js:573
@ -843,7 +843,7 @@ msgstr "Вернутся к Авторизации"
#. openerp-web
#: addons/web/static/src/xml/base.xml:353
msgid "Home"
msgstr ""
msgstr "Домой"
#. openerp-web
#: addons/web/static/src/xml/base.xml:363
@ -853,12 +853,12 @@ msgstr "ВЫЙТИ"
#. openerp-web
#: addons/web/static/src/xml/base.xml:388
msgid "Fold menu"
msgstr ""
msgstr "Свернуть меню"
#. openerp-web
#: addons/web/static/src/xml/base.xml:389
msgid "Unfold menu"
msgstr ""
msgstr "Развернуть меню"
#. openerp-web
#: addons/web/static/src/xml/base.xml:454
@ -873,7 +873,7 @@ msgstr "Отключить все подсказки"
#. openerp-web
#: addons/web/static/src/xml/base.xml:463
msgid "Add / Remove Shortcut..."
msgstr ""
msgstr "Добавить / Удалить ярлык..."
#. openerp-web
#: addons/web/static/src/xml/base.xml:471
@ -883,32 +883,32 @@ msgstr "Больше..."
#. openerp-web
#: addons/web/static/src/xml/base.xml:477
msgid "Debug View#"
msgstr ""
msgstr "Debug View#"
#. openerp-web
#: addons/web/static/src/xml/base.xml:478
msgid "View Log (perm_read)"
msgstr ""
msgstr "Просмотр Лога (доступ на чтение)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:479
msgid "View Fields"
msgstr ""
msgstr "Просмотр Полей"
#. openerp-web
#: addons/web/static/src/xml/base.xml:483
msgid "View"
msgstr ""
msgstr "Вид"
#. openerp-web
#: addons/web/static/src/xml/base.xml:484
msgid "Edit SearchView"
msgstr ""
msgstr "Изменить Вид Поиска"
#. openerp-web
#: addons/web/static/src/xml/base.xml:485
msgid "Edit Action"
msgstr ""
msgstr "Изменить действие"
#. openerp-web
#: addons/web/static/src/xml/base.xml:486
@ -918,191 +918,191 @@ msgstr "Редактировать Процесс"
#. openerp-web
#: addons/web/static/src/xml/base.xml:491
msgid "ID:"
msgstr ""
msgstr "ID:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:494
msgid "XML ID:"
msgstr ""
msgstr "XML ID:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:497
msgid "Creation User:"
msgstr ""
msgstr "Создатель:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:500
msgid "Creation Date:"
msgstr ""
msgstr "Дата Создания:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:503
msgid "Latest Modification by:"
msgstr ""
msgstr "Изменялся:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:506
msgid "Latest Modification Date:"
msgstr ""
msgstr "Дата Изменения:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:542
msgid "Field"
msgstr ""
msgstr "Поле"
#. openerp-web
#: addons/web/static/src/xml/base.xml:632
#: addons/web/static/src/xml/base.xml:758
#: addons/web/static/src/xml/base.xml:1708
msgid "Delete"
msgstr ""
msgstr "Удалить"
#. openerp-web
#: addons/web/static/src/xml/base.xml:757
msgid "Duplicate"
msgstr ""
msgstr "Клонировать"
#. openerp-web
#: addons/web/static/src/xml/base.xml:775
msgid "Add attachment"
msgstr ""
msgstr "Добавить вложение"
#. openerp-web
#: addons/web/static/src/xml/base.xml:801
msgid "Default:"
msgstr ""
msgstr "По умолчанию:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:818
msgid "Condition:"
msgstr ""
msgstr "Условие:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:837
msgid "Only you"
msgstr ""
msgstr "Только Вы"
#. openerp-web
#: addons/web/static/src/xml/base.xml:844
msgid "All users"
msgstr ""
msgstr "Все пользователи"
#. openerp-web
#: addons/web/static/src/xml/base.xml:851
msgid "Unhandled widget"
msgstr ""
msgstr "Неподдерживаемый виджет"
#. openerp-web
#: addons/web/static/src/xml/base.xml:900
msgid "Notebook Page \""
msgstr ""
msgstr "Страница Блокнота \""
#. openerp-web
#: addons/web/static/src/xml/base.xml:905
#: addons/web/static/src/xml/base.xml:964
msgid "Modifiers:"
msgstr ""
msgstr "Модификаторы:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:931
msgid "(nolabel)"
msgstr ""
msgstr "(без метки)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:936
msgid "Field:"
msgstr ""
msgstr "Поле:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:940
msgid "Object:"
msgstr ""
msgstr "Объект:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:944
msgid "Type:"
msgstr ""
msgstr "Тип:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:948
msgid "Widget:"
msgstr ""
msgstr "Виджет:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:952
msgid "Size:"
msgstr ""
msgstr "Размер:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:956
msgid "Context:"
msgstr ""
msgstr "Контекст:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:960
msgid "Domain:"
msgstr ""
msgstr "Домен:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:968
msgid "Change default:"
msgstr ""
msgstr "Изменить по умолчанию:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:972
msgid "On change:"
msgstr ""
msgstr "При изменении:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:976
msgid "Relation:"
msgstr ""
msgstr "Отношение:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:980
msgid "Selection:"
msgstr ""
msgstr "Выбор:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1020
msgid "Send an e-mail with your default e-mail client"
msgstr ""
msgstr "Отправлять имейл вашим почтовым клиентом"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1034
msgid "Open this resource"
msgstr ""
msgstr "Открыть этот ресурс"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1056
msgid "Select date"
msgstr ""
msgstr "Выбрать дату"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1090
msgid "Open..."
msgstr ""
msgstr "Открыть..."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1091
msgid "Create..."
msgstr ""
msgstr "Создать…"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1092
msgid "Search..."
msgstr ""
msgstr "Поиск…"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1095
msgid "..."
msgstr ""
msgstr "..."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1155
#: addons/web/static/src/xml/base.xml:1198
msgid "Set Image"
msgstr ""
msgstr "Назначить изображение"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1163
@ -1110,150 +1110,150 @@ msgstr ""
#: addons/web/static/src/xml/base.xml:1215
#: addons/web/static/src/xml/base.xml:1272
msgid "Clear"
msgstr ""
msgstr "Очистить"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1172
#: addons/web/static/src/xml/base.xml:1223
msgid "Uploading ..."
msgstr ""
msgstr "Загружаю ..."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1200
#: addons/web/static/src/xml/base.xml:1495
msgid "Select"
msgstr ""
msgstr "Выбор"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1207
#: addons/web/static/src/xml/base.xml:1209
msgid "Save As"
msgstr ""
msgstr "Сохранить как"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1238
msgid "Button"
msgstr ""
msgstr "Кнопка"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1241
msgid "(no string)"
msgstr ""
msgstr "(нет строки)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1248
msgid "Special:"
msgstr ""
msgstr "Специальное:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1253
msgid "Button Type:"
msgstr ""
msgstr "Тип Кнопки:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1257
msgid "Method:"
msgstr ""
msgstr "Метод:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1261
msgid "Action ID:"
msgstr ""
msgstr "ID Действия:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1271
msgid "Search"
msgstr ""
msgstr "Поиск"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1279
msgid "Filters"
msgstr ""
msgstr "Фильтры"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1280
msgid "-- Filters --"
msgstr ""
msgstr "-- Фильтры --"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1289
msgid "-- Actions --"
msgstr ""
msgstr "-- Действия --"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1290
msgid "Add Advanced Filter"
msgstr ""
msgstr "Добавить Расширенный Фильтр"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1291
msgid "Save Filter"
msgstr ""
msgstr "Сохранить фильтр"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1293
msgid "Manage Filters"
msgstr ""
msgstr "Управление фильтрами"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1298
msgid "Filter Name:"
msgstr ""
msgstr "Название Фильтра:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1300
msgid "(Any existing filter with the same name will be replaced)"
msgstr ""
msgstr "(Существующий фильтр с таким же именени будет замещен)"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1305
msgid "Select Dashboard to add this filter to:"
msgstr ""
msgstr "Выберите Дашбоард, к которому добавить этот фильтр:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1309
msgid "Title of new Dashboard item:"
msgstr ""
msgstr "Заголовок нового Дашбоарда:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1416
msgid "Advanced Filters"
msgstr ""
msgstr "Расширенные Фильтры"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1426
msgid "Any of the following conditions must match"
msgstr ""
msgstr "Одно из следующих условий должно соответствовать"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1427
msgid "All the following conditions must match"
msgstr ""
msgstr "Все следующие условия должны соответствовать"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1428
msgid "None of the following conditions must match"
msgstr ""
msgstr "Ни одно из следующих условий не должно соответствовать"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1435
msgid "Add condition"
msgstr ""
msgstr "Добавить условие"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1436
msgid "and"
msgstr ""
msgstr "и"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1503
msgid "Save & New"
msgstr ""
msgstr "Сохранить и Создать"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1504
msgid "Save & Close"
msgstr ""
msgstr "Сохранить и Закрыть"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1611
@ -1263,81 +1263,84 @@ msgid ""
" You can export all data or only the fields that can be "
"reimported after modification."
msgstr ""
"Этот мастер экспортирует все найденные данные в CSV файл.\n"
" Вы можете экспортровать все данные либо только те поля которые "
"могут быть в последствии импортированны."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1618
msgid "Export Type:"
msgstr ""
msgstr "Тип экспорта:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1620
msgid "Import Compatible Export"
msgstr ""
msgstr "Импорт совместимого Экспорта"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1621
msgid "Export all Data"
msgstr ""
msgstr "Экспортировать все данные"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1624
msgid "Export Formats"
msgstr ""
msgstr "Форматы Экспорта"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1630
msgid "Available fields"
msgstr ""
msgstr "Доступные поля"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1632
msgid "Fields to export"
msgstr ""
msgstr "Поля для экспорта"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1634
msgid "Save fields list"
msgstr ""
msgstr "Сохранить список полей"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1648
msgid "Remove All"
msgstr ""
msgstr "Удалить все"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1660
msgid "Name"
msgstr ""
msgstr "Название"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1693
msgid "Save as:"
msgstr ""
msgstr "Сохранить как:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1700
msgid "Saved exports:"
msgstr ""
msgstr "Сохраненые экспорты:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1714
msgid "Old Password:"
msgstr ""
msgstr "Старый пароль:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1719
msgid "New Password:"
msgstr ""
msgstr "Новый пароль:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1724
msgid "Confirm Password:"
msgstr ""
msgstr "Пароль ещё раз:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1742
msgid "1. Import a .CSV file"
msgstr ""
msgstr "1. Импорт из .CSV файла"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1743
@ -1345,56 +1348,59 @@ msgid ""
"Select a .CSV file to import. If you need a sample of file to import,\n"
" you should use the export tool with the \"Import Compatible\" option."
msgstr ""
"Укажите .CSV файл для импорта. Если вам нужен пример такого файла,\n"
" воспользуйтесь инструментом экспорта с опцией \"Совместимость для "
"Импорта\"."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1747
msgid "CSV File:"
msgstr ""
msgstr "Файл CSV:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1750
msgid "2. Check your file format"
msgstr ""
msgstr "2. Проверьте формат файла"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1753
msgid "Import Options"
msgstr ""
msgstr "Параметры импорта"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1757
msgid "Does your file have titles?"
msgstr ""
msgstr "В файле есть заголовки (строка с названиями колонок)?"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1763
msgid "Separator:"
msgstr ""
msgstr "Разделитель:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1765
msgid "Delimiter:"
msgstr ""
msgstr "Разделитель:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1769
msgid "Encoding:"
msgstr ""
msgstr "Кодировка:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1772
msgid "UTF-8"
msgstr ""
msgstr "UTF-8"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1773
msgid "Latin 1"
msgstr ""
msgstr "Latin 1"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1776
msgid "Lines to skip"
msgstr ""
msgstr "Пропустить строки"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1776
@ -1402,58 +1408,60 @@ msgid ""
"For use if CSV files have titles on multiple lines, skips more than a single "
"line during import"
msgstr ""
"Применимо если в CSV файле заголовки расположены в нескольких строках и из "
"необходимо пропустить"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1803
msgid "The import failed due to:"
msgstr ""
msgstr "Импорт не выполнен по причине:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1805
msgid "Here is a preview of the file we could not import:"
msgstr ""
msgstr "Предпросмотр файла, который система не смогла импортировать:"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1812
msgid "Activate the developper mode"
msgstr ""
msgstr "Активировать режим разработчика"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1814
msgid "Version"
msgstr ""
msgstr "Версия"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1815
msgid "Copyright © 2004-TODAY OpenERP SA. All Rights Reserved."
msgstr ""
msgstr "Авторское право © 2004-СЕГОДНЯ OpenERP SA. Все Права Защищены."
#. openerp-web
#: addons/web/static/src/xml/base.xml:1816
msgid "OpenERP is a trademark of the"
msgstr ""
msgstr "OpenERP является торговым знаком"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1817
msgid "OpenERP SA Company"
msgstr ""
msgstr "OpenERP SA Company"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1819
msgid "Licenced under the terms of"
msgstr ""
msgstr "Лицензированно по условиям"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1820
msgid "GNU Affero General Public License"
msgstr ""
msgstr "GNU Affero General Public License"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1822
msgid "For more information visit"
msgstr ""
msgstr "Для получения подробной информации посетите"
#. openerp-web
#: addons/web/static/src/xml/base.xml:1823
msgid "OpenERP.com"
msgstr ""
msgstr "OpenERP.com"

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-09 20:50+0000\n"
"PO-Revision-Date: 2012-02-24 11:29+0000\n"
"Last-Translator: Ahmet Altınışık <Unknown>\n"
"Language-Team: Turkish <tr@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-25 05:30+0000\n"
"X-Generator: Launchpad (build 14860)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -192,7 +192,7 @@ msgstr "Karşıdan yükle \"%s\""
#. openerp-web
#: addons/web/static/src/js/search.js:191
msgid "Filter disabled due to invalid syntax"
msgstr ""
msgstr "Geçersiz sözdizimi nedeniyle filtre engellendi"
#. openerp-web
#: addons/web/static/src/js/search.js:237
@ -381,12 +381,12 @@ msgstr "Editörü göster %d - %s"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:367
msgid "Inherited View"
msgstr ""
msgstr "Devralınan Görünüm"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:371
msgid "Do you really wants to create an inherited view here?"
msgstr ""
msgstr "Devralınmış görünüm oluşturmak istediğinden emin misin?"
#. openerp-web
#: addons/web/static/src/js/view_editor.js:381

1456
addons/web/i18n/uk.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-15 13:19+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"PO-Revision-Date: 2012-02-17 07:29+0000\n"
"Last-Translator: Jeff Wang <wjfonhand@hotmail.com>\n"
"Language-Team: Chinese (Simplified) <zh_CN@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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-18 05:18+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web/static/src/js/chrome.js:172
@ -654,7 +654,7 @@ msgstr "翻译"
#. openerp-web
#: addons/web/static/src/xml/base.xml:44 addons/web/static/src/xml/base.xml:315
msgid "Powered by"
msgstr "动力来自"
msgstr "选择自由,选择"
#. openerp-web
#: addons/web/static/src/xml/base.xml:44 addons/web/static/src/xml/base.xml:315

View File

@ -1,3 +1,7 @@
/*
Fork of some unlicensed library found somewhere, don't hesitate to
patch it directly.
*/
(function($) {
var menu,shadow,trigger,content,hash,currentTarget;
var defaults= {
@ -30,7 +34,7 @@
};
$.fn.contextMenu= function(id,options) {
if(!menu) {
menu=$('<div id="jqContextMenu"></div>').hide().css({
menu=$('<div id="jqContextMenu" class="openerp"></div>').hide().css({
position:'absolute',
zIndex:'2000'
}).appendTo('body').bind('click', function(e) {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,450 @@
/*
Copyright (c) 2011, OpenERP S.A.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
nova = (function() {
var lib = {};
lib.internal = {};
/*
* (Almost) unmodified John Resig's inheritance
*/
/*
* Simple JavaScript Inheritance By John Resig http://ejohn.org/ MIT
* Licensed.
*/
// Inspired by base2 and Prototype
(function() {
var initializing = false, fnTest = /xyz/.test(function() {
xyz;
}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function() {
};
// Create a new Class that inherits from this class
this.Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a web class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" &&
fnTest.test(prop[name]) ?
(function(name, fn) {
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same
// method but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so
// we remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if (!initializing && this.init) {
var ret = this.init.apply(this, arguments);
if (ret) { return ret; }
}
return this;
}
Class.include = function (properties) {
for (var name in properties) {
if (typeof properties[name] !== 'function'
|| !fnTest.test(properties[name])) {
prototype[name] = properties[name];
} else if (typeof prototype[name] === 'function'
&& prototype.hasOwnProperty(name)) {
prototype[name] = (function (name, fn, previous) {
return function () {
var tmp = this._super;
this._super = previous;
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
}
})(name, properties[name], prototype[name]);
} else if (typeof _super[name] === 'function') {
prototype[name] = (function (name, fn) {
return function () {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
}
})(name, properties[name]);
}
}
};
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
}).call(lib);
// end of John Resig's code
lib.DestroyableMixin = {
init: function() {
this.__destroyableDestroyed = false;
},
isDestroyed : function() {
return this.__destroyableDestroyed;
},
destroy : function() {
this.__destroyableDestroyed = true;
}
};
lib.ParentedMixin = _.extend({}, lib.DestroyableMixin, {
__parentedMixin : true,
init: function() {
lib.DestroyableMixin.init.call(this);
this.__parentedChildren = [];
this.__parentedParent = null;
},
setParent : function(parent) {
if (this.getParent()) {
if (this.getParent().__parentedMixin) {
this.getParent().__parentedChildren = _.without(this
.getParent().getChildren(), this);
}
}
this.__parentedParent = parent;
if (parent && parent.__parentedMixin) {
parent.__parentedChildren.push(this);
}
},
getParent : function() {
return this.__parentedParent;
},
getChildren : function() {
return _.clone(this.__parentedChildren);
},
destroy : function() {
_.each(this.getChildren(), function(el) {
el.destroy();
});
this.setParent(undefined);
lib.DestroyableMixin.destroy.call(this);
}
});
/*
* Yes, we steal Backbone's events :)
*
* This class just handle the dispatching of events, it is not meant to be extended,
* nor used directly. All integration with parenting and automatic unregistration of
* events is done in EventDispatcherMixin.
*/
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
lib.internal.Events = lib.Class.extend({
on : function(events, callback, context) {
var ev;
events = events.split(/\s+/);
var calls = this._callbacks || (this._callbacks = {});
while (ev = events.shift()) {
var list = calls[ev] || (calls[ev] = {});
var tail = list.tail || (list.tail = list.next = {});
tail.callback = callback;
tail.context = context;
list.tail = tail.next = {};
}
return this;
},
off : function(events, callback, context) {
var ev, calls, node;
if (!events) {
delete this._callbacks;
} else if (calls = this._callbacks) {
events = events.split(/\s+/);
while (ev = events.shift()) {
node = calls[ev];
delete calls[ev];
if (!callback || !node)
continue;
while ((node = node.next) && node.next) {
if (node.callback === callback
&& (!context || node.context === context))
continue;
this.on(ev, node.callback, node.context);
}
}
}
return this;
},
trigger : function(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks))
return this;
all = calls['all'];
(events = events.split(/\s+/)).push(null);
// Save references to the current heads & tails.
while (event = events.shift()) {
if (all)
events.push({
next : all.next,
tail : all.tail,
event : event
});
if (!(node = calls[event]))
continue;
events.push({
next : node.next,
tail : node.tail
});
}
rest = Array.prototype.slice.call(arguments, 1);
while (node = events.pop()) {
tail = node.tail;
args = node.event ? [ node.event ].concat(rest) : rest;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
return this;
}
});
// end of Backbone's events class
lib.EventDispatcherMixin = _.extend({}, lib.ParentedMixin, {
__eventDispatcherMixin: true,
init: function() {
lib.ParentedMixin.init.call(this);
this.__edispatcherEvents = new lib.internal.Events();
this.__edispatcherRegisteredEvents = [];
},
on: function(events, dest, func) {
var self = this;
events = events.split(/\s+/);
_.each(events, function(eventName) {
self.__edispatcherEvents.on(eventName, func, dest);
if (dest && dest.__eventDispatcherMixin) {
dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
}
});
return this;
},
off: function(events, dest, func) {
var self = this;
events = events.split(/\s+/);
_.each(events, function(eventName) {
self.__edispatcherEvents.off(eventName, func, dest);
if (dest && dest.__eventDispatcherMixin) {
dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
return !(el.name === eventName && el.func === func && el.source === self);
});
}
});
return this;
},
trigger: function(events) {
this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
return this;
},
destroy: function() {
var self = this;
_.each(this.__edispatcherRegisteredEvents, function(event) {
event.source.__edispatcherEvents.off(event.name, event.func, self);
});
this.__edispatcherRegisteredEvents = [];
this.__edispatcherEvents.off();
lib.ParentedMixin.destroy.call(this);
}
});
lib.GetterSetterMixin = _.extend({}, lib.EventDispatcherMixin, {
init: function() {
lib.EventDispatcherMixin.init.call(this);
this.__getterSetterInternalMap = {};
},
set: function(map) {
var self = this;
var changed = false;
_.each(map, function(val, key) {
var tmp = self.__getterSetterInternalMap[key];
if (tmp === val)
return;
changed = true;
self.__getterSetterInternalMap[key] = val;
self.trigger("change:" + key, self, {
oldValue: tmp,
newValue: val
});
});
if (changed)
self.trigger("change", self);
},
get: function(key) {
return this.__getterSetterInternalMap[key];
}
});
lib.Widget = lib.Class.extend(_.extend({}, lib.GetterSetterMixin, {
/**
* Tag name when creating a default $element.
* @type string
*/
tagName: 'div',
/**
* Constructs the widget and sets its parent if a parent is given.
*
* @constructs openerp.web.Widget
* @extends openerp.web.CallbackEnabled
*
* @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
* @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
* to bind the current Widget to an already existing part of the DOM, which is not compatible
* with the DOM insertion methods provided by the current implementation of Widget. So
* for new components this argument should not be provided any more.
*/
init: function(parent) {
lib.GetterSetterMixin.init.call(this);
this.$element = $(document.createElement(this.tagName));
this.setParent(parent);
},
/**
* Destroys the current widget, also destroys all its children before destroying itself.
*/
destroy: function() {
_.each(this.getChildren(), function(el) {
el.destroy();
});
if(this.$element != null) {
this.$element.remove();
}
lib.GetterSetterMixin.destroy.call(this);
},
/**
* Renders the current widget and appends it to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
appendTo: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.appendTo(t);
}, target);
},
/**
* Renders the current widget and prepends it to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
prependTo: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.prependTo(t);
}, target);
},
/**
* Renders the current widget and inserts it after to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
insertAfter: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.insertAfter(t);
}, target);
},
/**
* Renders the current widget and inserts it before to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
insertBefore: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.insertBefore(t);
}, target);
},
/**
* Renders the current widget and replaces the given jQuery object.
*
* @param target A jQuery object or a Widget instance.
*/
replace: function(target) {
return this.__widgetRenderAndInsert(_.bind(function(t) {
this.$element.replaceAll(t);
}, this), target);
},
__widgetRenderAndInsert: function(insertion, target) {
this.renderElement();
insertion(target);
return this.start();
},
/**
* This is the method to implement to render the Widget.
*/
renderElement: function() {},
/**
* Method called after rendering. Mostly used to bind actions, perform asynchronous
* calls, etc...
*
* By convention, the method should return a promise to inform the caller when
* this widget has been initialized.
*
* @returns {jQuery.Deferred}
*/
start: function() {}
}));
return lib;
})();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,226 @@
/**
* QUnit v1.2.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2011 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<link rel="stylesheet" href="qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="qunit.js"></script>
<script type="text/javascript" src="jquery-1.6.4.js"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="../src/nova.js"></script>
<script type="text/javascript" src="test.js"></script>
</head>
<body>
<h1 id="qunit-header">QUnit example</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>

View File

@ -0,0 +1,143 @@
module("Class");
test("base", function() {
ok(!!nova.Class, "Class does exist");
ok(!!nova.Class.extend, "extend does exist");
var Claz = nova.Class.extend({
test: function() {
return "ok";
}
});
equal(new Claz().test(), "ok");
var Claz2 = Claz.extend({
test: function() {
return this._super() + "2";
}
});
equal(new Claz2().test(), "ok2");
});
module("DestroyableMixin");
test("base", function() {
var Claz = nova.Class.extend(_.extend({}, nova.DestroyableMixin, {}));
var x = new Claz();
equal(!!x.isDestroyed(), false);
x.destroy();
equal(x.isDestroyed(), true);
});
module("ParentedMixin");
test("base", function() {
var Claz = nova.Class.extend(_.extend({}, nova.ParentedMixin, {}));
var x = new Claz();
var y = new Claz();
y.setParent(x);
equal(y.getParent(), x);
equal(x.getChildren()[0], y);
x.destroy();
equal(y.isDestroyed(), true);
});
module("Events");
test("base", function() {
var x = new nova.internal.Events();
var tmp = 0;
var fct = function() {tmp = 1;};
x.on("test", fct);
equal(tmp, 0);
x.trigger("test");
equal(tmp, 1);
tmp = 0;
x.off("test", fct);
x.trigger("test");
equal(tmp, 0);
});
module("EventDispatcherMixin");
test("base", function() {
var Claz = nova.Class.extend(_.extend({}, nova.EventDispatcherMixin, {}));
var x = new Claz();
var y = new Claz();
var tmp = 0;
var fct = function() {tmp = 1;};
x.on("test", y, fct);
equal(tmp, 0);
x.trigger("test");
equal(tmp, 1);
tmp = 0;
x.off("test", y, fct);
x.trigger("test");
equal(tmp, 0);
tmp = 0;
x.on("test", y, fct);
y.destroy();
x.trigger("test");
equal(tmp, 0);
});
module("GetterSetterMixin");
test("base", function() {
var Claz = nova.Class.extend(_.extend({}, nova.GetterSetterMixin, {}));
var x = new Claz();
var y = new Claz();
x.set({test: 1});
equal(x.get("test"), 1);
var tmp = 0;
x.on("change:test", y, function(model, options) {
tmp = 1;
equal(options.oldValue, 1);
equal(options.newValue, 2);
equal(x.get("test"), 2);
equal(model, x);
});
x.set({test: 2});
equal(tmp, 1);
});
test("change event only when changed", function() {
var Claz = nova.Class.extend(_.extend({}, nova.GetterSetterMixin, {}));
var x = new Claz();
var exec1 = false;
var exec2 = false;
x.on("change:test", null, function() {exec1 = true;});
x.on("change", null, function() {exec2 = true;});
x.set({"test": 3});
equal(exec1, true);
equal(exec2, true);
exec1 = false;
exec2 = false;
x.set({"test": 3});
equal(exec1, false);
equal(exec2, false);
});
module("Widget");
test("base", function() {
var Claz = nova.Widget.extend({
renderElement: function() {
this.$element.attr("id", "testdiv");
this.$element.html("test");
}
});
var x = new Claz();
x.appendTo($("body"));
var $el = $("#testdiv");
equal($el.length, 1);
equal($el.parents()[0], $("body")[0]);
equal($el.html(), "test");
var y = new Claz(x);
equal(y.getParent(), x);
x.destroy();
$el = $("#testdiv");
equal($el.length, 0);
});

View File

@ -0,0 +1,999 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.1';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
if (index == 0) {
shuffled[0] = value;
} else {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
}
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var result = [];
_.reduce(initial, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
memo[memo.length] = el;
result[result.length] = array[i];
}
return memo;
}, []);
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
func.apply(context, args);
}
whenDone();
throttling = true;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.escape || noMatch, function(match, code) {
return "',_.escape(" + unescape(code) + "),'";
})
.replace(c.interpolate || noMatch, function(match, code) {
return "'," + unescape(code) + ",'";
})
.replace(c.evaluate || noMatch, function(match, code) {
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', '_', tmpl);
if (data) return func(data, _);
return function(data) {
return func.call(this, data, _);
};
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);

View File

@ -0,0 +1,5 @@
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
node: 5adb2d9c89e53a6445e3799f9c4dc9110458c149
branch: default
latesttag: 0.5
latesttagdistance: 9

View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2012
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View File

@ -0,0 +1,197 @@
What
====
``py.js`` is a parser and evaluator of Python expressions, written in
pure javascript.
``py.js`` is not intended to implement a full Python interpreter
(although it could be used for such an effort later on), its
specification document is the `Python 2.7 Expressions spec
<http://docs.python.org/reference/expressions.html>`_ (along with the
lexical analysis part).
Syntax
------
* Lambdas and ternaries should be parsed but are not implemented (in
the evaluator)
* Only floats are implemented, ``int`` literals are parsed as floats.
* Octal and hexadecimal literals are not implemented
* Srings are backed by JavaScript strings and probably behave like
``unicode`` more than like ``str``
* Slices don't work
Builtins
--------
``py.js`` currently implements the following builtins:
``type``
Restricted to creating new types, can't be used to get an object's
type (yet)
``None``
``True``
``False``
``NotImplemented``
Returned from rich comparison methods when the comparison is not
implemented for this combination of operands. In ``py.js``, this
is also the default implementation for all rich comparison methods.
``issubclass``
``object``
``bool``
Does not inherit from ``int``, since ``int`` is not currently
implemented.
``float``
``str``
``tuple``
Constructor/coercer is not implemented, only handles literals
``list``
Same as tuple (``list`` is currently an alias for ``tuple``)
``dict``
Implements just about nothing
Note that most methods are probably missing from all of these.
Data model protocols
--------------------
``py.js`` currently implements the following protocols (or
sub-protocols) of the `Python 2.7 data model
<http://docs.python.org/reference/datamodel.html>`_:
Rich comparisons
Roughly complete implementation but for two limits: ``__eq__`` and
``__ne__`` can't return ``NotImplemented`` (well they can but it's
not going to work right), and the behavior is undefined if a
rich-comparison operation does not return a ``py.bool``.
Also, a ``NotImplemented`` result does not try the reverse
operation, not sure if it's supposed to. It directly falls back to
comparing type names.
Boolean conversion
Implementing ``__nonzero__`` should work.
Customizing attribute access
Protocols for getting and setting attributes (including new-style
extension) fully implemented but for ``__delattr__`` (since
``del`` is a statement)
Descriptor protocol
As with attributes, ``__delete__`` is not implemented.
Callable objects
Collections Abstract Base Classes
Container is the only implemented ABC protocol (ABCs themselves
are not currently implemented) (well technically Callable and
Hashable are kind-of implemented as well)
Numeric type emulation
Operators are implemented (but not tested), ``abs``, ``divmod``
and ``pow`` builtins are not implemented yet. Neither are ``oct``
and ``hex`` but I'm not sure we care (I'm not sure we care about
``pow`` or even ``divmod`` either, for that matter)
Utilities
---------
``py.js`` also provides (and exposes) a few utilities for "userland"
implementation:
``def``
Wraps a native javascript function into a ``py.js`` function, so
that it can be called from native expressions.
Does not ensure the return types are type-compatible with
``py.js`` types.
When accessing instance methods, ``py.js`` automatically wraps
these in a variant of ``py.def`` automatically, to behave as
Python's (bound) methods.
Why
===
Originally, to learn about Pratt parsers (which are very, very good at
parsing expressions with lots of infix or mixfix symbols). The
evaluator part came because "why not" and because I work on a product
with the "feature" of transmitting Python expressions (over the wire)
which the client is supposed to evaluate.
How
===
At this point, only three steps exist in ``py.js``: tokenizing,
parsing and evaluation. It is possible that a compilation step be
added later (for performance reasons).
To evaluate a Python expression, the caller merely needs to call
`py.eval`_. `py.eval`_ takes a mandatory Python
expression to evaluate (as a string) and an optional context, for the
substitution of the free variables in the expression::
> py.eval("type in ('a', 'b', 'c') and foo", {type: 'c', foo: true});
true
This is great for one-shot evaluation of expressions. If the
expression will need to be repeatedly evaluated with the same
parameters, the various parsing and evaluation steps can be performed
separately: `py.eval`_ is really a shortcut for sequentially calling
`py.tokenize`_, `py.parse`_ and `py.evaluate`_.
API
===
.. _py.eval:
``py.eval(expr[, context])``
"Do everything" function, to use for one-shot evaluation of a
Python expression: it will internally handle the tokenizing,
parsing and actual evaluation of the Python expression without
having to perform these separately.
``expr``
Python expression to evaluate
``context``
context dictionary holding the substitutions for the free
variables in the expression
.. _py.tokenize:
``py.tokenize(expr)``
``expr``
Python expression to tokenize
.. _py.parse:
``py.parse(tokens)``
Parses a token stream and returns an abstract syntax tree of the
expression (if the token stream represents a valid Python
expression).
A parse tree is stateless and can be memoized and used multiple
times in separate evaluations.
``tokens``
stream of tokens returned by `py.tokenize`_
.. _py.evaluate:
``py.evaluate(ast[, context])``
``ast``
The output of `py.parse`_
``context``
The evaluation context for the Python expression.

View File

@ -0,0 +1,47 @@
* Parser
since parsing expressions, try with a pratt parser
http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
http://effbot.org/zone/simple-top-down-parsing.htm
Evaluator
---------
* Builtins should be built-in, there should be no need to add e.g. ``py.bool`` to the evaluation context (?)
* Stop busyworking trivial binary operator
* Make it *trivial* to build Python type-wrappers
* Implement Python's `data model protocols
<http://docs.python.org/reference/datamodel.html#basic-customization>`_
for *all* supported operations, optimizations can come later
* Automatically type-wrap everything (for now anyway)
Base type requirements:
***********************
* int
* float
* --str-- unicode
* bool
* dict
* tuple
* list
* ?module
* ?object
* datetime.time
* datetime.timedelta
* NotImplementedType
Base methods requirement
************************
* ``__getattr__``
* ``dict.get``
* ``__len__``
In datamodel, not implemented in any type, untested
***************************************************
* a[b]
* a + b, a - b, a * b, ...
* +a, ~a

View File

@ -0,0 +1,905 @@
var py = {};
(function (py) {
var create = function (o, props) {
function F() {}
F.prototype = o;
var inst = new F;
if (props) {
for(var name in props) {
if(!props.hasOwnProperty(name)) { continue; }
inst[name] = props[name];
}
}
return inst;
};
var symbols = {};
var comparators = {};
var Base = {
nud: function () { throw new Error(this.id + " undefined as prefix"); },
led: function (led) { throw new Error(this.id + " undefined as infix"); },
toString: function () {
if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') {
return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join('');
} else if (this.id === '(end)') {
return '(end)';
} else if (this.id === '(comparator)' ) {
var repr = ['(comparator', this.expressions[0]];
for (var i=0;i<this.operators.length; ++i) {
repr.push(this.operators[i], this.expressions[i+1]);
}
return repr.join(' ') + ')';
}
var out = [this.id, this.first, this.second, this.third]
.filter(function (r){return r}).join(' ');
return '(' + out + ')';
}
};
function symbol(id, bp) {
bp = bp || 0;
var s = symbols[id];
if (s) {
if (bp > s.lbp) {
s.lbp = bp;
}
return s;
}
return symbols[id] = create(Base, {
id: id,
lbp: bp
});
}
function constant(id) {
var s = symbol(id);
s.id = '(constant)';
s.value = id;
s.nud = function () {
return this;
};
}
function prefix(id, bp, nud) {
symbol(id).nud = nud || function () {
this.first = expression(bp);
return this
}
}
function infix(id, bp, led) {
symbol(id, bp).led = led || function (left) {
this.first = left;
this.second = expression(bp);
return this;
}
}
function infixr(id, bp) {
symbol(id, bp).led = function (left) {
this.first = left;
this.second = expression(bp - 1);
return this;
}
}
function comparator(id) {
comparators[id] = true;
var bp = 60;
infix(id, bp, function (left) {
this.id = '(comparator)';
this.operators = [id];
this.expressions = [left, expression(bp)];
while (token.id in comparators) {
this.operators.push(token.id);
advance();
this.expressions.push(
expression(bp));
}
return this;
});
}
constant('None'); constant('False'); constant('True');
symbol('(number)').nud = function () { return this; };
symbol('(name)').nud = function () { return this; };
symbol('(string)').nud = function () { return this; };
symbol('(end)');
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
symbol('else');
infix('=', 10, function (left) {
if (left.id !== '(name)') {
throw new Error("Expected keyword argument name, got " + token.id);
}
this.first = left;
this.second = expression();
return this;
});
symbol('lambda', 20).nud = function () {
this.first = [];
if (token.id !== ':') {
for(;;) {
if (token.id !== '(name)') {
throw new Error('Excepted an argument name');
}
this.first.push(token);
advance();
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(':');
this.second = expression();
return this;
};
infix('if', 20, function (left) {
this.first = left;
this.second = expression();
advance('else');
this.third = expression();
return this;
});
infixr('or', 30); infixr('and', 40); prefix('not', 50);
comparator('in'); comparator('not in');
comparator('is'); comparator('is not');
comparator('<'); comparator('<=');
comparator('>'); comparator('>=');
comparator('<>'); comparator('!='); comparator('==');
infix('|', 70); infix('^', 80), infix('&', 90);
infix('<<', 100); infix('>>', 100);
infix('+', 110); infix('-', 110);
infix('*', 120); infix('/', 120);
infix('//', 120), infix('%', 120);
prefix('-', 130); prefix('+', 130); prefix('~', 130);
infixr('**', 140);
infix('.', 150, function (left) {
if (token.id !== '(name)') {
throw new Error('Expected attribute name, got ' + token.id);
}
this.first = left;
this.second = token;
advance();
return this;
});
symbol('(', 150).nud = function () {
this.first = [];
var comma = false;
if (token.id !== ')') {
while (true) {
if (token.id === ')') {
break;
}
this.first.push(expression());
if (token.id !== ',') {
break;
}
comma = true;
advance(',');
}
}
advance(')');
if (!this.first.length || comma) {
return this;
} else {
return this.first[0];
}
};
symbol('(').led = function (left) {
this.first = left;
this.second = [];
if (token.id !== ")") {
for(;;) {
this.second.push(expression());
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(")");
return this;
};
infix('[', 150, function (left) {
this.first = left;
this.second = expression();
advance("]");
return this;
});
symbol('[').nud = function () {
this.first = [];
if (token.id !== ']') {
for (;;) {
if (token.id === ']') {
break;
}
this.first.push(expression());
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(']');
return this;
};
symbol('{').nud = function () {
this.first = [];
if (token.id !== '}') {
for(;;) {
if (token.id === '}') {
break;
}
var key = expression();
advance(':');
var value = expression();
this.first.push([key, value]);
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance('}');
return this;
};
py.tokenize = (function () {
function group() { return '(' + Array.prototype.join.call(arguments, '|') + ')'; }
var Whitespace = '[ \\f\\t]*';
var Name = '[a-zA-Z_]\\w*';
var DecNumber = '\\d+';
var IntNumber = DecNumber;
var PointFloat = group('\\d+\\.\\d*', '\\.\\d+');
var FloatNumber = PointFloat;
var Number = group(FloatNumber, IntNumber);
var Operator = group("\\*\\*=?", ">>=?", "<<=?", "<>", "!=",
"//=?", "[+\\-*/%&|^=<>]=?", "~");
var Bracket = '[\\[\\]\\(\\)\\{\\}]';
var Special = '[:;.,`@]';
var Funny = group(Operator, Bracket, Special);
var ContStr = group("'[^']*'", '"[^"]*"');
var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);
return function tokenize(s) {
var max=s.length, tokens = [];
// /g flag makes repeated exec() have memory
var pseudoprog = new RegExp(PseudoToken, 'g');
while(pseudoprog.lastIndex < max) {
var pseudomatch = pseudoprog.exec(s);
if (!pseudomatch) {
// if match failed on trailing whitespace, end tokenizing
if (/^\s+$/.test(s.slice(end))) {
break;
}
throw new Error('Failed to tokenize <<' + s
+ '>> at index ' + (end || 0)
+ '; parsed so far: ' + tokens);
}
var start = pseudomatch.index, end = pseudoprog.lastIndex;
// strip leading space caught by Whitespace
var token = s.slice(start, end).replace(new RegExp('^' + Whitespace), '');
var initial = token[0];
if (/\d/.test(initial) || (initial === '.' && token !== '.')) {
tokens.push(create(symbols['(number)'], {
value: parseFloat(token)
}));
} else if (/'|"/.test(initial)) {
tokens.push(create(symbols['(string)'], {
value: token.slice(1, -1)
}));
} else if (token in symbols) {
var symbol;
// transform 'not in' and 'is not' in a single token
if (token === 'in' && tokens[tokens.length-1].id === 'not') {
symbol = symbols['not in'];
tokens.pop();
} else if (token === 'not' && tokens[tokens.length-1].id === 'is') {
symbol = symbols['is not'];
tokens.pop();
} else {
symbol = symbols[token];
}
tokens.push(create(symbol));
} else if (/[_a-zA-Z]/.test(initial)) {
tokens.push(create(symbols['(name)'], {
value: token
}));
} else {
throw new Error("Tokenizing failure of <<" + s + ">> at index " + start
+ " for token [[" + token + "]]"
+ "; parsed so far: " + tokens);
}
}
tokens.push(create(symbols['(end)']));
return tokens;
}
})();
var token, next;
function expression(rbp) {
rbp = rbp || 0;
var t = token;
token = next();
var left = t.nud();
while (rbp < token.lbp) {
t = token;
token = next();
left = t.led(left);
}
return left;
}
function advance(id) {
if (id && token.id !== id) {
throw new Error(
'Expected "' + id + '", got "' + token.id + '"');
}
token = next();
}
function PY_ensurepy(val, name) {
switch (val) {
case undefined:
throw new Error("NameError: name '" + name + "' is not defined");
case null:
return py.None;
case true:
return py.True;
case false:
return py.False;
}
if (val instanceof py.object
|| val === py.object
|| py.issubclass.__call__([val, py.object]) === py.True) {
return val;
}
switch (typeof val) {
case 'number':
return new py.float(val);
case 'string':
return new py.str(val);
case 'function':
return new py.def(val);
}
throw new Error("Could not convert " + val + " to a pyval");
}
// Builtins
py.type = function type(constructor, base, dict) {
var proto;
if (!base) {
base = py.object;
}
proto = constructor.prototype = create(base.prototype);
proto.constructor = constructor;
if (dict) {
for(var k in dict) {
if (!dict.hasOwnProperty(k)) { continue; }
proto[k] = dict[k];
}
}
constructor.__call__ = function () {
// create equivalent type with same prototype
var instance = create(proto);
// call actual constructor
var res = constructor.apply(instance, arguments);
// return result of constructor if any, otherwise instance
return res || instance;
};
return constructor;
};
var hash_counter = 0;
py.object = py.type(function object() {}, {}, {
// Basic customization
__hash__: function () {
if (this._hash) {
return this._hash;
}
return this._hash = hash_counter++;
},
__eq__: function (other) {
return (this === other) ? py.True : py.False;
},
__ne__: function (other) {
if (this.__eq__(other) === py.True) {
return py.False;
} else {
return py.True;
}
},
__lt__: function () { return py.NotImplemented; },
__le__: function () { return py.NotImplemented; },
__ge__: function () { return py.NotImplemented; },
__gt__: function () { return py.NotImplemented; },
__str__: function () {
return this.__unicode__();
},
__unicode__: function () {
// TODO: return python string
return '<object ' + this.constructor.name + '>';
},
__nonzero__: function () {
return py.True;
},
// Attribute access
__getattribute__: function (name) {
if (name in this) {
var val = this[name];
if ('__get__' in val) {
// TODO: second argument should be class
return val.__get__(this);
}
if (typeof val === 'function' && !this.hasOwnProperty(name)) {
// val is a method from the class
return new PY_instancemethod(val, this);
}
return PY_ensurepy(val);
}
if ('__getattr__' in this) {
return this.__getattr__(name);
}
throw new Error("AttributeError: object has no attribute '" + name +"'");
},
__setattr__: function (name, value) {
if (name in this && '__set__' in this[name]) {
this[name].__set__(this, value);
}
this[name] = value;
},
// no delattr, because no 'del' statement
// Conversion
toJSON: function () {
throw new Error(this.constructor.name + ' can not be converted to JSON');
}
});
var NoneType = py.type(function NoneType() {}, py.object, {
__nonzero__: function () { return py.False; },
toJSON: function () { return null; }
});
py.None = new NoneType();
var NotImplementedType = py.type(function NotImplementedType(){});
py.NotImplemented = new NotImplementedType();
var booleans_initialized = false;
py.bool = py.type(function bool(value) {
value = (value instanceof Array) ? value[0] : value;
// The only actual instance of py.bool should be py.True
// and py.False. Return the new instance of py.bool if we
// are initializing py.True and py.False, otherwise always
// return either py.True or py.False.
if (!booleans_initialized) {
return;
}
if (value === undefined) { return py.False; }
return value.__nonzero__() === py.True ? py.True : py.False;
}, py.object, {
__nonzero__: function () { return this; },
toJSON: function () { return this === py.True; }
});
py.True = new py.bool();
py.False = new py.bool();
booleans_initialized = true;
py.float = py.type(function float(value) {
value = (value instanceof Array) ? value[0] : value;
if (value === undefined) { this._value = 0; return; }
if (value instanceof py.float) { return value; }
if (typeof value === 'number' || value instanceof Number) {
this._value = value;
return;
}
if (typeof value === 'string' || value instanceof String) {
this._value = parseFloat(value);
return;
}
if (value instanceof py.object && '__float__' in value) {
var res = value.__float__();
if (res instanceof py.float) {
return res;
}
throw new Error('TypeError: __float__ returned non-float (type ' +
res.constructor.name + ')');
}
throw new Error('TypeError: float() argument must be a string or a number');
}, py.object, {
__eq__: function (other) {
return this._value === other._value ? py.True : py.False;
},
__lt__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return this._value < other._value ? py.True : py.False;
},
__le__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return this._value <= other._value ? py.True : py.False;
},
__gt__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return this._value > other._value ? py.True : py.False;
},
__ge__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return this._value >= other._value ? py.True : py.False;
},
__add__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return new py.float(this._value + other._value);
},
__neg__: function () {
return new py.float(-this._value);
},
__sub__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return new py.float(this._value - other._value);
},
__mul__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return new py.float(this._value * other._value);
},
__div__: function (other) {
if (!(other instanceof py.float)) { return py.NotImplemented; }
return new py.float(this._value / other._value);
},
__nonzero__: function () {
return this._value ? py.True : py.False;
},
toJSON: function () {
return this._value;
}
});
py.str = py.type(function str(s) {
s = (s instanceof Array) ? s[0] : s;
if (s === undefined) { this._value = ''; return; }
if (s instanceof py.str) { return s; }
if (typeof s === 'string' || s instanceof String) {
this._value = s;
return;
}
var v = s.__str__();
if (v instanceof py.str) { return v; }
throw new Error('TypeError: __str__ returned non-string (type ' +
v.constructor.name + ')');
}, py.object, {
__eq__: function (other) {
if (other instanceof py.str && this._value === other._value) {
return py.True;
}
return py.False;
},
__lt__: function (other) {
if (!(other instanceof py.str)) { return py.NotImplemented; }
return this._value < other._value ? py.True : py.False;
},
__le__: function (other) {
if (!(other instanceof py.str)) { return py.NotImplemented; }
return this._value <= other._value ? py.True : py.False;
},
__gt__: function (other) {
if (!(other instanceof py.str)) { return py.NotImplemented; }
return this._value > other._value ? py.True : py.False;
},
__ge__: function (other) {
if (!(other instanceof py.str)) { return py.NotImplemented; }
return this._value >= other._value ? py.True : py.False;
},
__add__: function (other) {
if (!(other instanceof py.str)) { return py.NotImplemented; }
return new py.str(this._value + other._value);
},
__nonzero__: function () {
return this._value.length ? py.True : py.False;
},
__contains__: function (s) {
return (this._value.indexOf(s._value) !== -1) ? py.True : py.False;
},
toJSON: function () {
return this._value;
}
});
py.tuple = py.type(function tuple() {}, null, {
__contains__: function (value) {
for(var i=0, len=this.values.length; i<len; ++i) {
if (this.values[i].__eq__(value) === py.True) {
return py.True;
}
}
return py.False;
},
toJSON: function () {
var out = [];
for (var i=0; i<this.values.length; ++i) {
out.push(this.values[i].toJSON());
}
return out;
}
});
py.list = py.tuple;
py.dict = py.type(function dict() {
this._store = {};
}, py.object, {
__setitem__: function (key, value) {
this._store[key.__hash__()] = [key, value];
},
toJSON: function () {
var out = {};
for(var k in this._store) {
var item = this._store[k];
out[item[0].toJSON()] = item[1].toJSON();
}
return out;
}
});
py.def = py.type(function def(nativefunc) {
this._inst = null;
this._func = nativefunc;
}, py.object, {
__call__: function (args, kwargs) {
// don't want to rewrite __call__ for instancemethod
return this._func.call(this._inst, args, kwargs);
},
toJSON: function () {
return this._func;
}
});
var PY_instancemethod = py.type(function instancemethod(nativefunc, instance, _cls) {
// could also use bind?
this._inst = instance;
this._func = nativefunc;
}, py.def, {});
py.issubclass = new py.def(function issubclass(args) {
var derived = args[0], parent = args[1];
// still hurts my brain that this can work
return derived.prototype instanceof py.object
? py.True
: py.False;
});
// All binary operators with fallbacks, so they can be applied generically
var PY_operators = {
'==': ['eq', 'eq', function (a, b) { return a === b; }],
'!=': ['ne', 'ne', function (a, b) { return a !== b; }],
'<': ['lt', 'gt', function (a, b) {return a.constructor.name < b.constructor.name;}],
'<=': ['le', 'ge', function (a, b) {return a.constructor.name <= b.constructor.name;}],
'>': ['gt', 'lt', function (a, b) {return a.constructor.name > b.constructor.name;}],
'>=': ['ge', 'le', function (a, b) {return a.constructor.name >= b.constructor.name;}],
'+': ['add', 'radd'],
'-': ['sub', 'rsub'],
'*': ['mul', 'rmul'],
'/': ['div', 'rdiv'],
'//': ['floordiv', 'rfloordiv'],
'%': ['mod', 'rmod'],
'**': ['pow', 'rpow'],
'<<': ['lshift', 'rlshift'],
'>>': ['rshift', 'rrshift'],
'&': ['and', 'rand'],
'^': ['xor', 'rxor'],
'|': ['or', 'ror']
};
/**
* Implements operator fallback/reflection.
*
* First two arguments are the objects to apply the operator on,
* in their actual order (ltr).
*
* Third argument is the actual operator.
*
* If the operator methods raise exceptions, those exceptions are
* not intercepted.
*/
var PY_op = function (o1, o2, op) {
var r;
var methods = PY_operators[op];
var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__';
var otherwise = methods[2];
if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
return r;
}
if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
return r;
}
if (otherwise) {
return PY_ensurepy(otherwise(o1, o2));
}
throw new Error(
"TypeError: unsupported operand type(s) for " + op + ": '"
+ o1.constructor.name + "' and '"
+ o2.constructor.name + "'");
};
var PY_builtins = {
type: py.type,
None: py.None,
True: py.True,
False: py.False,
NotImplemented: py.NotImplemented,
object: py.object,
bool: py.bool,
float: py.float,
tuple: py.tuple,
list: py.list,
dict: py.dict,
issubclass: py.issubclass
};
py.parse = function (toks) {
var index = 0;
token = toks[0];
next = function () { return toks[++index]; };
return expression();
};
var evaluate_operator = function (operator, a, b) {
var v;
switch (operator) {
case 'is': return a === b ? py.True : py.False;
case 'is not': return a !== b ? py.True : py.False;
case 'in':
return b.__contains__(a);
case 'not in':
return b.__contains__(a) === py.True ? py.False : py.True;
case '==': case '!=':
case '<': case '<=':
case '>': case '>=':
return PY_op(a, b, operator);
}
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
};
py.evaluate = function (expr, context) {
context = context || {};
switch (expr.id) {
case '(name)':
var val = context[expr.value];
if (val === undefined && expr.value in PY_builtins) {
return PY_builtins[expr.value];
}
return PY_ensurepy(val, expr.value);
case '(string)':
return new py.str(expr.value);
case '(number)':
return new py.float(expr.value);
case '(constant)':
switch (expr.value) {
case 'None': return py.None;
case 'False': return py.False;
case 'True': return py.True;
}
throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
case '(comparator)':
var result, left = py.evaluate(expr.expressions[0], context);
for(var i=0; i<expr.operators.length; ++i) {
result = evaluate_operator(
expr.operators[i],
left,
left = py.evaluate(expr.expressions[i+1], context));
if (result === py.False) { return py.False; }
}
return py.True;
case 'not':
return py.evaluate(expr.first, context).__nonzero__() === py.True
? py.False
: py.True;
case 'and':
var and_first = py.evaluate(expr.first, context);
if (and_first.__nonzero__() === py.True) {
return py.evaluate(expr.second, context);
}
return and_first;
case 'or':
var or_first = py.evaluate(expr.first, context);
if (or_first.__nonzero__() === py.True) {
return or_first
}
return py.evaluate(expr.second, context);
case '(':
if (expr.second) {
var callable = py.evaluate(expr.first, context);
var args = [], kwargs = {};
for (var jj=0; jj<expr.second.length; ++jj) {
var arg = expr.second[jj];
if (arg.id !== '=') {
// arg
args.push(py.evaluate(arg, context));
} else {
// kwarg
kwargs[arg.first.value] =
py.evaluate(arg.second, context);
}
}
return callable.__call__(args, kwargs);
}
var tuple_exprs = expr.first,
tuple_values = [];
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
tuple_values.push(py.evaluate(
tuple_exprs[j], context));
}
var t = new py.tuple();
t.values = tuple_values;
return t;
case '[':
if (expr.second) {
return py.evaluate(expr.first, context)
.__getitem__(expr.evaluate(expr.second, context));
}
var list_exprs = expr.first, list_values = [];
for (var k=0; k<list_exprs.length; ++k) {
list_values.push(py.evaluate(
list_exprs[k], context));
}
var l = new py.list();
l.values = list_values;
return l;
case '{':
var dict_exprs = expr.first, dict = new py.dict;
for(var l=0; l<dict_exprs.length; ++l) {
dict.__setitem__(
py.evaluate(dict_exprs[l][0], context),
py.evaluate(dict_exprs[l][1], context));
}
return dict;
case '.':
if (expr.second.id !== '(name)') {
throw new Error('SyntaxError: ' + expr);
}
return py.evaluate(expr.first, context)
.__getattribute__(expr.second.value);
// numerical operators
case '~':
return (py.evaluate(expr.first, context)).__invert__();
case '+':
if (!expr.second) {
return (py.evaluate(expr.first, context)).__pos__();
}
case '-':
if (!expr.second) {
return (py.evaluate(expr.first, context)).__neg__();
}
case '*': case '/': case '//':
case '%':
case '**':
case '<<': case '>>':
case '&': case '^': case '|':
return PY_op(
py.evaluate(expr.first, context),
py.evaluate(expr.second, context),
expr.id);
default:
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
}
};
py.eval = function (str, context) {
return py.evaluate(
py.parse(
py.tokenize(
str)),
context).toJSON();
}
})(typeof exports === 'undefined' ? py : exports);

View File

@ -0,0 +1,132 @@
var py = require('../lib/py.js'),
expect = require('expect.js');
expect.Assertion.prototype.tokens = function (n) {
var length = this.obj.length;
this.assert(length === n + 1,
'expected ' + this.obj + ' to have ' + n + ' tokens',
'expected ' + this.obj + ' to not have ' + n + ' tokens');
this.assert(this.obj[length-1].id === '(end)',
'expected ' + this.obj + ' to have and end token',
'expected ' + this.obj + ' to not have an end token');
};
expect.Assertion.prototype.named = function (value) {
this.assert(this.obj.id === '(name)',
'expected ' + this.obj + ' to be a name token',
'expected ' + this.obj + ' not to be a name token');
this.assert(this.obj.value === value,
'expected ' + this.obj + ' to have tokenized ' + value,
'expected ' + this.obj + ' not to have tokenized ' + value);
};
expect.Assertion.prototype.constant = function (value) {
this.assert(this.obj.id === '(constant)',
'expected ' + this.obj + ' to be a constant token',
'expected ' + this.obj + ' not to be a constant token');
this.assert(this.obj.value === value,
'expected ' + this.obj + ' to have tokenized ' + value,
'expected ' + this.obj + ' not to have tokenized ' + value);
};
expect.Assertion.prototype.number = function (value) {
this.assert(this.obj.id === '(number)',
'expected ' + this.obj + ' to be a number token',
'expected ' + this.obj + ' not to be a number token');
this.assert(this.obj.value === value,
'expected ' + this.obj + ' to have tokenized ' + value,
'expected ' + this.obj + ' not to have tokenized ' + value);
};
expect.Assertion.prototype.string = function (value) {
this.assert(this.obj.id === '(string)',
'expected ' + this.obj + ' to be a string token',
'expected ' + this.obj + ' not to be a string token');
this.assert(this.obj.value === value,
'expected ' + this.obj + ' to have tokenized ' + value,
'expected ' + this.obj + ' not to have tokenized ' + value);
};
describe('Tokenizer', function () {
describe('simple literals', function () {
it('tokenizes numbers', function () {
var toks = py.tokenize('1');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.number(1);
var toks = py.tokenize('-1');
expect(toks).to.have.tokens(2);
expect(toks[0].id).to.be('-');
expect(toks[1]).to.be.number(1);
var toks = py.tokenize('1.2');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.number(1.2);
var toks = py.tokenize('.42');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.number(0.42);
});
it('tokenizes strings', function () {
var toks = py.tokenize('"foo"');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.string('foo');
var toks = py.tokenize("'foo'");
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.string('foo');
});
it('tokenizes bare names', function () {
var toks = py.tokenize('foo');
expect(toks).to.have.tokens(1);
expect(toks[0].id).to.be('(name)');
expect(toks[0].value).to.be('foo');
});
it('tokenizes constants', function () {
var toks = py.tokenize('None');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.constant('None');
var toks = py.tokenize('True');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.constant('True');
var toks = py.tokenize('False');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.constant('False');
});
it('does not fuck up on trailing spaces', function () {
var toks = py.tokenize('None ');
expect(toks).to.have.tokens(1);
expect(toks[0]).to.be.constant('None');
});
});
describe('collections', function () {
it('tokenizes opening and closing symbols', function () {
var toks = py.tokenize('()');
expect(toks).to.have.tokens(2);
expect(toks[0].id).to.be('(');
expect(toks[1].id).to.be(')');
});
});
describe('functions', function () {
it('tokenizes kwargs', function () {
var toks = py.tokenize('foo(bar=3, qux=4)');
expect(toks).to.have.tokens(10);
});
});
});
describe('Parser', function () {
describe('functions', function () {
var ast = py.parse(py.tokenize('foo(bar=3, qux=4)'));
expect(ast.id).to.be('(');
expect(ast.first).to.be.named('foo');
args = ast.second;
expect(args[0].id).to.be('=');
expect(args[0].first).to.be.named('bar');
expect(args[0].second).to.be.number(3);
expect(args[1].id).to.be('=');
expect(args[1].first).to.be.named('qux');
expect(args[1].second).to.be.number(4);
});
});

View File

@ -0,0 +1,379 @@
var py = require('../lib/py.js'),
expect = require('expect.js');
var ev = function (str, context) {
return py.evaluate(py.parse(py.tokenize(str)), context);
};
describe('Literals', function () {
describe('Number', function () {
it('should have the right type', function () {
expect(ev('1')).to.be.a(py.float);
});
it('should yield the corresponding JS value', function () {
expect(py.eval('1')).to.be(1);
expect(py.eval('42')).to.be(42);
expect(py.eval('9999')).to.be(9999);
});
it('should correctly handle negative literals', function () {
expect(py.eval('-1')).to.be(-1);
expect(py.eval('-42')).to.be(-42);
expect(py.eval('-9999')).to.be(-9999);
});
it('should correctly handle float literals', function () {
expect(py.eval('.42')).to.be(0.42);
expect(py.eval('1.2')).to.be(1.2);
});
});
describe('Booleans', function () {
it('should have the right type', function () {
expect(ev('False')).to.be.a(py.bool);
expect(ev('True')).to.be.a(py.bool);
});
it('should yield the corresponding JS value', function () {
expect(py.eval('False')).to.be(false);
expect(py.eval('True')).to.be(true);
});
});
describe('None', function () {
it('should have the right type', function () {
expect(ev('None')).to.be.a(py.object)
});
it('should yield a JS null', function () {
expect(py.eval('None')).to.be(null);
});
});
describe('String', function () {
it('should have the right type', function () {
expect(ev('"foo"')).to.be.a(py.str);
expect(ev("'foo'")).to.be.a(py.str);
});
it('should yield the corresponding JS string', function () {
expect(py.eval('"somestring"')).to.be('somestring');
expect(py.eval("'somestring'")).to.be('somestring');
});
});
describe('Tuple', function () {
it('shoud have the right type', function () {
expect(ev('()')).to.be.a(py.tuple);
});
it('should map to a JS array', function () {
expect(py.eval('()')).to.eql([]);
expect(py.eval('(1, 2, 3)')).to.eql([1, 2, 3]);
});
});
describe('List', function () {
it('shoud have the right type', function () {
expect(ev('[]')).to.be.a(py.list);
});
it('should map to a JS array', function () {
expect(py.eval('[]')).to.eql([]);
expect(py.eval('[1, 2, 3]')).to.eql([1, 2, 3]);
});
});
describe('Dict', function () {
it('shoud have the right type', function () {
expect(ev('{}')).to.be.a(py.dict);
});
it('should map to a JS object', function () {
expect(py.eval("{}")).to.eql({});
expect(py.eval("{'foo': 1, 'bar': 2}"))
.to.eql({foo: 1, bar: 2});
});
});
});
describe('Free variables', function () {
it('should return its identity', function () {
expect(py.eval('foo', {foo: 1})).to.be(1);
expect(py.eval('foo', {foo: true})).to.be(true);
expect(py.eval('foo', {foo: false})).to.be(false);
expect(py.eval('foo', {foo: null})).to.be(null);
expect(py.eval('foo', {foo: 'bar'})).to.be('bar');
});
});
describe('Comparisons', function () {
describe('equality', function () {
it('should work with literals', function () {
expect(py.eval('1 == 1')).to.be(true);
expect(py.eval('"foo" == "foo"')).to.be(true);
expect(py.eval('"foo" == "bar"')).to.be(false);
});
it('should work with free variables', function () {
expect(py.eval('1 == a', {a: 1})).to.be(true);
expect(py.eval('foo == "bar"', {foo: 'bar'})).to.be(true);
expect(py.eval('foo == "bar"', {foo: 'qux'})).to.be(false);
});
});
describe('inequality', function () {
it('should work with literals', function () {
expect(py.eval('1 != 2')).to.be(true);
expect(py.eval('"foo" != "foo"')).to.be(false);
expect(py.eval('"foo" != "bar"')).to.be(true);
});
it('should work with free variables', function () {
expect(py.eval('1 != a', {a: 42})).to.be(true);
expect(py.eval('foo != "bar"', {foo: 'bar'})).to.be(false);
expect(py.eval('foo != "bar"', {foo: 'qux'})).to.be(true);
expect(py.eval('foo != bar', {foo: 'qux', bar: 'quux'}))
.to.be(true);
});
});
describe('rich comparisons', function () {
it('should work with numbers', function () {
expect(py.eval('3 < 5')).to.be(true);
expect(py.eval('5 >= 3')).to.be(true);
expect(py.eval('3 >= 3')).to.be(true);
expect(py.eval('3 > 5')).to.be(false);
});
it('should support comparison chains', function () {
expect(py.eval('1 < 3 < 5')).to.be(true);
expect(py.eval('5 > 3 > 1')).to.be(true);
expect(py.eval('1 < 3 > 2 == 2 > -2')).to.be(true);
});
it('should compare strings', function () {
expect(py.eval('date >= current',
{date: '2010-06-08', current: '2010-06-05'}))
.to.be(true);
expect(py.eval('state == "cancel"', {state: 'cancel'}))
.to.be(true);
expect(py.eval('state == "cancel"', {state: 'open'}))
.to.be(false);
});
});
describe('missing eq/neq', function () {
it('should fall back on identity', function () {
var typ = new py.type(function MyType() {});
expect(py.eval('MyType() == MyType()', {MyType: typ})).to.be(false);
});
});
describe('un-comparable types', function () {
it('should default to type-name ordering', function () {
var t1 = new py.type(function Type1() {});
var t2 = new py.type(function Type2() {});
expect(py.eval('T1() < T2()', {T1: t1, T2: t2})).to.be(true);
expect(py.eval('T1() > T2()', {T1: t1, T2: t2})).to.be(false);
});
it('should handle native stuff', function () {
expect(py.eval('None < 42')).to.be(true);
expect(py.eval('42 > None')).to.be(true);
expect(py.eval('None > 42')).to.be(false);
expect(py.eval('None < False')).to.be(true);
expect(py.eval('None < True')).to.be(true);
expect(py.eval('False > None')).to.be(true);
expect(py.eval('True > None')).to.be(true);
expect(py.eval('None > False')).to.be(false);
expect(py.eval('None > True')).to.be(false);
expect(py.eval('False < ""')).to.be(true);
expect(py.eval('"" > False')).to.be(true);
expect(py.eval('False > ""')).to.be(false);
});
});
});
describe('Boolean operators', function () {
it('should work', function () {
expect(py.eval("foo == 'foo' or foo == 'bar'",
{foo: 'bar'}))
.to.be(true);;
expect(py.eval("foo == 'foo' and bar == 'bar'",
{foo: 'foo', bar: 'bar'}))
.to.be(true);;
});
it('should be lazy', function () {
// second clause should nameerror if evaluated
expect(py.eval("foo == 'foo' or bar == 'bar'",
{foo: 'foo'}))
.to.be(true);;
expect(py.eval("foo == 'foo' and bar == 'bar'",
{foo: 'bar'}))
.to.be(false);;
});
it('should return the actual object', function () {
expect(py.eval('"foo" or "bar"')).to.be('foo');
expect(py.eval('None or "bar"')).to.be('bar');
expect(py.eval('False or None')).to.be(null);
expect(py.eval('0 or 1')).to.be(1);
});
});
describe('Containment', function () {
describe('in sequences', function () {
it('should match collection items', function () {
expect(py.eval("'bar' in ('foo', 'bar')"))
.to.be(true);
expect(py.eval('1 in (1, 2, 3, 4)'))
.to.be(true);;
expect(py.eval('1 in (2, 3, 4)'))
.to.be(false);;
expect(py.eval('"url" in ("url",)'))
.to.be(true);
expect(py.eval('"foo" in ["foo", "bar"]'))
.to.be(true);
});
it('should not be recursive', function () {
expect(py.eval('"ur" in ("url",)'))
.to.be(false);;
});
it('should be negatable', function () {
expect(py.eval('1 not in (2, 3, 4)')).to.be(true);
expect(py.eval('"ur" not in ("url",)')).to.be(true);
expect(py.eval('-2 not in (1, 2, 3)')).to.be(true);
});
});
describe('in dict', function () {
// TODO
});
describe('in strings', function () {
it('should match the whole string', function () {
expect(py.eval('"view" in "view"')).to.be(true);
expect(py.eval('"bob" in "view"')).to.be(false);
});
it('should match substrings', function () {
expect(py.eval('"ur" in "url"')).to.be(true);
});
});
});
describe('Conversions', function () {
describe('to bool', function () {
describe('strings', function () {
it('should be true if non-empty', function () {
expect(py.eval('bool(date_deadline)',
{date_deadline: '2008'}))
.to.be(true);
});
it('should be false if empty', function () {
expect(py.eval('bool(s)', {s: ''})) .to.be(false);
});
});
});
});
describe('Attribute access', function () {
it("should return the attribute's value", function () {
var o = new py.object();
o.bar = py.True;
expect(py.eval('foo.bar', {foo: o})).to.be(true);
o.bar = py.False;
expect(py.eval('foo.bar', {foo: o})).to.be(false);
});
it("should work with functions", function () {
var o = new py.object();
o.bar = new py.def(function () {
return new py.str("ok");
});
expect(py.eval('foo.bar()', {foo: o})).to.be('ok');
});
it('should not convert function attributes into methods', function () {
var o = new py.object();
o.bar = new py.type(function bar() {});
o.bar.__getattribute__ = function () {
return o.bar.baz;
}
o.bar.baz = py.True;
expect(py.eval('foo.bar.baz', {foo: o})).to.be(true);
});
it('should work on instance attributes', function () {
var typ = py.type(function MyType() {
this.attr = new py.float(3);
}, py.object, {});
expect(py.eval('MyType().attr', {MyType: typ})).to.be(3);
});
it('should work on class attributes', function () {
var typ = py.type(function MyType() {}, py.object, {
attr: new py.float(3)
});
expect(py.eval('MyType().attr', {MyType: typ})).to.be(3);
});
it('should work with methods', function () {
var typ = py.type(function MyType() {
this.attr = new py.float(3);
}, py.object, {
some_method: function () { return new py.str('ok'); },
get_attr: function () { return this.attr; }
});
expect(py.eval('MyType().some_method()', {MyType: typ})).to.be('ok');
expect(py.eval('MyType().get_attr()', {MyType: typ})).to.be(3);
});
});
describe('Callables', function () {
it('should wrap JS functions', function () {
expect(py.eval('foo()', {foo: function foo() { return new py.float(3); }}))
.to.be(3);
});
it('should work on custom types', function () {
var typ = py.type(function MyType() {}, py.object, {
toJSON: function () { return true; }
});
expect(py.eval('MyType()', {MyType: typ})).to.be(true);
});
it('should accept kwargs', function () {
expect(py.eval('foo(ok=True)', {
foo: function foo() { return py.True; }
})).to.be(true);
});
it('should be able to get its kwargs', function () {
expect(py.eval('foo(ok=True)', {
foo: function foo(args, kwargs) { return kwargs.ok; }
})).to.be(true);
});
it('should be able to have both args and kwargs', function () {
expect(py.eval('foo(1, 2, 3, ok=True, nok=False)', {
foo: function (args, kwargs) {
expect(args).to.have.length(3);
expect(args[0].toJSON()).to.be(1);
expect(kwargs).to.only.have.keys('ok', 'nok')
expect(kwargs.nok.toJSON()).to.be(false);
return kwargs.ok;
}
})).to.be(true);
});
});
describe('issubclass', function () {
it('should say a type is its own subclass', function () {
expect(py.issubclass.__call__([py.dict, py.dict]).toJSON())
.to.be(true);
expect(py.eval('issubclass(dict, dict)'))
.to.be(true);
});
it('should work with subtypes', function () {
expect(py.issubclass.__call__([py.bool, py.object]).toJSON())
.to.be(true);
});
});
describe('builtins', function () {
it('should aways be available', function () {
expect(py.eval('bool("foo")')).to.be(true);
});
});
describe('numerical protocols', function () {
describe('True numbers (float)', function () {
describe('Basic arithmetic', function () {
it('can be added', function () {
expect(py.eval('1 + 1')).to.be(2);
expect(py.eval('1.5 + 2')).to.be(3.5);
expect(py.eval('1 + -1')).to.be(0);
});
it('can be subtracted', function () {
expect(py.eval('1 - 1')).to.be(0);
expect(py.eval('1.5 - 2')).to.be(-0.5);
expect(py.eval('2 - 1.5')).to.be(0.5);
});
it('can be multiplied', function () {
expect(py.eval('1 * 3')).to.be(3);
expect(py.eval('0 * 5')).to.be(0);
expect(py.eval('42 * -2')).to.be(-84);
});
it('can be divided', function () {
expect(py.eval('1 / 2')).to.be(0.5);
expect(py.eval('2 / 1')).to.be(2);
});
});
});
describe('Strings', function () {
describe('Basic arithmetics operators', function () {
it('can be added (concatenation)', function () {
expect(py.eval('"foo" + "bar"')).to.be('foobar');
});
});
});
});

View File

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

View File

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

View File

@ -1,14 +0,0 @@
* Parser
since parsing expressions, try with a pratt parser
http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
http://effbot.org/zone/simple-top-down-parsing.htm
Evaluator
---------
* Stop busyworking trivial binary operator
* Make it *trivial* to build Python type-wrappers
* Implement Python's `data model
protocols<http://docs.python.org/reference/datamodel.html#basic-customization>`_
for *all* supported operations, optimizations can come later
* Automatically type-wrap everything (for now anyway)

View File

@ -1,546 +0,0 @@
var py = {};
(function (exports) {
var NUMBER = /^\d$/,
NAME_FIRST = /^[a-zA-Z_]$/,
NAME = /^[a-zA-Z0-9_]$/;
var create = function (o, props) {
function F() {}
F.prototype = o;
var inst = new F;
for(var name in props) {
if(!props.hasOwnProperty(name)) { continue; }
inst[name] = props[name];
}
return inst;
};
var symbols = {};
var comparators = {};
var Base = {
nud: function () { throw new Error(this.id + " undefined as prefix"); },
led: function (led) { throw new Error(this.id + " undefined as infix"); },
toString: function () {
if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') {
return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join('');
} else if (this.id === '(end)') {
return '(end)';
} else if (this.id === '(comparator)' ) {
var repr = ['(comparator', this.expressions[0]];
for (var i=0;i<this.operators.length; ++i) {
repr.push(this.operators[i], this.expressions[i+1]);
}
return repr.join(' ') + ')';
}
var out = [this.id, this.first, this.second, this.third]
.filter(function (r){return r}).join(' ');
return '(' + out + ')';
}
};
function symbol(id, bp) {
bp = bp || 0;
var s = symbols[id];
if (s) {
if (bp > s.lbp) {
s.lbp = bp;
}
return s;
}
return symbols[id] = create(Base, {
id: id,
lbp: bp
});
}
function constant(id) {
symbol(id).nud = function () {
this.id = "(constant)";
this.value = id;
return this;
};
}
function prefix(id, bp, nud) {
symbol(id).nud = nud || function () {
this.first = expression(bp);
return this
}
}
function infix(id, bp, led) {
symbol(id, bp).led = led || function (left) {
this.first = left;
this.second = expression(bp);
return this;
}
}
function infixr(id, bp) {
symbol(id, bp).led = function (left) {
this.first = left;
this.second = expression(bp - 1);
return this;
}
}
function comparator(id) {
comparators[id] = true;
var bp = 60;
infix(id, bp, function (left) {
this.id = '(comparator)';
this.operators = [id];
this.expressions = [left, expression(bp)];
while (token.id in comparators) {
this.operators.push(token.id);
advance();
this.expressions.push(
expression(bp));
}
return this;
});
}
constant('None'); constant('False'); constant('True');
symbol('(number)').nud = function () { return this; };
symbol('(name)').nud = function () { return this; };
symbol('(string)').nud = function () { return this; };
symbol('(end)');
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
symbol('else');
symbol('lambda', 20).nud = function () {
this.first = [];
if (token.id !== ':') {
for(;;) {
if (token.id !== '(name)') {
throw new Error('Excepted an argument name');
}
this.first.push(token);
advance();
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(':');
this.second = expression();
return this;
};
infix('if', 20, function (left) {
this.first = left;
this.second = expression();
advance('else');
this.third = expression();
return this;
});
infixr('or', 30); infixr('and', 40); prefix('not', 50);
comparator('in'); comparator('not in');
comparator('is'); comparator('is not');
comparator('<'); comparator('<=');
comparator('>'); comparator('>=');
comparator('<>'); comparator('!='); comparator('==');
infix('|', 70); infix('^', 80), infix('&', 90);
infix('<<', 100); infix('>>', 100);
infix('+', 110); infix('-', 110);
infix('*', 120); infix('/', 120);
infix('//', 120), infix('%', 120);
prefix('-', 130); prefix('+', 130); prefix('~', 130);
infixr('**', 140);
infix('.', 150, function (left) {
if (token.id !== '(name)') {
throw new Error('Expected attribute name, got ', token.id);
}
this.first = left;
this.second = token;
advance();
return this;
});
symbol('(', 150).nud = function () {
this.first = [];
var comma = false;
if (token.id !== ')') {
while (true) {
if (token.id === ')') {
break;
}
this.first.push(expression());
if (token.id !== ',') {
break;
}
comma = true;
advance(',');
}
}
advance(')');
if (!this.first.length || comma) {
return this;
} else {
return this.first[0];
}
};
symbol('(').led = function (left) {
this.first = left;
this.second = [];
if (token.id !== ")") {
for(;;) {
this.second.push(expression());
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(")");
return this;
};
infix('[', 150, function (left) {
this.first = left;
this.second = expression();
advance("]");
return this;
});
symbol('[').nud = function () {
this.first = [];
if (token.id !== ']') {
for (;;) {
if (token.id === ']') {
break;
}
this.first.push(expression());
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(']');
return this;
};
symbol('{').nud = function () {
this.first = [];
if (token.id !== '}') {
for(;;) {
if (token.id === '}') {
break;
}
var key = expression();
advance(':');
var value = expression();
this.first.push([key, value]);
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance('}');
return this;
};
var longops = {
'*': ['*'],
'<': ['<', '=', '>'],
'>': ['=', '>'],
'!': ['='],
'=': ['='],
'/': ['/']
};
function Tokenizer() {
this.states = ['initial'];
this.tokens = [];
}
Tokenizer.prototype = {
builder: function (empty) {
var key = this.states[0] + '_builder';
if (empty) {
var value = this[key];
delete this[key];
return value;
} else {
return this[key] = this[key] || [];
}
},
simple: function (type) {
this.tokens.push({type: type});
},
push: function (new_state) {
this.states.push(new_state);
},
pop: function () {
this.states.pop();
},
feed: function (str, index) {
var s = this.states;
return this[s[s.length - 1]](str, index);
},
initial: function (str, index) {
var character = str[index];
if (character in longops) {
var follow = longops[character];
for(var i=0, len=follow.length; i<len; ++i) {
if (str[index+1] === follow[i]) {
character += follow[i];
index++;
break;
}
}
}
if (character === ' ') {
return index+1;
} else if (character === '\0') {
this.tokens.push(symbols['(end)']);
return index + 1
} else if (character === '"' || character === "'") {
this.push('string');
return index + 1;
} else if (NUMBER.test(character)) {
this.push('number');
return index;
} else if (NAME_FIRST.test(character)) {
this.push('name');
return index;
} else if (character in symbols) {
this.tokens.push(create(symbols[character]));
return index + 1;
}
throw new Error("Tokenizing failure of <<" + str + ">> at index " + index
+ ", character [[" + character + "]]"
+ "; parsed so far: " + this.tokens);
},
string: function (str, index) {
var character = str[index];
if (character === '"' || character === "'") {
this.tokens.push(create(symbols['(string)'], {
value: this.builder(true).join('')
}));
this.pop();
return index + 1;
}
this.builder().push(character);
return index + 1;
},
number: function (str, index) {
var character = str[index];
if (!(character == '.' || NUMBER.test(character))) {
this.tokens.push(create(symbols['(number)'], {
value: parseFloat(this.builder(true).join(''))
}));
this.pop();
return index;
}
this.builder().push(character);
return index + 1;
},
name: function (str, index) {
var character = str[index];
if (!NAME.test(character)) {
var name = this.builder(true).join('');
var symbol = symbols[name];
if (symbol) {
if (name === 'in' && this.tokens[this.tokens.length-1].id === 'not') {
symbol = symbols['not in'];
this.tokens.pop();
} else if (name === 'not' && this.tokens[this.tokens.length-1].id === 'is') {
symbol = symbols['is not'];
this.tokens.pop();
}
this.tokens.push(create(symbol));
} else {
this.tokens.push(create(symbols['(name)'], {
value: name
}));
}
this.pop();
return index;
}
this.builder().push(character);
return index + 1;
}
};
exports.tokenize = function tokenize(str) {
var index = 0,
tokenizer = new Tokenizer(str);
str += '\0';
do {
index = tokenizer.feed(str, index);
} while (index !== str.length);
return tokenizer.tokens;
};
var token, next;
function expression(rbp) {
rbp = rbp || 0;
var t = token;
token = next();
var left = t.nud();
while (rbp < token.lbp) {
t = token;
token = next();
left = t.led(left);
}
return left;
}
function advance(id) {
if (id && token.id !== id) {
throw new Error(
'Expected "' + id + '", got "' + token.id + '"');
}
token = next();
}
exports.object = create({}, {});
exports.bool = function (arg) { return !!arg; };
exports.tuple = create(exports.object, {
__contains__: function (value) {
for(var i=0, len=this.values.length; i<len; ++i) {
if (this.values[i] === value) {
return true;
}
}
return false;
},
toJSON: function () {
return this.values;
}
});
exports.list = exports.tuple;
exports.dict = create(exports.object, {
toJSON: function () {
return this.values;
}
});
exports.parse = function (toks) {
var index = 0;
token = toks[0];
next = function () { return toks[++index]; };
return expression();
};
var evaluate_operator = function (operator, a, b) {
switch (operator) {
case '==': case 'is': return a === b;
case '!=': case 'is not': return a !== b;
case '<': return a < b;
case '<=': return a <= b;
case '>': return a > b;
case '>=': return a >= b;
case 'in':
if (typeof b === 'string') {
return b.indexOf(a) !== -1;
}
return b.__contains__(a);
case 'not in':
if (typeof b === 'string') {
return b.indexOf(a) === -1;
}
return !b.__contains__(a);
}
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
};
exports.evaluate = function (expr, context) {
switch (expr.id) {
case '(name)':
var val = context[expr.value];
if (val === undefined) {
throw new Error("NameError: name '" + expr.value + "' is not defined");
}
return val;
case '(string)':
case '(number)':
return expr.value;
case '(constant)':
if (expr.value === 'None')
return null;
else if (expr.value === 'False')
return false;
else if (expr.value === 'True')
return true;
throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
case '(comparator)':
var result, left = exports.evaluate(expr.expressions[0], context);
for(var i=0; i<expr.operators.length; ++i) {
result = evaluate_operator(
expr.operators[i],
left,
left = exports.evaluate(expr.expressions[i+1], context));
if (!result) { return false; }
}
return true;
case '-':
if (expr.second) {
throw new Error('SyntaxError: binary [-] not implemented yet');
}
return -(exports.evaluate(expr.first, context));
case 'not':
return !(exports.evaluate(expr.first, context));
case 'and':
return (exports.evaluate(expr.first, context)
&& exports.evaluate(expr.second, context));
case 'or':
return (exports.evaluate(expr.first, context)
|| exports.evaluate(expr.second, context));
case '(':
if (expr.second) {
var fn = exports.evaluate(expr.first, context), args=[];
for (var jj=0; jj<expr.second.length; ++jj) {
args.push(exports.evaluate(
expr.second[jj], context));
}
return fn.apply(null, args);
}
var tuple_exprs = expr.first,
tuple_values = [];
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
tuple_values.push(exports.evaluate(
tuple_exprs[j], context));
}
return create(exports.tuple, {values: tuple_values});
case '[':
if (expr.second) {
throw new Error('SyntaxError: indexing not implemented yet');
}
var list_exprs = expr.first, list_values = [];
for (var k=0; k<list_exprs.length; ++k) {
list_values.push(exports.evaluate(
list_exprs[k], context));
}
return create(exports.list, {values: list_values});
case '{':
var dict_exprs = expr.first, dict_values = {};
for(var l=0; l<dict_exprs.length; ++l) {
dict_values[exports.evaluate(dict_exprs[l][0], context)] =
exports.evaluate(dict_exprs[l][1], context);
}
return create(exports.dict, {values: dict_values});
case '.':
if (expr.second.id !== '(name)') {
throw new Error('SyntaxError: ' + expr);
}
return exports.evaluate(expr.first, context)[expr.second.value];
default:
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
}
};
exports.eval = function (str, context) {
return exports.evaluate(
exports.parse(
exports.tokenize(
str)),
context);
}
})(typeof exports === 'undefined' ? py : exports);

View File

@ -1,90 +0,0 @@
var py = require('../lib/py.js'),
assert = require('assert');
// Literals
assert.strictEqual(py.eval('1'), 1);
assert.strictEqual(py.eval('None'), null);
assert.strictEqual(py.eval('False'), false);
assert.strictEqual(py.eval('True'), true);
assert.strictEqual(py.eval('"somestring"'), 'somestring');
assert.strictEqual(py.eval("'somestring'"), 'somestring');
assert.deepEqual(py.eval("()").toJSON(), []);
assert.deepEqual(py.eval("[]").toJSON(), []);
assert.deepEqual(py.eval("{}").toJSON(), {});
assert.deepEqual(py.eval("(None, True, False, 0, 1, 'foo')").toJSON(),
[null, true, false, 0, 1, 'foo']);
assert.deepEqual(py.eval("[None, True, False, 0, 1, 'foo']").toJSON(),
[null, true, false, 0, 1, 'foo']);
assert.deepEqual(py.eval("{'foo': 1, foo: 2}", {foo: 'bar'}).toJSON(),
{foo: 1, bar: 2});
// Equality tests
assert.ok(py.eval(
"foo == 'foo'", {foo: 'foo'}));
// Inequality
assert.ok(py.eval(
"foo != bar", {foo: 'foo', bar: 'bar'}));
// Comparisons
assert.ok(py.eval('3 < 5'));
assert.ok(py.eval('5 >= 3'));
assert.ok(py.eval('3 >= 3'));
assert.ok(!py.eval('5 < 3'));
assert.ok(py.eval('1 < 3 < 5'));
assert.ok(py.eval('5 > 3 > 1'));
assert.ok(py.eval('1 < 3 > 2 == 2 > -2 not in (0, 1, 2)'));
// string rich comparisons
assert.ok(py.eval(
'date >= current', {date: '2010-06-08', current: '2010-06-05'}));
// Boolean operators
assert.ok(py.eval(
"foo == 'foo' or foo == 'bar'", {foo: 'bar'}));
assert.ok(py.eval(
"foo == 'foo' and bar == 'bar'", {foo: 'foo', bar: 'bar'}));
// - lazyness, second clauses NameError if not short-circuited
assert.ok(py.eval(
"foo == 'foo' or bar == 'bar'", {foo: 'foo'}));
assert.ok(!py.eval(
"foo == 'foo' and bar == 'bar'", {foo: 'bar'}));
// contains (in)
assert.ok(py.eval(
"foo in ('foo', 'bar')", {foo: 'bar'}));
assert.ok(py.eval('1 in (1, 2, 3, 4)'));
assert.ok(!py.eval('1 in (2, 3, 4)'));
assert.ok(py.eval('type in ("url",)', {type: 'url'}));
assert.ok(!py.eval('type in ("url",)', {type: 'ur'}));
assert.ok(py.eval('1 not in (2, 3, 4)'));
assert.ok(py.eval('type not in ("url",)', {type: 'ur'}));
assert.ok(py.eval(
"foo in ['foo', 'bar']", {foo: 'bar'}));
// string contains
assert.ok(py.eval('type in "view"', {type: 'view'}));
assert.ok(!py.eval('type in "view"', {type: 'bob'}));
assert.ok(py.eval('type in "url"', {type: 'ur'}));
// Literals
assert.strictEqual(py.eval('False'), false);
assert.strictEqual(py.eval('True'), true);
assert.strictEqual(py.eval('None'), null);
assert.ok(py.eval('foo == False', {foo: false}));
assert.ok(!py.eval('foo == False', {foo: true}));
// conversions
assert.strictEqual(
py.eval('bool(date_deadline)', {bool: py.bool, date_deadline: '2008'}),
true);
// getattr
assert.ok(py.eval('foo.bar', {foo: {bar: true}}));
assert.ok(!py.eval('foo.bar', {foo: {bar: false}}));
// complex expressions
assert.ok(py.eval(
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
{state: 'pending', date_deadline: false}));
assert.ok(py.eval(
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
{state: 'pending', date_deadline: '2010-05-08', current_date: '2010-05-08'}));;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,330 @@
// Variables {{{
$colour4: #8a89ba
//$colour4: #d14836
// }}}
// Mixins {{{
@mixin vertical-gradient($startColor: #555, $endColor: #333)
background-color: $startColor
background-image: -webkit-gradient(linear, left top, left bottom, from($startColor), to($endColor)) /* Saf4+, Chrome */
background-image: -webkit-linear-gradient(top, $startColor, $endColor) /* Chrome 10+, Saf5.1+, iOS 5+ */
background-image: -moz-linear-gradient(top, $startColor, $endColor) /* FF3.6 */
background-image: -ms-linear-gradient(top, $startColor, $endColor) /* IE10 */
background-image: -o-linear-gradient(top, $startColor, $endColor) /* Opera 11.10+ */
background-image: linear-gradient(to bottom, $startColor, $endColor)
@mixin radial-gradient($gradient)
background-position: center center
background-image: -webkit-radial-gradient(circle, $gradient)
background-image: -moz-radial-gradient($gradient)
background-image: -ms-radial-gradient($gradient)
background-image: radial-gradient($gradient)
@mixin radius($radius: 5px)
-moz-border-radius: $radius
-webkit-border-radius: $radius
border-radius: $radius
@mixin box-shadow($bsval: 0px 1px 4px #777)
-moz-box-shadow: $bsval
-webkit-box-shadow: $bsval
-box-shadow: $bsval
@mixin transition($transval: (border linear 0.2s, box-shadow linear 0.2s))
-webkit-transition: $transval
-moz-transition: $transval
-ms-transition: $transval
-o-transition: $transval
transition: $transval
@mixin opacity($opacity: .5)
filter: alpha(opacity=$opacity * 100)
opacity: $opacity
@mixin background-clip($clip: padding-box)
-webkit-background-clip: $clip
-moz-background-clip: $clip
background-clip: $clip
// }}}
.openerp2
// Common styles {{{
padding: 0
margin: 0
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif
color: #4c4c4c
font-size: 13px
background: white
position: relative
a
text-decoration: none
// }}}
// WebClient {{{
.oe_webclient
position: absolute
top: 0
bottom: 0
left: 0
right: 0
.oe_application
position: absolute
top: 32px
bottom: 0
left: 206px
right: 0
// }}}
// Fullscreen mode {{{
.oe_content_full_screen
.oe_application
top: 0
left: 0
.topbar, .leftbar
display: none
// }}
// Topbar {{{
.oe_topbar
width: 100%
height: 31px
border-top: solid 1px #d3d3d3
border-bottom: solid 1px black
@include vertical-gradient(#646060, #262626)
.oe_systray
float: right
.oe_systray > div
float: left
padding: 0 4px 0 4px
.oe_topbar_item
li
float: left
a
display: block
padding: 5px 10px 7px
line-height: 20px
height: 20px
color: #eee
vertical-align: top
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
&:hover
background: #303030
color: white
@include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset)
.oe_active
background: #303030
font-weight: bold
color: white
@include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset)
.oe_topbar_avatar
width: 24px
height: 24px
margin: -2px 2px 0 0
@include radius(4px)
.oe_topbar_avatar
vertical-align: top
// }}}
// Leftbar {{{
.oe_leftbar
width: 205px
height: 100%
background: #f0eeee
border-right: 1px solid #afafb6
overflow: auto
text-shadow: 0 1px 1px white
.oe_footer
position: absolute
width: 205px
text-align: center
bottom: 8px
a.oe_logo
display: block
text-align: center
height: 70px
line-height: 70px
img
height: 40px
width: 157px
margin: 14px 0
.oe_footer
position: absolute
width: 205px
text-align: center
bottom: 8px
a
font-weight: 800
font-family: serif
font-size: 16px
color: black
span
color: #c81010
font-style: italic
// }}}
// Menu {{{
.oe_menu
float: left
padding: 0
margin: 0
li
list-style-type: none
float: left
a
display: block
padding: 5px 10px 7px
line-height: 20px
height: 20px
color: #eee
vertical-align: top
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
&:hover
background: #303030
color: white
@include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset)
.oe_active
background: #303030
font-weight: bold
color: white
@include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset)
.oe_secondary_menu_section
font-weight: bold
margin-left: 8px
color: $colour4
.oe_secondary_submenu
padding: 2px 0 8px 0
margin: 0
width: 100%
display: inline-block
li
position: relative
padding: 1px 0 1px 16px
list-style-type: none
a
display: block
color: #4c4c4c
padding: 2px 4px 2px 0
.oe_menu_label
position: absolute
top: 1px
right: 1px
font-size: 10px
background: $colour4
color: white
padding: 2px 4px
margin: 1px 6px 0 0
border: 1px solid lightGray
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
@include radius(4px)
@include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2))
.oe_active
background: $colour4
border-top: 1px solid lightGray
border-bottom: 1px solid lightGray
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
@include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2))
a
color: white
.oe_menu_label
background: #eee
color: $colour4
text-shadow: 0 1px 1px white
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2))
.oe_menu_toggler:before
width: 0
height: 0
display: inline-block
content: "&darr"
text-indent: -99999px
vertical-align: top
margin-left: -8px
margin-top: 4px
margin-right: 4px
border-top: 4px solid transparent
border-bottom: 4px solid transparent
border-left: 4px solid #4c4c4c
@include opacity(0.5)
.oe_menu_opened:before
margin-top: 6px
margin-left: -12px
margin-right: 4px
border-left: 4px solid transparent
border-right: 4px solid transparent
border-top: 4px solid #4c4c4c
// }}}
// UserMenu {{{
.oe_user_menu
float: right
padding: 0
margin: 0
li
list-style-type: none
float: left
.oe_dropdown
position: relative
.oe_dropdown_toggle:after
width: 0
height: 0
display: inline-block
content: "&darr"
text-indent: -99999px
vertical-align: top
margin-top: 8px
margin-left: 4px
border-left: 4px solid transparent
border-right: 4px solid transparent
border-top: 4px solid white
@include opacity(0.5)
.oe_dropdown_options
float: left
background: #333
background: rgba(37,37,37,0.9)
display: none
position: absolute
top: 32px
right: -1px
border: 0
z-index: 900
margin-left: 0
margin-right: 0
padding: 6px 0
zoom: 1
border-color: #999
border-color: rgba(0, 0, 0, 0.2)
border-style: solid
border-width: 0 1px 1px
@include radius(0 0 6px 6px)
@include box-shadow(0 1px 4px rgba(0,0,0,0.3))
@include background-clip()
li
float: none
display: block
background-color: none
a
display: block
padding: 4px 15px
clear: both
font-weight: normal
line-height: 18px
color: #eee
&:hover
@include vertical-gradient(#292929, #191919)
@include box-shadow(none)
// }}}
.openerp
// Transitional overrides for old styles {{{
// }}}
// au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css"
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
body {
padding: 0;
margin: 0;
overflow-y: scroll;
height: 100%;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

View File

@ -45,6 +45,12 @@ openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web.
});
openerp.web.dialog = function(element) {
var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
result.dialog("widget").addClass("openerp");
return result;
}
openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{
dialog_title: "",
/**
@ -86,7 +92,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
if (this.dialog_options.autoOpen) {
this.open();
} else {
this.$element.dialog(this.get_options());
openerp.web.dialog(this.$element, this.get_options());
}
},
get_options: function(options) {
@ -125,7 +131,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
this.$element.html(this.render());
}
var o = this.get_options(options);
this.$element.dialog(o).dialog('open');
openerp.web.dialog(this.$element, o).dialog('open');
if (o.height === 'auto' && o.max_height) {
this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' });
}
@ -142,7 +148,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
on_resized: function() {
//openerp.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height());
},
stop: function () {
destroy: function () {
// Destroy widget
this.close();
this.$element.dialog('destroy');
@ -166,7 +172,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
}
},
on_managed_error: function(error) {
$('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>').dialog({
openerp.web.dialog($('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>'), {
title: "OpenERP " + _.str.capitalize(error.type),
buttons: [
{text: _t("Ok"), click: function() { $(this).dialog("close"); }}
@ -234,7 +240,7 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin
this.session.on_rpc_request.add_first(this.request_call);
this.session.on_rpc_response.add_last(this.response_call);
},
stop: function() {
destroy: function() {
this.session.on_rpc_request.remove(this.request_call);
this.session.on_rpc_response.remove(this.response_call);
this.on_rpc_event(-this.count);
@ -255,7 +261,7 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin
$(".loading",this.$element).text(_.str.sprintf(
_t("Loading (%d)"), this.count));
$(".loading",this.$element).show();
this.widget_parent.$element.addClass('loading');
this.getParent().$element.addClass('loading');
} else {
this.count = 0;
clearTimeout(this.long_running_timer);
@ -265,7 +271,7 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin
$.unblockUI();
}
$(".loading",this.$element).fadeOut();
this.widget_parent.$element.removeClass('loading');
this.getParent().$element.removeClass('loading');
}
}
});
@ -312,7 +318,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
self.hide();
});
},
stop: function () {
destroy: function () {
this.hide();
this.$option_id.empty();
@ -344,44 +350,6 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
});
return result;
},
/**
* Waits until the new database is done creating, then unblocks the UI and
* logs the user in as admin
*
* @param {Number} db_creation_id identifier for the db-creation operation, used to fetch the current installation progress
* @param {Object} info info fields for this database creation
* @param {String} info.db name of the database being created
* @param {String} info.password super-admin password for the database
*/
wait_for_newdb: function (db_creation_id, info) {
var self = this;
self.rpc('/web/database/progress', {
id: db_creation_id,
password: info.password
}, function (result) {
var progress = result[0];
// I'd display a progress bar, but turns out the progress status
// the server report kind-of blows goats: it's at 0 for ~75% of
// the installation, then jumps to 75%, then jumps down to either
// 0 or ~40%, then back up to 75%, then terminates. Let's keep that
// mess hidden behind a not-very-useful but not overly weird
// message instead.
if (progress < 1) {
setTimeout(function () {
self.wait_for_newdb(db_creation_id, info);
}, 500);
return;
}
var admin = result[1][0];
setTimeout(function () {
self.widget_parent.do_login(
info.db, admin.login, admin.password);
self.stop();
self.unblockUI();
});
});
},
/**
* Blocks UI and replaces $.unblockUI by a noop to prevent third parties
* from unblocking the UI
@ -407,7 +375,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
* @param {String} error.error message of the error dialog
*/
display_error: function (error) {
return $('<div>').dialog({
return openerp.web.dialog($('<div>'), {
modal: true,
title: error.title,
buttons: [
@ -421,23 +389,19 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
self.$option_id.find("form[name=create_db_form]").validate({
submitHandler: function (form) {
var fields = $(form).serializeArray();
self.blockUI();
self.rpc("/web/database/create", {'fields': fields}, function(result) {
if (result.error) {
self.unblockUI();
self.display_error(result);
return;
}
if (self.db_list) {
self.db_list.push(self.to_object(fields)['db_name']);
self.db_list.sort();
self.widget_parent.set_db_list(self.db_list);
}
var form_obj = self.to_object(fields);
self.wait_for_newdb(result, {
password: form_obj['super_admin_pwd'],
db: form_obj['db_name']
});
self.getParent().do_login(
form_obj['db_name'],
'admin',
form_obj['create_admin_pwd']);
self.destroy();
});
}
});
@ -449,7 +413,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
submitHandler: function (form) {
var $form = $(form),
fields = $form.serializeArray(),
$db_list = $form.find('select[name=drop_db]'),
$db_list = $form.find('[name=drop_db]'),
db = $db_list.val();
if (!confirm("Do you really want to delete the database: " + db + " ?")) {
@ -463,7 +427,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
$db_list.find(':selected').remove();
if (self.db_list) {
self.db_list.splice(_.indexOf(self.db_list, db, true), 1);
self.widget_parent.set_db_list(self.db_list);
self.getParent().set_db_list(self.db_list);
}
self.do_notify("Dropping database", "The database '" + db + "' has been dropped");
});
@ -660,58 +624,212 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login#
}
});
openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header# */{
template: "Header",
openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
/**
* @constructs openerp.web.Header
* @extends openerp.web.OldWidget
* @constructs openerp.web.Menu
* @extends openerp.web.Widget
*
* @param parent
*/
template: 'Menu',
init: function() {
this._super.apply(this, arguments);
this.has_been_loaded = $.Deferred();
},
start: function() {
this._super.apply(this, arguments);
this.$secondary_menus = this.getParent().$element.find('.oe_secondary_menus_container');
this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
},
do_reload: function() {
var self = this;
return this.rpc("/web/menu/load", {}, this.on_loaded).then(function () {
if (self.current_menu) {
self.open_menu(self.current_menu);
}
});
},
on_loaded: function(data) {
this.data = data;
this.renderElement();
this.$element.on('click', 'a[data-menu]', this.on_menu_click);
this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this }));
// Hide second level submenus
this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide();
this.has_been_loaded.resolve();
},
/**
* Opens a given menu by id, as if a user had browsed to that menu by hand
* except does not trigger any event on the way
*
* @param {Number} id database id of the terminal menu to select
*/
open_menu: function (id) {
var $clicked_menu, $sub_menu, $main_menu;
$clicked_menu = this.$element.add(this.$secondary_menus).find('a[data-menu=' + id + ']');
if (this.$secondary_menus.has($clicked_menu).length) {
$sub_menu = $clicked_menu.parents('.oe_secondary_menu');
$main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
} else {
$sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
$main_menu = $clicked_menu;
}
// Activate current main menu
this.$element.find('.oe_active').removeClass('oe_active');
$main_menu.addClass('oe_active');
// Show current sub menu
this.$secondary_menus.find('.oe_secondary_menu').hide();
$sub_menu.show();
// Activate current menu item and show parents
this.$secondary_menus.find('.oe_active').removeClass('oe_active');
if ($main_menu !== $clicked_menu) {
$clicked_menu.parents().show();
if ($clicked_menu.is('.oe_menu_toggler')) {
$clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggle();
} else {
$clicked_menu.parent().addClass('oe_active');
}
}
},
open_action: function (id) {
var menu_id, $menu = this.$element.add(this.$secondary_menus).find('a[data-action-id=' + id + ']');
if (menu_id = $menu.data('menu')) {
this.open_menu(menu_id);
}
},
on_menu_click: function(ev, id) {
id = id || 0;
var $clicked_menu, manual = false;
if (id) {
// We can manually activate a menu with it's id (for hash url mapping)
manual = true;
$clicked_menu = this.$element.find('a[data-menu=' + id + ']');
if (!$clicked_menu.length) {
$clicked_menu = this.$secondary_menus.find('a[data-menu=' + id + ']');
}
} else {
$clicked_menu = $(ev.currentTarget);
id = $clicked_menu.data('menu');
}
this.trigger('menuClicked', id, $clicked_menu);
if (id) {
this.open_menu(id);
this.current_menu = id;
this.session.active_id = id;
var action_id = $clicked_menu.data('action-id');
if (action_id) {
this.on_action(action_id);
}
}
if (ev) {
ev.stopPropagation();
}
return false;
},
do_show_secondary: function($sub_menu, $main_menu) {
var self = this;
this.$secondary_menus.show();
if (!arguments.length) {
return;
}
$sub_menu.show();
},
on_action: function(action) {
}
});
openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMenu# */{
template: "UserMenu",
/**
* @constructs openerp.web.UserMenu
* @extends openerp.web.Widget
*
* @param parent
*/
init: function(parent) {
this._super(parent);
this.qs = "?" + jQuery.param.querystring();
this.$content = $();
this.update_promise = $.Deferred().resolve();
},
start: function() {
this._super();
var self = this;
this._super.apply(this, arguments);
$('html').bind('click', function() {
self.$element.find('.oe_dropdown_options').hide();
});
this.$element.find('.oe_dropdown_toggle').click(function() {
self.$element.find('.oe_dropdown_options').toggle();
return false;
});
this.$element.on('click', '.oe_dropdown_options li a[data-menu]', function() {
var f = self['on_menu_' + $(this).data('menu')];
f && f($(this));
self.$element.find('.oe_dropdown_options').hide();
return false;
});
},
change_password :function() {
var self = this;
this.dialog = new openerp.web.Dialog(this, {
title: _t("Change Password"),
width : 'auto'
}).open();
this.dialog.$element.html(QWeb.render("Change_Pwd", self));
this.dialog.$element.find("form[name=change_password_form]").validate({
submitHandler: function (form) {
self.rpc("/web/session/change_password",{
'fields': $(form).serializeArray()
}, function(result) {
if (result.error) {
self.display_error(result);
return;
} else {
openerp.webclient.on_logout();
}
});
}
});
},
display_error: function (error) {
return openerp.web.dialog($('<div>'), {
modal: true,
title: error.title,
buttons: [
{text: _("Ok"), click: function() { $(this).dialog("close"); }}
]
}).html(error.error);
},
do_update: function () {
var self = this;
var fct = function() {
self.$content.remove();
var $avatar = self.$element.find('.oe_topbar_avatar');
$avatar.attr('src', $avatar.data('default-src'));
if (!self.session.uid)
return;
var func = new openerp.web.Model("res.users").get_func("read");
return func(self.session.uid, ["name", "company_id"]).pipe(function(res) {
self.$content = $(QWeb.render("Header-content", {widget: self, user: res}));
self.$content.appendTo(self.$element);
self.$element.find(".logout").click(self.on_logout);
self.$element.find("a.preferences").click(self.on_preferences);
self.$element.find(".about").click(self.on_about);
// TODO: Only show company if multicompany in use
self.$element.find('.oe_topbar_name').text(res.name + '/' + res.company_id[1]);
return self.shortcut_load();
});
};
this.update_promise = this.update_promise.pipe(fct, fct);
},
on_about: function() {
var self = this;
self.rpc("/web/webclient/version_info", {}).then(function(res) {
var $help = $(QWeb.render("About-Page", {version_info: res}));
$help.find('a.oe_activate_debug_mode').click(function (e) {
e.preventDefault();
window.location = $.param.querystring(
window.location.href, 'debug');
});
$help.dialog({autoOpen: true,
modal: true, width: 960, title: _t("About")});
});
on_action: function() {
},
shortcut_load :function(){
var self = this,
sc = self.session.shortcuts,
shortcuts_ds = new openerp.web.DataSet(this, 'ir.ui.view_sc');
self.$element.find('.oe_dropdown_options a[data-menu=shortcut]').each(function() {
$(this).parent().remove();
});
// TODO: better way to communicate between sections.
// sc.bindings, because jquery does not bind/trigger on arrays...
if (!sc.binding) {
@ -719,52 +837,41 @@ openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header
$(sc.binding).bind({
'add': function (e, attrs) {
shortcuts_ds.create(attrs, function (out) {
$('<li>', {
'data-shortcut-id':out.result,
'data-id': attrs.res_id
}).text(attrs.name)
.appendTo(self.$element.find('.oe-shortcuts ul'));
var shortcut = QWeb.render('UserMenu.shortcut', {
shortcuts : [{
name : attrs.name,
id : out.result,
res_id : attrs.res_id
}]
});
$(shortcut).appendTo(self.$element.find('.oe_dropdown_options'));
attrs.id = out.result;
sc.push(attrs);
});
},
'remove-current': function () {
var menu_id = self.session.active_id;
var $shortcut = self.$element
.find('.oe-shortcuts li[data-id=' + menu_id + ']');
var $shortcut = self.$element.find('.oe_dropdown_options li a[data-id=' + menu_id + ']');
var shortcut_id = $shortcut.data('shortcut-id');
$shortcut.remove();
shortcuts_ds.unlink([shortcut_id]);
var sc_new = _.reject(sc, function(shortcut){ return shortcut_id === shortcut.id});
sc.splice(0, sc.length);
sc.push.apply(sc, sc_new);
}
}
});
}
return this.rpc('/web/session/sc_list', {}, function(shortcuts) {
sc.splice(0, sc.length);
sc.push.apply(sc, shortcuts);
self.$element.find('.oe-shortcuts')
.html(QWeb.render('Shortcuts', {'shortcuts': shortcuts}))
.undelegate('li', 'click')
.delegate('li', 'click', function(e) {
e.stopPropagation();
var id = $(this).data('id');
self.session.active_id = id;
self.rpc('/web/menu/action', {'menu_id':id}, function(ir_menu_data) {
if (ir_menu_data.action.length){
self.on_action(ir_menu_data.action[0][2]);
}
});
});
$(QWeb.render('UserMenu.shortcut', {'shortcuts': shortcuts}))
.appendTo(self.$element.find('.oe_dropdown_options'));
});
},
on_action: function(action) {
on_menu_logout: function() {
},
on_preferences: function(){
on_menu_settings: function() {
var self = this;
var action_manager = new openerp.web.ActionManager(this);
var dataset = new openerp.web.DataSet (this,'res.users',this.context);
@ -793,7 +900,7 @@ openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header
var inner_viewmanager = action_manager.inner_viewmanager;
inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save()
.then(function() {
self.dialog.stop();
self.dialog.destroy();
// needs to refresh interface in case language changed
window.location.reload();
});
@ -804,298 +911,66 @@ openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header
action_manager.appendTo(this.dialog);
action_manager.render(this.dialog);
},
change_password :function() {
on_menu_about: function() {
var self = this;
this.dialog = new openerp.web.Dialog(this, {
title: _t("Change Password"),
width : 'auto'
}).open();
this.dialog.$element.html(QWeb.render("Change_Pwd", self));
this.dialog.$element.find("form[name=change_password_form]").validate({
submitHandler: function (form) {
self.rpc("/web/session/change_password",{
'fields': $(form).serializeArray()
}, function(result) {
if (result.error) {
self.display_error(result);
return;
} else {
openerp.webclient.on_logout();
}
});
}
});
},
display_error: function (error) {
return $('<div>').dialog({
modal: true,
title: error.title,
buttons: [
{text: _("Ok"), click: function() { $(this).dialog("close"); }}
]
}).html(error.error);
},
on_logout: function() {
}
});
openerp.web.Menu = openerp.web.OldWidget.extend(/** @lends openerp.web.Menu# */{
/**
* @constructs openerp.web.Menu
* @extends openerp.web.OldWidget
*
* @param parent
* @param element_id
* @param secondary_menu_id
*/
init: function(parent, element_id, secondary_menu_id) {
this._super(parent, element_id);
this.secondary_menu_id = secondary_menu_id;
this.$secondary_menu = $("#" + secondary_menu_id);
this.menu = false;
this.folded = false;
if (window.localStorage) {
this.folded = localStorage.getItem('oe_menu_folded') === 'true';
}
this.float_timeout = 700;
},
start: function() {
this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded');
},
do_reload: function() {
var self = this;
return this.rpc("/web/menu/load", {}, this.on_loaded).then(function () {
if (self.current_menu) {
self.open_menu(self.current_menu);
}
});
},
on_loaded: function(data) {
this.data = data;
this.$element.html(QWeb.render("Menu", { widget : this }));
this.$secondary_menu.html(QWeb.render("Menu.secondary", { widget : this }));
this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click);
this.$secondary_menu.find('.oe_toggle_secondary_menu').click(this.on_toggle_fold);
},
on_toggle_fold: function() {
this.$secondary_menu.toggleClass('oe_folded').toggleClass('oe_unfolded');
if (this.folded) {
this.$secondary_menu.find('.oe_secondary_menu.active').show();
} else {
this.$secondary_menu.find('.oe_secondary_menu').hide();
}
this.folded = !this.folded;
if (window.localStorage) {
localStorage.setItem('oe_menu_folded', this.folded.toString());
}
},
/**
* Opens a given menu by id, as if a user had browsed to that menu by hand
* except does not trigger any event on the way
*
* @param {Number} menu_id database id of the terminal menu to select
*/
open_menu: function (menu_id) {
this.$element.add(this.$secondary_menu).find('.active')
.removeClass('active');
this.$secondary_menu.find('> .oe_secondary_menu').hide();
var $primary_menu;
var $secondary_submenu = this.$secondary_menu.find(
'a[data-menu=' + menu_id +']');
if ($secondary_submenu.length) {
for(;;) {
if ($secondary_submenu.hasClass('leaf')) {
$secondary_submenu.addClass('active');
} else if ($secondary_submenu.hasClass('submenu')) {
$secondary_submenu.addClass('opened')
}
var $parent = $secondary_submenu.parent().show();
if ($parent.hasClass('oe_secondary_menu')) {
var primary_id = $parent.data('menu-parent');
$primary_menu = this.$element.find(
'a[data-menu=' + primary_id + ']');
break;
}
$secondary_submenu = $parent.prev();
}
} else {
$primary_menu = this.$element.find('a[data-menu=' + menu_id + ']');
}
if (!$primary_menu.length) {
return;
}
$primary_menu.addClass('active');
this.$secondary_menu.find(
'div[data-menu-parent=' + $primary_menu.data('menu') + ']').addClass('active').toggle(!this.folded);
},
on_menu_click: function(ev, id) {
id = id || 0;
var $clicked_menu, manual = false;
if (id) {
// We can manually activate a menu with it's id (for hash url mapping)
manual = true;
$clicked_menu = this.$element.find('a[data-menu=' + id + ']');
if (!$clicked_menu.length) {
$clicked_menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
}
} else {
$clicked_menu = $(ev.currentTarget);
id = $clicked_menu.data('menu');
}
if (this.do_menu_click($clicked_menu, manual) && id) {
this.current_menu = id;
this.session.active_id = id;
this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded);
}
if (ev) {
ev.stopPropagation();
}
return false;
},
do_menu_click: function($clicked_menu, manual) {
var $sub_menu, $main_menu,
active = $clicked_menu.is('.active'),
sub_menu_visible = false,
has_submenu_items = false;
if (this.$secondary_menu.has($clicked_menu).length) {
$sub_menu = $clicked_menu.parents('.oe_secondary_menu');
$main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
} else {
$sub_menu = this.$secondary_menu.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
$main_menu = $clicked_menu;
}
sub_menu_visible = $sub_menu.is(':visible');
has_submenu_items = !!$sub_menu.children().length;
this.$secondary_menu.find('.oe_secondary_menu').hide();
$('.active', this.$element.add(this.$secondary_menu)).removeClass('active');
$main_menu.add($clicked_menu).add($sub_menu).addClass('active');
if (has_submenu_items) {
if (!(this.folded && manual)) {
this.do_show_secondary($sub_menu, $main_menu);
} else {
this.do_show_secondary();
}
}
if ($main_menu != $clicked_menu) {
if ($clicked_menu.is('.submenu')) {
$sub_menu.find('.submenu.opened').each(function() {
if (!$(this).next().has($clicked_menu).length && !$(this).is($clicked_menu)) {
$(this).removeClass('opened').next().hide();
}
});
$clicked_menu.toggleClass('opened').next().toggle();
} else if ($clicked_menu.is('.leaf')) {
$sub_menu.toggle(!this.folded);
return true;
}
} else if (this.folded) {
if ((active && sub_menu_visible) || !has_submenu_items) {
$sub_menu.hide();
return true;
}
return manual;
} else {
return true;
}
return false;
},
do_hide_secondary: function() {
this.$secondary_menu.hide();
},
do_show_secondary: function($sub_menu, $main_menu) {
var self = this;
this.$secondary_menu.show();
if (!arguments.length) {
return;
}
if (this.folded) {
var css = $main_menu.position(),
fold_width = this.$secondary_menu.width() + 2,
window_width = $(window).width();
css.top += 33;
css.left -= Math.round(($sub_menu.width() - $main_menu.width()) / 2);
css.left = css.left < fold_width ? fold_width : css.left;
if ((css.left + $sub_menu.width()) > window_width) {
delete(css.left);
css.right = 1;
}
$sub_menu.css(css);
$sub_menu.mouseenter(function() {
clearTimeout($sub_menu.data('timeoutId'));
$sub_menu.data('timeoutId', null);
return false;
}).mouseleave(function(evt) {
var timeoutId = setTimeout(function() {
if (self.folded && $sub_menu.data('timeoutId')) {
$sub_menu.hide().unbind('mouseenter').unbind('mouseleave');
}
}, self.float_timeout);
$sub_menu.data('timeoutId', timeoutId);
return false;
self.rpc("/web/webclient/version_info", {}).then(function(res) {
var $help = $(QWeb.render("About-Page", {version_info: res}));
$help.find('a.oe_activate_debug_mode').click(function (e) {
e.preventDefault();
window.location = $.param.querystring(
window.location.href, 'debug');
});
}
$sub_menu.show();
openerp.web.dialog($help, {autoOpen: true,
modal: true, width: 960, title: _t("About")});
});
},
on_menu_action_loaded: function(data) {
var self = this;
if (data.action.length) {
var action = data.action[0][2];
action.from_menu = true;
self.on_action(action);
} else {
self.on_action({type: 'null_action'});
}
},
on_action: function(action) {
on_menu_shortcut: function($link) {
var self = this,
id = $link.data('id');
self.session.active_id = id;
self.rpc('/web/menu/action', {'menu_id': id}, function(ir_menu_data) {
if (ir_menu_data.action.length){
self.on_action(ir_menu_data.action[0][2]);
}
});
}
});
openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebClient */{
openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClient */{
/**
* @constructs openerp.web.WebClient
* @extends openerp.web.OldWidget
*
* @param element_id
* @extends openerp.web.Widget
*/
init: function(parent) {
var self = this;
this._super(parent);
openerp.webclient = this;
this.querystring = '?' + jQuery.param.querystring();
this._current_state = null;
},
start: function() {
var self = this;
this.$element = $(document.body);
this.$element.addClass("openerp openerp2");
if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
this.$element.addClass("kitten-mode-activated");
this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) {
self.$element.toggleClass('clark-gable');
});
}
this.session.bind().then(function() {
this.session.bind_session().then(function() {
if (!self.session.session_is_valid()) {
self.show_login();
}
});
this.session.on_session_valid.add(function() {
self.show_application();
self.header.do_update();
self.user_menu.do_update();
self.menu.do_reload();
if(self.action_manager)
self.action_manager.stop();
self.action_manager.destroy();
self.action_manager = new openerp.web.ActionManager(self);
self.action_manager.appendTo($("#oe_app"));
self.action_manager.appendTo(self.$element.find('.oe_application'));
self.bind_hashchange();
var version_label = _t("OpenERP - Unsupported/Community Version");
if (!self.session.openerp_entreprise) {
@ -1103,6 +978,9 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC
document.title = version_label;
}
});
this.$element.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() {
$(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter');
});
},
show_login: function() {
var self = this;
@ -1115,15 +993,15 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC
var self = this;
this.destroy_content();
this.show_common();
self.$table = $(QWeb.render("Interface", {}));
self.$table = $(QWeb.render("WebClient", {}));
self.$element.append(self.$table);
self.header = new openerp.web.Header(self);
self.header.on_logout.add(this.proxy('on_logout'));
self.header.on_action.add(this.proxy('on_menu_action'));
self.header.appendTo($("#oe_header"));
self.menu = new openerp.web.Menu(self, "oe_menu", "oe_secondary_menu");
self.menu = new openerp.web.Menu(self);
self.menu.replace(this.$element.find('.oe_menu_placeholder'));
self.menu.on_action.add(this.proxy('on_menu_action'));
self.menu.start();
self.user_menu = new openerp.web.UserMenu(self);
self.user_menu.replace(this.$element.find('.oe_user_menu_placeholder'));
self.user_menu.on_menu_logout.add(this.proxy('on_logout'));
self.user_menu.on_action.add(this.proxy('on_menu_action'));
},
show_common: function() {
var self = this;
@ -1144,8 +1022,8 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC
this.loading.appendTo(this.$element);
},
destroy_content: function() {
_.each(_.clone(this.widget_children), function(el) {
el.stop();
_.each(_.clone(this.getChildren()), function(el) {
el.destroy();
});
this.$element.children().remove();
},
@ -1207,6 +1085,12 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC
self.menu.on_menu_click(null, action.menu_id);
});
}
},
set_content_full_screen: function(fullscreen) {
if (fullscreen)
$(".oe_webclient", this.$element).addClass("oe_content_full_screen");
else
$(".oe_webclient", this.$element).removeClass("oe_content_full_screen");
}
});
@ -1250,7 +1134,7 @@ openerp.web.embed = function (origin, dbname, login, key, action, options) {
var sc = document.getElementsByTagName('script');
currentScript = sc[sc.length-1];
}
openerp.connection.bind(origin).then(function () {
openerp.connection.bind_session(origin).then(function () {
openerp.connection.session_authenticate(dbname, login, key, true).then(function () {
var client = new openerp.web.EmbeddedClient(action, options);
client.insertAfter(currentScript);

View File

@ -10,110 +10,8 @@ if (!console.debug) {
}
openerp.web.core = function(openerp) {
/**
* John Resig Class with factory improvement
*/
(function() {
var initializing = false,
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The web Class implementation (does nothing)
/**
* Extended version of John Resig's Class pattern
*
* @class
*/
openerp.web.Class = function(){};
/**
* Subclass an existing class
*
* @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
*/
openerp.web.Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a web class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" &&
fnTest.test(prop[name]) ?
(function(name, fn) {
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same
// method but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so
// we remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if (!initializing && this.init) {
var ret = this.init.apply(this, arguments);
if (ret) { return ret; }
}
return this;
}
Class.include = function (properties) {
for (var name in properties) {
if (typeof properties[name] !== 'function'
|| !fnTest.test(properties[name])) {
prototype[name] = properties[name];
} else if (typeof prototype[name] === 'function'
&& prototype.hasOwnProperty(name)) {
prototype[name] = (function (name, fn, previous) {
return function () {
var tmp = this._super;
this._super = previous;
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
}
})(name, properties[name], prototype[name]);
} else if (typeof _super[name] === 'function') {
prototype[name] = (function (name, fn) {
return function () {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
}
})(name, properties[name]);
}
}
};
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
openerp.web.Class = nova.Class;
openerp.web.callback = function(obj, method) {
var callback = function() {
@ -174,31 +72,6 @@ openerp.web.callback = function(obj, method) {
});
};
/**
* Generates an inherited class that replaces all the methods by null methods (methods
* that does nothing and always return undefined).
*
* @param {Class} claz
* @param {Object} add Additional functions to override.
* @return {Class}
*/
openerp.web.generate_null_object_class = function(claz, add) {
var newer = {};
var copy_proto = function(prototype) {
for (var name in prototype) {
if(typeof prototype[name] == "function") {
newer[name] = function() {};
}
}
if (prototype.prototype)
copy_proto(prototype.prototype);
};
copy_proto(claz.prototype);
newer.init = openerp.web.Widget.prototype.init;
var tmpclass = claz.extend(newer);
return tmpclass.extend(add || {});
};
/**
* web error for lookup failure
*
@ -362,11 +235,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
}
});
openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.CallbackEnabled# */{
/**
* @constructs openerp.web.CallbackEnabled
* @extends openerp.web.Class
*/
openerp.web.CallbackEnabledMixin = {
init: function() {
// Transform on_* method into openerp.web.callbacks
for (var name in this) {
@ -404,7 +273,14 @@ openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.Ca
return self[method_name].apply(self, arguments);
}
}
});
};
openerp.web.CallbackEnabled = openerp.web.Class.extend(_.extend({}, nova.GetterSetterMixin, openerp.web.CallbackEnabledMixin, {
init: function() {
nova.GetterSetterMixin.init.call(this);
openerp.web.CallbackEnabledMixin.init.call(this);
}
}));
openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Connection# */{
/**
@ -422,7 +298,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
this.name = openerp._session_id;
this.qweb_mutex = new $.Mutex();
},
bind: function(origin) {
bind_session: function(origin) {
var window_origin = location.protocol+"//"+location.host, self=this;
this.origin = origin ? _.str.rtrim(origin,'/') : window_origin;
this.prefix = this.origin;
@ -445,6 +321,269 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
this.active_id = null;
return this.session_init();
},
test_eval_get_context: function () {
var asJS = function (arg) {
if (arg instanceof py.object) {
return arg.toJSON();
}
return arg;
};
var datetime = new py.object();
datetime.datetime = new py.type(function datetime() {
throw new Error('datetime.datetime not implemented');
});
var date = datetime.date = new py.type(function date(y, m, d) {
if (y instanceof Array) {
d = y[2];
m = y[1];
y = y[0];
}
this.year = asJS(y);
this.month = asJS(m);
this.day = asJS(d);
}, py.object, {
strftime: function (args) {
var f = asJS(args[0]), self = this;
return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) {
switch (c) {
case 'Y': return self.year;
case 'm': return _.str.sprintf('%02d', self.month);
case 'd': return _.str.sprintf('%02d', self.day);
}
throw new Error('ValueError: No known conversion for ' + m);
}));
}
});
date.__getattribute__ = function (name) {
if (name === 'today') {
return date.today;
}
throw new Error("AttributeError: object 'date' has no attribute '" + name +"'");
};
date.today = new py.def(function () {
var d = new Date();
return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
});
datetime.time = new py.type(function time() {
throw new Error('datetime.time not implemented');
});
var time = new py.object();
time.strftime = new py.def(function (args) {
return date.today.__call__().strftime(args);
});
var relativedelta = new py.type(function relativedelta(args, kwargs) {
if (!_.isEmpty(args)) {
throw new Error('Extraction of relative deltas from existing datetimes not supported');
}
this.ops = kwargs;
}, py.object, {
__add__: function (other) {
if (!(other instanceof datetime.date)) {
return py.NotImplemented;
}
// TODO: test this whole mess
var year = asJS(this.ops.year) || asJS(other.year);
if (asJS(this.ops.years)) {
year += asJS(this.ops.years);
}
var month = asJS(this.ops.month) || asJS(other.month);
if (asJS(this.ops.months)) {
month += asJS(this.ops.months);
// FIXME: no divmod in JS?
while (month < 1) {
year -= 1;
month += 12;
}
while (month > 12) {
year += 1;
month -= 12;
}
}
var lastMonthDay = new Date(year, month, 0).getDate();
var day = asJS(this.ops.day) || asJS(other.day);
if (day > lastMonthDay) { day = lastMonthDay; }
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
if (days_offset) {
day = new Date(year, month-1, day + days_offset).getDate();
}
// TODO: leapdays?
// TODO: hours, minutes, seconds? Not used in XML domains
// TODO: weekday?
return new datetime.date(year, month, day);
},
__radd__: function (other) {
return this.__add__(other);
},
__sub__: function (other) {
if (!(other instanceof datetime.date)) {
return py.NotImplemented;
}
// TODO: test this whole mess
var year = asJS(this.ops.year) || asJS(other.year);
if (asJS(this.ops.years)) {
year -= asJS(this.ops.years);
}
var month = asJS(this.ops.month) || asJS(other.month);
if (asJS(this.ops.months)) {
month -= asJS(this.ops.months);
// FIXME: no divmod in JS?
while (month < 1) {
year -= 1;
month += 12;
}
while (month > 12) {
year += 1;
month -= 12;
}
}
var lastMonthDay = new Date(year, month, 0).getDate();
var day = asJS(this.ops.day) || asJS(other.day);
if (day > lastMonthDay) { day = lastMonthDay; }
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
if (days_offset) {
day = new Date(year, month-1, day - days_offset).getDate();
}
// TODO: leapdays?
// TODO: hours, minutes, seconds? Not used in XML domains
// TODO: weekday?
return new datetime.date(year, month, day);
},
__rsub__: function (other) {
return this.__sub__(other);
}
});
return {
uid: new py.float(this.uid),
datetime: datetime,
time: time,
relativedelta: relativedelta
};
},
/**
* FIXME: Huge testing hack, especially the evaluation context, rewrite + test for real before switching
*/
test_eval: function (source, expected) {
try {
var ctx = this.test_eval_contexts(source.contexts);
if (!_.isEqual(ctx, expected.context)) {
console.group('Local context does not match remote, nothing is broken but please report to R&D (xmo)');
console.warn('source', source.contexts);
console.warn('local', ctx);
console.warn('remote', expected.context);
console.groupEnd();
}
} catch (e) {
console.group('Failed to evaluate contexts, nothing is broken but please report to R&D (xmo)');
console.error(e);
console.log('source', source.contexts);
console.groupEnd();
}
try {
var dom = this.test_eval_domains(source.domains, this.test_eval_get_context());
if (!_.isEqual(dom, expected.domain)) {
console.group('Local domain does not match remote, nothing is broken but please report to R&D (xmo)');
console.warn('source', source.domains);
console.warn('local', dom);
console.warn('remote', expected.domain);
console.groupEnd();
}
} catch (e) {
console.group('Failed to evaluate domains, nothing is broken but please report to R&D (xmo)');
console.error(e);
console.log('source', source.domains);
console.groupEnd();
}
try {
var groups = this.test_eval_groupby(source.group_by_seq);
if (!_.isEqual(groups, expected.group_by)) {
console.group('Local groupby does not match remote, nothing is broken but please report to R&D (xmo)');
console.warn('source', source.group_by_seq);
console.warn('local', groups);
console.warn('remote', expected.group_by);
console.groupEnd();
}
} catch (e) {
console.group('Failed to evaluate groupby, nothing is broken but please report to R&D (xmo)');
console.error(e);
console.log('source', source.group_by_seq);
console.groupEnd();
}
},
test_eval_contexts: function (contexts) {
var result_context = _.extend({}, this.user_context),
self = this;
_(contexts).each(function (ctx) {
switch(ctx.__ref) {
case 'context':
_.extend(result_context, py.eval(ctx.__debug));
break;
case 'compound_context':
_.extend(
result_context, self.test_eval_contexts(ctx.__contexts));
break;
default:
_.extend(result_context, ctx);
}
});
return result_context;
},
test_eval_domains: function (domains, eval_context) {
var result_domain = [], self = this;
_(domains).each(function (dom) {
switch(dom.__ref) {
case 'domain':
result_domain.push.apply(
result_domain, py.eval(dom.__debug, eval_context));
break;
case 'compound_domain':
result_domain.push.apply(
result_domain, self.test_eval_domains(
dom.__domains, eval_context));
break;
default:
result_domain.push.apply(
result_domain, dom);
}
});
return result_domain;
},
test_eval_groupby: function (contexts) {
var result_group = [], self = this;
_(contexts).each(function (ctx) {
var group;
switch(ctx.__ref) {
case 'context':
group = py.eval(ctx.__debug).group_by;
break;
case 'compound_context':
group = self.test_eval_contexts(ctx.__contexts).group_by;
break;
default:
group = ctx.group_by
}
if (!group) { return; }
if (typeof group === 'string') {
result_group.push(group);
} else if (group instanceof Array) {
result_group.push.apply(result_group, group);
} else {
throw new Error('Got invalid groupby {{'
+ JSON.stringify(group) + '}}');
}
});
return result_group;
},
/**
* Executes an RPC call, registering the provided callbacks.
*
@ -480,6 +619,9 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
function (response, textStatus, jqXHR) {
self.on_rpc_response();
if (!response.error) {
if (url.url === '/web/session/eval_domain_and_context') {
self.test_eval(params, response.result);
}
deferred.resolve(response["result"], textStatus, jqXHR);
} else if (response.error.data.type === "session_invalid") {
self.uid = false;
@ -733,6 +875,14 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"];
return self.rpc('/web/webclient/jslist', {mods: to_load}).pipe(function(files) {
return self.do_load_js(file_list.concat(files));
}).then(function () {
if (!Date.CultureInfo.pmDesignator) {
// If no am/pm designator is specified but the openerp
// datetime format uses %i, date.js won't be able to
// correctly format a date. See bug#938497.
Date.CultureInfo.amDesignator = 'AM';
Date.CultureInfo.pmDesignator = 'PM';
}
});
}))
}
@ -873,9 +1023,12 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
_(_.extend({}, options.data || {},
{session_id: this.session_id, token: token}))
.each(function (value, key) {
$('<input type="hidden" name="' + key + '">')
.val(value)
.appendTo($form_data);
var $input = $form.find('[name=' + key +']');
if (!$input.length) {
$input = $('<input type="hidden" name="' + key + '">')
.appendTo($form_data);
}
$input.val(value)
});
$form
@ -958,11 +1111,11 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
*
* And of course, when you don't need that widget anymore, just do:
*
* my_widget.stop();
* my_widget.destroy();
*
* That will kill the widget in a clean way and erase its content from the dom.
*/
openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.Widget# */{
openerp.web.Widget = nova.Widget.extend(_.extend({}, openerp.web.CallbackEnabledMixin, {
/**
* The name of the QWeb template that will be used for rendering. Must be
* redefined in subclasses or the default render() method can not be used.
@ -970,11 +1123,6 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W
* @type string
*/
template: null,
/**
* Tag name when creating a default $element.
* @type string
*/
tag_name: 'div',
/**
* Constructs the widget and sets its parent if a parent is given.
*
@ -982,7 +1130,7 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W
* @extends openerp.web.CallbackEnabled
*
* @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling stop(), the current instance will be
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
* @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
* to bind the current Widget to an already existing part of the DOM, which is not compatible
@ -990,132 +1138,24 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W
* for new components this argument should not be provided any more.
*/
init: function(parent) {
this._super();
this._super(parent);
openerp.web.CallbackEnabledMixin.init.call(this);
this.session = openerp.connection;
this.$element = $(document.createElement(this.tag_name));
this.widget_parent = parent;
this.widget_children = [];
if(parent && parent.widget_children) {
parent.widget_children.push(this);
}
// useful to know if the widget was destroyed and should not be used anymore
this.widget_is_stopped = false;
},
/**
* Renders the current widget and appends it to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
* Renders the element. The default implementation renders the widget using QWeb,
* `this.template` must be defined. The context given to QWeb contains the "widget"
* key that references `this`.
*/
appendTo: function(target) {
var self = this;
return this._render_and_insert(function(t) {
self.$element.appendTo(t);
}, target);
},
/**
* Renders the current widget and prepends it to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
prependTo: function(target) {
var self = this;
return this._render_and_insert(function(t) {
self.$element.prependTo(t);
}, target);
},
/**
* Renders the current widget and inserts it after to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
insertAfter: function(target) {
var self = this;
return this._render_and_insert(function(t) {
self.$element.insertAfter(t);
}, target);
},
/**
* Renders the current widget and inserts it before to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
insertBefore: function(target) {
var self = this;
return this._render_and_insert(function(t) {
self.$element.insertBefore(t);
}, target);
},
/**
* Renders the current widget and replaces the given jQuery object.
*
* @param target A jQuery object or a Widget instance.
*/
replace: function(target) {
return this._render_and_insert(_.bind(function(t) {
this.$element.replaceAll(t);
}, this), target);
},
_render_and_insert: function(insertion, target) {
this.render_element();
if (target instanceof openerp.web.Widget)
target = target.$element;
insertion(target);
this.on_inserted(this.$element, this);
return this.start();
},
on_inserted: function(element, widget) {},
/**
* Renders the element and insert the result of the render() method in this.$element.
*/
render_element: function() {
var rendered = this.render();
if (rendered) {
renderElement: function() {
var rendered = null;
if (this.template)
rendered = openerp.web.qweb.render(this.template, {widget: this});
if (_.str.trim(rendered)) {
var elem = $(rendered);
this.$element.replaceWith(elem);
this.$element = elem;
}
return this;
},
/**
* Renders the widget using QWeb, `this.template` must be defined.
* The context given to QWeb contains the "widget" key that references `this`.
*
* @param {Object} additional Additional context arguments to pass to the template.
*/
render: function (additional) {
if (this.template)
return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {}));
return null;
},
/**
* Method called after rendering. Mostly used to bind actions, perform asynchronous
* calls, etc...
*
* By convention, the method should return a promise to inform the caller when
* this widget has been initialized.
*
* @returns {jQuery.Deferred}
*/
start: function() {
return $.Deferred().done().promise();
},
/**
* Destroys the current widget, also destroys all its children before destroying itself.
*/
stop: function() {
_.each(_.clone(this.widget_children), function(el) {
el.stop();
});
if(this.$element != null) {
this.$element.remove();
}
if (this.widget_parent && this.widget_parent.widget_children) {
this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this);
}
this.widget_parent = null;
this.widget_is_stopped = true;
},
/**
* Informs the action manager to do an action. This supposes that
@ -1123,37 +1163,36 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W
* If that's not the case this method will simply return `false`.
*/
do_action: function(action, on_finished) {
if (this.widget_parent) {
return this.widget_parent.do_action(action, on_finished);
if (this.getParent()) {
return this.getParent().do_action(action, on_finished);
}
return false;
},
do_notify: function() {
if (this.widget_parent) {
return this.widget_parent.do_notify.apply(this,arguments);
if (this.getParent()) {
return this.getParent().do_notify.apply(this,arguments);
}
return false;
},
do_warn: function() {
if (this.widget_parent) {
return this.widget_parent.do_warn.apply(this,arguments);
if (this.getParent()) {
return this.getParent().do_warn.apply(this,arguments);
}
return false;
},
rpc: function(url, data, success, error) {
var def = $.Deferred().then(success, error);
var self = this;
openerp.connection.rpc(url, data). then(function() {
if (!self.widget_is_stopped)
if (!self.isDestroyed())
def.resolve.apply(def, arguments);
}, function() {
if (!self.widget_is_stopped)
if (!self.isDestroyed())
def.reject.apply(def, arguments);
});
return def.promise();
}
});
}));
/**
* @deprecated use :class:`openerp.web.Widget`
@ -1164,7 +1203,21 @@ openerp.web.OldWidget = openerp.web.Widget.extend({
this.element_id = element_id;
this.element_id = this.element_id || _.uniqueId('widget-');
var tmp = document.getElementById(this.element_id);
this.$element = tmp ? $(tmp) : $(document.createElement(this.tag_name));
this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName));
},
renderElement: function() {
var rendered = this.render();
if (rendered) {
var elem = $(rendered);
this.$element.replaceWith(elem);
this.$element = elem;
}
return this;
},
render: function (additional) {
if (this.template)
return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {}));
return null;
}
});
@ -1196,7 +1249,7 @@ openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.we
if (translation_bundle.lang_parameters) {
this.parameters = translation_bundle.lang_parameters;
this.parameters.grouping = py.eval(
this.parameters.grouping).toJSON();
this.parameters.grouping);
}
},
add_module_translation: function(mod) {
@ -1244,7 +1297,7 @@ openerp.web._lt = function (s) {
return {toString: function () { return openerp.web._t(s); }}
};
openerp.web.qweb = new QWeb2.Engine();
openerp.web.qweb.debug = (window.location.search.indexOf('?debug') !== -1);
openerp.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined);
openerp.web.qweb.default_dict = {
'_' : _,
'_t' : openerp.web._t
@ -1279,6 +1332,23 @@ openerp.web.qweb.preprocess_node = function() {
}
};
/**
* A small utility function to check if a class implements correctly an interface, assuming that
* interface is simply specified using a dictionary containing methods and attributes with the
* correct type. It only performs the check when in debug mode and the only effect of an invalid
* check is messages in the console.
*/
openerp.web.check_interface = function(_class, _interface) {
if (! openerp.web.check_interface.debug)
return;
for (var member in _interface) {
if ( (typeof _class.prototype[member] != typeof _interface[member]) ) {
console.error("class failed to implement interface member '" + member + "'");
}
}
}
openerp.web.check_interface.debug = ($.deparam($.param.querystring()).debug != undefined);
/** Jquery extentions */
$.Mutex = (function() {
function Mutex() {

View File

@ -1012,18 +1012,25 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
: (v1 > v2) ? 1
: 0;
};
records.sort(function (a, b) {
return _.reduce(sort_fields, function (acc, field) {
if (acc) { return acc; }
var sign = 1;
if (field[0] === '-') {
sign = -1;
field = field.slice(1);
}
return sign * compare(a[field], b[field]);
}, 0);
});
// Array.sort is not necessarily stable. We must be careful with this because
// sorting an array where all items are considered equal is a worst-case that
// will randomize the array with an unstable sort! Therefore we must avoid
// sorting if there are no sort_fields (i.e. all items are considered equal)
// See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11
// http://code.google.com/p/v8/issues/detail?id=90
if (sort_fields.length) {
records.sort(function (a, b) {
return _.reduce(sort_fields, function (acc, field) {
if (acc) { return acc; }
var sign = 1;
if (field[0] === '-') {
sign = -1;
field = field.slice(1);
}
return sign * compare(a[field], b[field]);
}, 0);
});
}
completion.resolve(records);
};
if(to_get.length > 0) {

View File

@ -66,11 +66,11 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
this._super();
this.open({
buttons: [
{text: _t("Close"), click: function() { self.stop(); }},
{text: _t("Close"), click: function() { self.destroy(); }},
{text: _t("Import File"), click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
],
close: function(event, ui) {
self.stop();
self.destroy();
}
});
this.toggle_import_button(false);
@ -151,7 +151,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
});
},
toggle_import_button: function (newstate) {
this.$element.dialog('widget')
openerp.web.dialog(this.$element, 'widget')
.find('.oe-dialog-import-button')
.button('option', 'disabled', !newstate);
},
@ -201,10 +201,10 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
return;
}
if (results['success']) {
if (this.widget_parent.widget_parent.active_view == "list") {
this.widget_parent.reload_content();
if (this.getParent().getParent().active_view == "list") {
this.getParent().reload_content();
}
this.stop();
this.destroy();
return;
}
@ -358,7 +358,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
}
return true;
},
stop: function() {
destroy: function() {
this.$element.remove();
this._super();
}

View File

@ -148,7 +148,7 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) {
if (typeof(value) == "string")
value = openerp.web.auto_str_to_date(value);
return value.toString(normalize_format(l10n.time_format));
case 'selection':
case 'selection': case 'statusbar':
// Each choice is [value, label]
if(_.isArray(value)) {
value = value[0]
@ -336,7 +336,7 @@ openerp.web.format_cell = function (row_data, column, options) {
case 'progressbar':
return _.template(
'<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
value: row_data[column.id].value
value: _.str.sprintf("%.0f", row_data[column.id].value || 0)
});
}

View File

@ -32,6 +32,8 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
this.hidden = !!hidden;
this.headless = this.hidden && !this.has_defaults;
this.filter_data = {};
this.ready = $.Deferred();
},
start: function() {
@ -232,7 +234,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
context.add({"group_by": groupbys});
var dial_html = QWeb.render("SearchView.managed-filters.add");
var $dial = $(dial_html);
$dial.dialog({
openerp.web.dialog($dial, {
modal: true,
title: _t("Filter Entry"),
buttons: [
@ -263,10 +265,20 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
var filter = this.managed_filters[val];
this.do_clear(false).then(_.bind(function() {
select.val('get:' + val);
var groupbys = _.map(filter.context.group_by.split(","), function(el) {
return {"group_by": el};
});
this.on_search([filter.domain], [filter.context], groupbys);
var groupbys = [];
var group_by = filter.context.group_by;
if (group_by) {
groupbys = _.map(
group_by instanceof Array ? group_by : group_by.split(','),
function (el) { return { group_by: el }; });
}
this.filter_data = {
domains: [filter.domain],
contexts: [filter.context],
groupbys: groupbys
};
this.do_search();
}, this));
} else {
select.val('');
@ -281,7 +293,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
selected_menu_id : menu.$element.find('a.active').data('menu')
}));
$dialog.find('input').val(this.fields_view.name);
$dialog.dialog({
openerp.web.dialog($dialog, {
modal: true,
title: _t("Add to Dashboard"),
buttons: [
@ -303,10 +315,10 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
});
self.rpc('/web/searchview/add_to_dashboard', {
menu_id: menu_id,
action_id: self.widget_parent.action.id,
action_id: self.getParent().action.id,
context_to_save: context,
domain: domain,
view_mode: self.widget_parent.active_view,
view_mode: self.getParent().active_view,
name: title
}, function(r) {
if (r === false) {
@ -334,9 +346,6 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
if (this.headless && !this.has_defaults) {
return this.on_search([], [], []);
}
// reset filters management
var select = this.$element.find(".oe_search-view-filters-management");
select.val("_filters");
if (e && e.preventDefault) { e.preventDefault(); }
@ -380,6 +389,16 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
.map(function (filter) { return filter.get_context();})
.compact()
.value();
if (this.filter_data.contexts) {
contexts = this.filter_data.contexts.concat(contexts)
}
if (this.filter_data.domains) {
domains = this.filter_data.domains.concat(domains);
}
if (this.filter_data.groupbys) {
groupbys = this.filter_data.groupbys.concat(groupbys);
}
return {domains: domains, contexts: contexts, errors: errors, groupbys: groupbys};
},
/**
@ -418,6 +437,9 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
* @param {Boolean} [reload_view=true]
*/
do_clear: function (reload_view) {
this.filter_data = {};
this.$element.find(".oe_search-view-filters-management").val('');
this.$element.find('.filter_label, .filter_icon').removeClass('enabled');
this.enabled_filters.splice(0);
var string = $('a.searchview_group_string');
@ -552,7 +574,7 @@ openerp.web.search.Widget = openerp.web.OldWidget.extend( /** @lends openerp.web
* "Stops" the widgets. Called when the view destroys itself, this
* lets the widgets clean up after themselves.
*/
stop: function () {
destroy: function () {
delete this.view;
this._super();
},
@ -1114,7 +1136,7 @@ openerp.web.search.ExtendedSearch = openerp.web.search.Input.extend({
if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") {
return null;
}
return _.reduce(this.widget_children,
return _.reduce(this.getChildren(),
function(mem, x) { return mem.concat(x.get_domain());}, []);
},
on_activate: function() {
@ -1133,9 +1155,9 @@ openerp.web.search.ExtendedSearch = openerp.web.search.Input.extend({
}
},
check_last_element: function() {
_.each(this.widget_children, function(x) {x.set_last_group(false);});
if (this.widget_children.length >= 1) {
this.widget_children[this.widget_children.length - 1].set_last_group(true);
_.each(this.getChildren(), function(x) {x.set_last_group(false);});
if (this.getChildren().length >= 1) {
this.getChildren()[this.getChildren().length - 1].set_last_group(true);
}
}
});
@ -1148,7 +1170,7 @@ openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({
},
add_prop: function() {
var prop = new openerp.web.search.ExtendedSearchProposition(this, this.fields);
var render = prop.render({'index': this.widget_children.length - 1});
var render = prop.render({'index': this.getChildren().length - 1});
this.$element.find('.searchview_extended_propositions_list').append(render);
prop.start();
},
@ -1159,11 +1181,11 @@ openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({
_this.add_prop();
});
this.$element.find('.searchview_extended_delete_group').click(function () {
_this.stop();
_this.destroy();
});
},
get_domain: function() {
var props = _(this.widget_children).chain().map(function(x) {
var props = _(this.getChildren()).chain().map(function(x) {
return x.get_proposition();
}).compact().value();
var choice = this.$element.find(".searchview_extended_group_choice").val();
@ -1172,10 +1194,10 @@ openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({
_.map(_.range(_.max([0,props.length - 1])), function() { return op; }),
props);
},
stop: function() {
var parent = this.widget_parent;
if (this.widget_parent.widget_children.length == 1)
this.widget_parent.hide();
destroy: function() {
var parent = this.getParent();
if (this.getParent().getChildren().length == 1)
this.getParent().hide();
this._super();
parent.check_last_element();
},
@ -1210,16 +1232,16 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/**
_this.changed();
});
this.$element.find('.searchview_extended_delete_prop').click(function () {
_this.stop();
_this.destroy();
});
},
stop: function() {
destroy: function() {
var parent;
if (this.widget_parent.widget_children.length == 1)
parent = this.widget_parent;
if (this.getParent().getChildren().length == 1)
parent = this.getParent();
this._super();
if (parent)
parent.stop();
parent.destroy();
},
changed: function() {
var nval = this.$element.find(".searchview_extended_prop_field").val();
@ -1235,7 +1257,7 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/**
select_field: function(field) {
var self = this;
if(this.attrs.selected != null) {
this.value.stop();
this.value.destroy();
this.value = null;
this.$element.find('.searchview_extended_prop_op').html('');
}

View File

@ -1002,10 +1002,10 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
$.when(action_manager.do_action(action)).then(function() {
var controller = action_manager.dialog_viewmanager.views['form'].controller;
controller.on_button_cancel.add_last(function(){
action_manager.stop()
action_manager.destroy()
});
controller.do_save.add_last(function(){
action_manager.stop();
action_manager.destroy();
var value =controller.fields.name.value;
self.add_node_dialog.$element.find('select[id=field_value]').append($("<option selected></option>").attr("value",value).text(value));
_.detect(self.add_widget,function(widget){

View File

@ -74,13 +74,13 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}, this.on_loaded);
}
},
stop: function() {
destroy: function() {
if (this.sidebar) {
this.sidebar.attachments.stop();
this.sidebar.stop();
this.sidebar.attachments.destroy();
this.sidebar.destroy();
}
_.each(this.widgets, function(w) {
w.stop();
w.destroy();
});
this._super();
},
@ -90,17 +90,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
},
on_loaded: function(data) {
var self = this;
if (data) {
this.fields_order = [];
this.fields_view = data;
var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'widget': this });
if (!data) {
throw "No data provided.";
}
if (this.root_frame) {
throw "Form view does not support multiple calls to on_loaded";
}
this.fields_order = [];
this.fields_view = data;
this.rendered = QWeb.render(this.form_template, {'widget': this});
this.$element.html(this.rendered);
_.each(this.widgets, function(w) {
w.start();
});
this.root_frame = instanciate_widget(this.registry.get_object('frame'), this, this.fields_view.arch);
var to_append = $(".oe_form_header", this.$element);
this.root_frame.appendTo(to_append.length > 0 ? to_append : this.$element);
this.root_frame.$element.children().unwrap();
this.$form_header = this.$element.find('.oe_form_header:first');
this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
var action = $(this).data('pager-action');
@ -205,7 +211,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
if (self.sidebar) {
self.sidebar.attachments.do_update();
}
if (self.default_focus_field && !self.embedded_view) {
if (self.default_focus_field) {
self.default_focus_field.focus();
}
if (record.id) {
@ -418,7 +424,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.on_form_changed();
}
if (!_.isEmpty(result.warning)) {
$(QWeb.render("CrashManagerWarning", result.warning)).dialog({
openerp.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), {
modal: true,
buttons: [
{text: _t("Ok"), click: function() { $(this).dialog("close"); }}
@ -879,7 +885,7 @@ openerp.web.form.compute_domain = function(expr, fields) {
};
openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{
template: 'Widget',
form_template: 'Widget',
/**
* @constructs openerp.web.form.Widget
* @extends openerp.web.OldWidget
@ -894,13 +900,9 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true);
this.type = this.type || node.tag;
this.element_name = this.element_name || this.type;
this.element_class = [
'formview', this.view.view_id, this.element_name,
this.view.widgets_counter++].join("_");
this._super(view);
this.view.widgets[this.element_class] = this;
this.children = node.children;
this.colspan = parseInt(node.attrs.colspan || 1, 10);
this.decrease_max_width = 0;
@ -919,14 +921,9 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
this.align = 'center';
}
this.width = this.node.attrs.width;
},
start: function() {
this.$element = this.view.$element.find(
'.' + this.element_class.replace(/[^\r\n\f0-9A-Za-z_-]/g, "\\$&"));
},
stop: function() {
destroy: function() {
this._super.apply(this, arguments);
$.fn.tipsy.clear();
},
@ -939,9 +936,8 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
update_dom: function() {
this.$element.toggle(!this.invisible);
},
render: function() {
var template = this.template;
return QWeb.render(template, { "widget": this });
renderElement: function() {
this.$element.html(QWeb.render(this.form_template, { "widget": this }));
},
do_attach_tooltip: function(widget, trigger, options) {
widget = widget || this;
@ -951,7 +947,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
delayOut: 0,
fade: true,
title: function() {
var template = widget.template + '.tooltip';
var template = widget.form_template + '.tooltip';
if (!QWeb.has_template(template)) {
template = 'WidgetLabel.tooltip';
}
@ -1016,7 +1012,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
});
openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
template: 'WidgetFrame',
form_template: 'WidgetFrame',
init: function(view, node) {
this._super(view, node);
this.columns = parseInt(node.attrs.col || 4, 10);
@ -1034,6 +1030,23 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
}
this.set_row_cells_with(this.table[this.table.length - 1]);
},
renderElement: function() {
this._super();
var self = this;
_.each(this.table, function(row) {
_.each(row, function(td) {
td.$element = self.$element.find('.' + td.element_class);
td.renderElement();
});
});
},
start: function() {
_.each(this.table, function(row) {
_.each(row, function(td) {
td.start();
});
});
},
add_row: function(){
if (this.table.length) {
this.set_row_cells_with(this.table[this.table.length - 1]);
@ -1081,14 +1094,14 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
node.attrs.nolabel = '1';
}
}
var widget = new (this.view.registry.get_any(
[node.attrs.widget, type.type, node.tag])) (this.view, node);
var widget = instanciate_widget(this.view.registry.get_any(
[node.attrs.widget, type.type, node.tag]), this.view, node);
if (node.tag == 'field') {
if (!this.view.default_focus_field || node.attrs.default_focus == '1') {
this.view.default_focus_field = widget;
}
if (node.attrs.nolabel != '1') {
var label = new (this.view.registry.get_object('label')) (this.view, node);
var label = instanciate_widget(this.view.registry.get_object('label'), this.view, node);
label["for"] = widget;
this.add_widget(label, widget.colspan + 1);
}
@ -1110,23 +1123,31 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
});
openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({
template: 'WidgetGroup'
form_template: 'WidgetGroup'
}),
openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
template: 'WidgetNotebook',
form_template: 'WidgetNotebook',
init: function(view, node) {
this._super(view, node);
this.pages = [];
for (var i = 0; i < node.children.length; i++) {
var n = node.children[i];
if (n.tag == "page") {
var page = new (this.view.registry.get_object('notebookpage'))(
var page = instanciate_widget(this.view.registry.get_object('notebookpage'),
this.view, n, this, this.pages.length);
this.pages.push(page);
}
}
},
renderElement: function() {
this._super();
var self = this;
_.each(this.pages, function(page) {
page.$element = self.$element.find('.' + page.element_class);
page.renderElement();
});
},
start: function() {
var self = this;
this._super.apply(this, arguments);
@ -1146,6 +1167,9 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
gravity: 's'
});
}
_.each(this.pages, function(page) {
page.start();
});
},
do_select_first_visible_tab: function() {
for (var i = 0; i < this.pages.length; i++) {
@ -1159,7 +1183,7 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
});
openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
template: 'WidgetNotebookPage',
form_template: 'WidgetNotebookPage',
init: function(view, node, notebook, index) {
this.notebook = notebook;
this.index = index;
@ -1181,7 +1205,7 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
});
openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
template: 'WidgetSeparator',
form_template: 'WidgetSeparator',
init: function(view, node) {
this._super(view, node);
this.orientation = node.attrs.orientation || 'horizontal';
@ -1193,7 +1217,7 @@ openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
});
openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
template: 'WidgetButton',
form_template: 'WidgetButton',
init: function(view, node) {
this._super(view, node);
this.force_disabled = false;
@ -1227,7 +1251,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
var exec_action = function() {
if (self.node.attrs.confirm) {
var def = $.Deferred();
var dialog = $('<div>' + self.node.attrs.confirm + '</div>').dialog({
var dialog = openerp.web.dialog($('<div>' + self.node.attrs.confirm + '</div>'), {
title: _t('Confirm'),
modal: true,
buttons: [
@ -1283,7 +1307,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
});
openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
template: 'WidgetLabel',
form_template: 'WidgetLabel',
init: function(view, node) {
this.element_name = 'label_' + node.attrs.name;
@ -1295,7 +1319,7 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
}
if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) {
this.template = "WidgetParagraph";
this.form_template = "WidgetParagraph";
this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
// Widgets default to right-aligned, but paragraph defaults to
// left-aligned
@ -1311,12 +1335,15 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
this.nowrap = true;
}
},
render: function () {
renderElement: function() {
var rendered;
if (this['for'] && this.type !== 'label') {
return QWeb.render(this.template, {widget: this['for']});
rendered = QWeb.render(this.form_template, {widget: this['for']});
} else {
// Actual label widgets should not have a false and have type label
rendered = QWeb.render(this.form_template, {widget: this});
}
// Actual label widgets should not have a false and have type label
return QWeb.render(this.template, {widget: this});
this.$element.html(rendered);
},
start: function() {
this._super();
@ -1332,6 +1359,58 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
}
});
/**
* Interface to be implemented by fields.
*
* Novajs Attributes:
* - ...
*
* Novajs Events:
* - ...
*
*/
openerp.web.form.FieldInterface = {
/**
* Called by the form view to indicate the value of the field.
*
* set_value() may return an object that can be passed to $.when() that represents the moment when
* the field has finished all operations necessary before the user can effectively use the widget.
*
* Multiple calls to set_value() can occur at any time and must be handled correctly by the implementation,
* regardless of any asynchronous operation currently running and the status of any promise that a
* previous call to set_value() could have returned.
*
* set_value() must be able, at any moment, to handle the syntax returned by the "read" method of the
* osv class in the OpenERP server as well as the syntax used by the set_value() (see below). It must
* also be able to handle any other format commonly used in the _defaults key on the models in the addons
* as well as any format commonly returned in a on_change. It must be able to autodetect those formats as
* no information is ever given to know which format is used.
*/
set_value: function(value) {},
/**
* Get the current value of the widget.
*
* Must always return a syntaxically correct value to be passed to the "write" method of the osv class in
* the OpenERP server, although it is not assumed to respect the constraints applied to the field.
* For example if the field is marqued as "required", a call to get_value() can return false.
*
* get_value() can also be called *before* a call to set_value() and, in that case, is supposed to
* return a defaut value according to the type of field.
*
* This method is always assumed to perform synchronously, it can not return a promise.
*
* If there was no user interaction to modify the value of the field, it is always assumed that
* get_value() return the same semantic value than the one passed in the last call to set_value(),
* altough the syntax can be different. This can be the case for type of fields that have a different
* syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0).
*/
get_value: function() {},
};
/**
* Abstract class for classes implementing FieldInterface. Should be renamed to AbstractField some
* day.
*/
openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{
/**
* @constructs openerp.web.form.Field
@ -1448,7 +1527,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
});
openerp.web.form.FieldChar = openerp.web.form.Field.extend({
template: 'FieldChar',
form_template: 'FieldChar',
init: function (view, node) {
this._super(view, node);
this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
@ -1493,7 +1572,7 @@ openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({
});
openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
template: 'FieldEmail',
form_template: 'FieldEmail',
start: function() {
this._super.apply(this, arguments);
this.$element.find('button').click(this.on_button_clicked);
@ -1508,7 +1587,7 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
});
openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
template: 'FieldUrl',
form_template: 'FieldUrl',
start: function() {
this._super.apply(this, arguments);
this.$element.find('button').click(this.on_button_clicked);
@ -1526,16 +1605,11 @@ openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
init: function (view, node) {
this._super(view, node);
if (node.attrs.digits) {
this.parse_digits(node.attrs.digits);
this.digits = py.eval(node.attrs.digits).toJSON();
} else {
this.digits = view.fields_view.fields[node.attrs.name].digits;
}
},
parse_digits: function (digits_attr) {
// could use a Python parser instead.
var match = /^\s*[\(\[](\d+),\s*(\d+)/.exec(digits_attr);
return [parseInt(match[1], 10), parseInt(match[2], 10)];
},
set_value: function(value) {
if (value === false || value === undefined) {
// As in GTK client, floats default to 0
@ -1631,7 +1705,7 @@ openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
});
openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
template: "EmptyComponent",
form_template: "EmptyComponent",
build_widget: function() {
return new openerp.web.DateTimeWidget(this);
},
@ -1668,7 +1742,7 @@ openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
});
openerp.web.form.FieldText = openerp.web.form.Field.extend({
template: 'FieldText',
form_template: 'FieldText',
start: function() {
this._super.apply(this, arguments);
this.$element.find('textarea').change(this.on_ui_change);
@ -1729,7 +1803,7 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
});
openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
template: 'FieldBoolean',
form_template: 'FieldBoolean',
start: function() {
var self = this;
this._super.apply(this, arguments);
@ -1753,7 +1827,7 @@ openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
});
openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
template: 'FieldProgressBar',
form_template: 'FieldProgressBar',
start: function() {
this._super.apply(this, arguments);
this.$element.find('div').progressbar({
@ -1777,7 +1851,7 @@ openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
});
openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
template: 'FieldSelection',
form_template: 'FieldSelection',
init: function(view, node) {
var self = this;
this._super(view, node);
@ -1883,7 +1957,7 @@ openerp.web.form.dialog = function(content, options) {
};
openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
template: 'FieldMany2One',
form_template: 'FieldMany2One',
init: function(view, node) {
this._super(view, node);
this.limit = 7;
@ -1912,9 +1986,13 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
$cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
var bindings = {};
bindings[self.cm_id + "_search"] = function() {
if (self.readonly)
return;
self._search_create_popup("search");
};
bindings[self.cm_id + "_create"] = function() {
if (self.readonly)
return;
self._search_create_popup("form");
};
bindings[self.cm_id + "_open"] = function() {
@ -2019,6 +2097,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
minLength: 0,
delay: 0
});
this.$input.autocomplete("widget").addClass("openerp");
// 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) {
@ -2201,6 +2280,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
this.$input.prop('readonly', this.readonly);
}
});
openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface);
/*
# Values: (0, 0, { fields }) create
@ -2248,7 +2328,7 @@ var commands = {
}
};
openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
template: 'FieldOne2Many',
form_template: 'FieldOne2Many',
multi_selection: false,
init: function(view, node) {
this._super(view, node);
@ -2522,7 +2602,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
this.previous_readonly = this.readonly;
if (this.viewmanager) {
this.is_loaded = this.is_loaded.pipe(function() {
self.viewmanager.stop();
self.viewmanager.destroy();
return $.when(self.load_views()).then(function() {
self.reload_current_view();
});
@ -2622,7 +2702,7 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
});
openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
template: 'FieldMany2Many',
form_template: 'FieldMany2Many',
multi_selection: false,
init: function(view, node) {
this._super(view, node);
@ -2702,7 +2782,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
this.previous_readonly = this.readonly;
if (this.list_view) {
this.is_loaded = this.is_loaded.pipe(function() {
self.list_view.stop();
self.list_view.destroy();
return $.when(self.load_view()).then(function() {
self.reload_content();
});
@ -2750,7 +2830,7 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
var pop = new openerp.web.form.FormOpenPopup(this);
pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
title: _t("Open: ") + this.name,
readonly: this.widget_parent.is_readonly()
readonly: this.getParent().is_readonly()
});
pop.on_write_completed.add_last(function() {
self.reload_content();
@ -2787,7 +2867,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
}, read_function: null});
this.initial_ids = this.options.initial_ids;
this.created_elements = [];
this.render_element();
this.renderElement();
openerp.web.form.dialog(this.$element, {
close: function() {
self.check_exit();
@ -2831,10 +2911,14 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
this.new_object();
}
},
stop: function () {
this.$element.dialog('close');
this._super();
},
setup_search_view: function(search_defaults) {
var self = this;
if (this.searchview) {
this.searchview.stop();
this.searchview.destroy();
}
this.searchview = new openerp.web.SearchView(this,
this.dataset, false, search_defaults);
@ -2864,7 +2948,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
$buttons.prepend(QWeb.render("SelectCreatePopup.search.buttons"));
var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close");
$cbutton.click(function() {
self.stop();
self.destroy();
});
var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select");
if(self.options.disable_multiple_selection) {
@ -2872,7 +2956,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
}
$sbutton.click(function() {
self.on_select_elements(self.selected_ids);
self.stop();
self.destroy();
});
});
});
@ -2956,7 +3040,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
if (this.created_elements.length > 0) {
this.on_select_elements(this.created_elements);
}
this.stop();
this.destroy();
},
on_default_get: function(res) {}
});
@ -2967,7 +3051,7 @@ openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
},
select_record: function(index) {
this.popup.on_select_elements([this.dataset.ids[index]]);
this.popup.stop();
this.popup.destroy();
},
do_select: function(ids, records) {
this._super(ids, records);
@ -2996,8 +3080,8 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
this.row_id = row_id;
this.context = context || {};
this.options = _.defaults(options || {}, {"auto_write": true});
this.render_element();
this.$element.dialog({
this.renderElement();
openerp.web.dialog(this.$element, {
title: options.title || '',
modal: true,
width: 960,
@ -3043,12 +3127,12 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
var $nbutton = $buttons.find(".oe_formopenpopup-form-save");
$nbutton.click(function() {
self.view_form.do_save().then(function() {
self.stop();
self.destroy();
});
});
var $cbutton = $buttons.find(".oe_formopenpopup-form-close");
$cbutton.click(function() {
self.stop();
self.destroy();
});
if (self.options.readonly) {
$nbutton.hide();
@ -3071,7 +3155,7 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
});
openerp.web.form.FieldReference = openerp.web.form.Field.extend({
template: 'FieldReference',
form_template: 'FieldReference',
init: function(view, node) {
this._super(view, node);
this.fields_view = {
@ -3086,7 +3170,7 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
};
this.get_fields_values = view.get_fields_values;
this.get_selected_ids = view.get_selected_ids;
this.do_onchange = this.on_form_changed = this.on_nop;
this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop;
this.dataset = this.view.dataset;
this.widgets_counter = 0;
this.view_id = 'reference_' + _.uniqueId();
@ -3117,7 +3201,11 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
},
start: function() {
this._super();
this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
this.selection.renderElement();
this.selection.start();
this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element);
this.m2o.renderElement();
this.m2o.start();
},
is_valid: function() {
@ -3226,7 +3314,7 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
});
openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
template: 'FieldBinaryFile',
form_template: 'FieldBinaryFile',
update_dom: function() {
this._super.apply(this, arguments);
this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly);
@ -3264,7 +3352,7 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
});
openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
template: 'FieldBinaryImage',
form_template: 'FieldBinaryImage',
start: function() {
this._super.apply(this, arguments);
this.$image = this.$element.find('img.oe-binary-image');
@ -3276,8 +3364,14 @@ openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
set_value: function(value) {
this._super.apply(this, arguments);
this.set_image_maxwidth();
var url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
var url;
if (value && value.substr(0, 10).indexOf(' ') == -1) {
url = 'data:image/png;base64,' + this.value;
} else {
url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
}
this.$image.attr('src', url);
},
set_image_maxwidth: function() {
@ -3299,7 +3393,7 @@ openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
});
openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
template: "EmptyComponent",
form_template: "EmptyComponent",
start: function() {
this._super();
this.selected_value = null;
@ -3364,10 +3458,11 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
});
openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({
render: function () {
renderElement: function () {
var $root = $('<div class="oe_form_html_view">');
this.render_children(this, $root);
return $root.html();
var rendered = $root.html();
this.$element.html(rendered);
},
render_children: function (object, $into) {
var self = this,
@ -3376,10 +3471,9 @@ openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({
if (typeof child === 'string') {
$into.text(child);
} else if (child.tag === 'field') {
$into.append(
new (self.view.registry.get_object('frame'))(
self.view, {tag: 'ueule', attrs: {}, children: [child] })
.render());
var widget = instanciate_widget(self.view.registry.get_object('frame'),
self.view, {tag: 'ueule', attrs: {}, children: [child] });
widget.appendTo($into);
} else {
var $child = $(document.createElement(child.tag))
.attr(child.attrs)
@ -3426,6 +3520,14 @@ openerp.web.form.widgets = new openerp.web.Registry({
'html': 'openerp.web.form.WidgetHtml'
});
var instanciate_widget = function(claz, view, node, o1, o2) {
var widget = new (claz)(view, node, o1, o2);
widget.element_class = (['formview', view.view_id, widget.element_name,
view.widgets_counter++].join("_")).replace(/[^\r\n\f0-9A-Za-z_-]/g, "_");
view.widgets[widget.element_class] = widget;
return widget;
}
};

View File

@ -96,7 +96,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
if (this._limit === undefined) {
this._limit = (this.options.limit
|| this.defaults.limit
|| (this.widget_parent.action || {}).limit
|| (this.getParent().action || {}).limit
|| 80);
}
return this._limit;
@ -158,7 +158,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
var pair = this.colors[i],
color = pair[0],
expression = pair[1];
if (py.evaluate(expression, _.extend({bool: py.bool}, context))) {
if (py.evaluate(expression, context).toJSON()) {
return 'color: ' + color + ';';
}
// TODO: handle evaluation errors
@ -291,7 +291,10 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
*/
configure_pager: function (dataset) {
this.dataset.ids = dataset.ids;
this.dataset._length = dataset._length;
// Not exactly clean
if (dataset._length) {
this.dataset._length = dataset._length;
}
var limit = this.limit(),
total = dataset.size(),
@ -360,7 +363,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
_(this.fields_view.arch.children).map(field_to_column));
if (grouped) {
this.columns.unshift({
id: '_group', tag: '', string: "Group", meta: true,
id: '_group', tag: '', string: _t("Group"), meta: true,
modifiers_for: function () { return {}; }
}, {
id: '_count', tag: '', string: '#', meta: true,
@ -541,19 +544,17 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
* @param {Array} records selected record values
*/
do_select: function (ids, records) {
this.$element.find('.oe-list-delete')
.attr('disabled', !ids.length);
if (this.sidebar) {
if (ids.length) {
this.sidebar.do_unfold();
} else {
this.sidebar.do_fold();
}
}
if (!records.length) {
this.$element.find('.oe-list-delete').attr('disabled', !ids.length);
if (!ids.length) {
this.dataset.index = 0;
if (this.sidebar) { this.sidebar.do_fold(); }
this.compute_aggregates();
return;
}
this.dataset.index = _(this.dataset.ids).indexOf(ids[0]);
if (this.sidebar) { this.sidebar.do_unfold(); }
this.compute_aggregates(_(records).map(function (record) {
return {count: 1, values: record};
}));
@ -1260,9 +1261,11 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
if (column.meta) {
// do not do anything
} else if (column.id in group.aggregates) {
var value = group.aggregates[column.id];
var r = {};
r[column.id] = {value: group.aggregates[column.id]};
$('<td class="oe-number">')
.html(openerp.web.format_value(value, column))
.html(openerp.web.format_cell(
r, column, {process_modifiers: false}))
.appendTo($row);
} else {
$row.append('<td>');

View File

@ -139,7 +139,7 @@ openerp.web.list_editable = function (openerp) {
}
cancelled.then(function () {
self.view.unpad_columns();
self.edition_form.stop();
self.edition_form.destroy();
self.edition_form.$element.remove();
delete self.edition_form;
delete self.edition_id;
@ -235,26 +235,16 @@ openerp.web.list_editable = function (openerp) {
}
self.edition = true;
self.edition_id = record_id;
self.edition_form = _.extend(new openerp.web.ListEditableFormView(self.view, self.dataset, false), {
form_template: 'ListView.row.form',
registry: openerp.web.list.form.widgets,
$element: $new_row
});
// HA HA
self.edition_form.appendTo();
self.edition_form = new openerp.web.ListEditableFormView(self.view, self.dataset, false);
self.edition_form.$element = $new_row;
self.edition_form.editable_list = self;
// HO HO
// empty
$.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
// put in $.when just in case FormView.on_loaded becomes asynchronous
$new_row.find('> td')
.addClass('oe-field-cell')
.removeAttr('width')
.end()
.find('td:last').removeClass('oe-field-cell').end();
if (self.options.selectable) {
$new_row.prepend('<th>');
}
if (self.options.isClarkGable) {
$new_row.prepend('<th>');
}
// pad in case of groupby
_(self.columns).each(function (column) {
if (column.meta) {
@ -383,7 +373,7 @@ openerp.web.list_editable = function (openerp) {
openerp.web.list.form = {};
}
openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({
template: 'ListView.row.frame'
form_template: 'ListView.row.frame'
});
var form_widgets = openerp.web.form.widgets;
openerp.web.list.form.widgets = form_widgets.extend({
@ -418,9 +408,10 @@ openerp.web.list_editable = function (openerp) {
});
openerp.web.ListEditableFormView = openerp.web.FormView.extend({
init_view: function() {},
_render_and_insert: function () {
return this.start();
}
form_template: 'ListView.row.form',
init: function() {
this._super.apply(this, arguments);
this.registry = openerp.web.list.form.widgets;
},
});
};

View File

@ -68,13 +68,13 @@ openerp.web.page = function (openerp) {
openerp.web.page = {};
openerp.web.page.WidgetFrameReadonly = openerp.web.form.WidgetFrame.extend({
template: 'WidgetFrame.readonly'
form_template: 'WidgetFrame.readonly'
});
openerp.web.page.FieldReadonly = openerp.web.form.Field.extend({
});
openerp.web.page.FieldCharReadonly = openerp.web.page.FieldReadonly.extend({
template: 'FieldChar.readonly',
form_template: 'FieldChar.readonly',
init: function(view, node) {
this._super(view, node);
this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
@ -89,13 +89,27 @@ openerp.web.page = function (openerp) {
return show_value;
}
});
openerp.web.page.FieldFloatReadonly = openerp.web.page.FieldCharReadonly.extend({
init: function (view, node) {
this._super(view, node);
if (node.attrs.digits) {
this.digits = py.eval(node.attrs.digits).toJSON();
} else {
this.digits = view.fields_view.fields[node.attrs.name].digits;
}
}
});
openerp.web.page.FieldURIReadonly = openerp.web.page.FieldCharReadonly.extend({
template: 'FieldURI.readonly',
form_template: 'FieldURI.readonly',
scheme: null,
format_value: function (value) {
return value;
},
set_value: function (value) {
if (!value) {
this.$element.find('a').text('').attr('href', '#');
return;
}
this.$element.find('a')
.attr('href', this.scheme + ':' + value)
.text(this.format_value(value));
@ -106,6 +120,10 @@ openerp.web.page = function (openerp) {
});
openerp.web.page.FieldUrlReadonly = openerp.web.page.FieldURIReadonly.extend({
set_value: function (value) {
if (!value) {
this.$element.find('a').text('').attr('href', '#');
return;
}
var s = /(\w+):(.+)/.exec(value);
if (!s) {
value = "http://" + value;
@ -120,7 +138,7 @@ openerp.web.page = function (openerp) {
}
});
openerp.web.page.FieldSelectionReadonly = openerp.web.page.FieldReadonly.extend({
template: 'FieldChar.readonly',
form_template: 'FieldChar.readonly',
init: function(view, node) {
// lifted straight from r/w version
var self = this;
@ -225,7 +243,7 @@ openerp.web.page = function (openerp) {
}
});
openerp.web.page.FieldBinaryFileReadonly = openerp.web.form.FieldBinary.extend({
template: 'FieldURI.readonly',
form_template: 'FieldURI.readonly',
start: function() {
this._super.apply(this, arguments);
var self = this;
@ -261,7 +279,7 @@ openerp.web.page = function (openerp) {
'one2many_list' : 'openerp.web.page.FieldOne2ManyReadonly',
'reference': 'openerp.web.page.FieldReferenceReadonly',
'boolean': 'openerp.web.page.FieldBooleanReadonly',
'float': 'openerp.web.page.FieldCharReadonly',
'float': 'openerp.web.page.FieldFloatReadonly',
'integer': 'openerp.web.page.FieldCharReadonly',
'float_time': 'openerp.web.page.FieldCharReadonly',
'binary': 'openerp.web.page.FieldBinaryFileReadonly',

View File

@ -146,7 +146,7 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
var pair = this.colors[i],
color = pair[0],
expression = pair[1];
if (py.evaluate(expression, _.extend({bool: py.bool}, context))) {
if (py.evaluate(expression, context).toJSON()) {
return 'color: ' + color + ';';
}
// TODO: handle evaluation errors
@ -224,21 +224,25 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
active_model: self.dataset.model,
active_id: id,
active_ids: [id]};
this.rpc('/web/treeview/action', {
return this.rpc('/web/treeview/action', {
id: id,
model: this.dataset.model,
context: new openerp.web.CompoundContext(
this.dataset.get_context(), local_context)
}, function (actions) {
}).pipe(function (actions) {
if (!actions.length) { return; }
var action = actions[0][2];
var c = new openerp.web.CompoundContext(local_context);
if (action.context) {
c.add(action.context);
}
action.context = c;
self.do_action(action);
});
return self.rpc('/web/session/eval_domain_and_context', {
contexts: [c], domains: []
}).pipe(function (res) {
action.context = res.context;
return self.do_action(action);
}, null);
}, null);
},
// show & hide the contents

View File

@ -30,31 +30,31 @@ session.web.ActionManager = session.web.OldWidget.extend({
},
dialog_stop: function () {
if (this.dialog) {
this.dialog_viewmanager.stop();
this.dialog_viewmanager.destroy();
this.dialog_viewmanager = null;
this.dialog.stop();
this.dialog.destroy();
this.dialog = null;
}
},
content_stop: function () {
if (this.inner_viewmanager) {
this.inner_viewmanager.stop();
this.inner_viewmanager.destroy();
this.inner_viewmanager = null;
}
if (this.client_widget) {
this.client_widget.stop();
this.client_widget.destroy();
this.client_widget = null;
}
},
do_push_state: function(state) {
if (this.widget_parent && this.widget_parent.do_push_state) {
if (this.getParent() && this.getParent().do_push_state) {
if (this.inner_action) {
state['model'] = this.inner_action.res_model;
if (this.inner_action.id) {
state['action_id'] = this.inner_action.id;
}
}
this.widget_parent.do_push_state(state);
this.getParent().do_push_state(state);
}
},
do_load_state: function(state, warm) {
@ -65,6 +65,9 @@ session.web.ActionManager = session.web.OldWidget.extend({
if (run_action) {
this.null_action();
action_loaded = this.do_action(state.action_id);
session.webclient.menu.has_been_loaded.then(function() {
session.webclient.menu.open_action(state.action_id);
});
}
} else if (state.model && state.id) {
// TODO handle context & domain ?
@ -142,7 +145,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
if(on_close)
this.dialog.on_close.add(on_close);
} else {
this.dialog_viewmanager.stop();
this.dialog_viewmanager.destroy();
}
this.dialog.dialog_title = action.name;
this.dialog_viewmanager = new session.web.ViewManagerAction(this, action);
@ -150,7 +153,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
this.dialog.open();
} else {
if(action.menu_id) {
return this.widget_parent.do_action(action, function () {
return this.getParent().do_action(action, function () {
session.webclient.menu.open_menu(action.menu_id);
});
}
@ -180,7 +183,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
this.content_stop();
this.dialog_stop();
var ClientWidget = session.web.client_actions.get_object(action.tag);
(this.client_widget = new ClientWidget(this, action.params)).appendTo(this);
(this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element);
},
ir_actions_report_xml: function(action, on_closed) {
var self = this;
@ -209,7 +212,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
window.open(action.url, action.target === 'self' ? '_self' : '_blank');
},
ir_ui_menu: function (action) {
this.widget_parent.do_action(action);
this.getParent().do_action(action);
}
});
@ -385,7 +388,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
setup_search_view: function(view_id, search_defaults) {
var self = this;
if (this.searchview) {
this.searchview.stop();
this.searchview.destroy();
}
this.searchview = new session.web.SearchView(
this, this.dataset,
@ -408,6 +411,9 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
var groupby = results.group_by.length
? results.group_by
: action_context.group_by;
if (_.isString(groupby)) {
groupby = [groupby];
}
controller.do_search(results.domain, results.context, groupby || []);
});
},
@ -680,9 +686,9 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
});
},
do_push_state: function(state) {
if (this.widget_parent && this.widget_parent.do_push_state) {
if (this.getParent() && this.getParent().do_push_state) {
state["view_type"] = this.active_view;
this.widget_parent.do_push_state(state);
this.getParent().do_push_state(state);
}
},
do_load_state: function(state, warm) {
@ -702,7 +708,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
},
shortcut_check : function(view) {
var self = this;
var grandparent = this.widget_parent && this.widget_parent.widget_parent;
var grandparent = this.getParent() && this.getParent().getParent();
// display shortcuts if on the first view for the action
var $shortcut_toggle = this.$element.find('.oe-shortcut-toggle');
if (!this.action.name ||
@ -796,8 +802,8 @@ session.web.Sidebar = session.web.OldWidget.extend({
},
add_default_sections: function() {
var self = this,
view = this.widget_parent,
view_manager = view.widget_parent,
view = this.getParent(),
view_manager = view.getParent(),
action = view_manager.action;
if (this.session.uid === 1) {
this.add_section(_t('Customize'), 'customize');
@ -912,11 +918,11 @@ session.web.Sidebar = session.web.OldWidget.extend({
},
on_item_action_clicked: function(item) {
var self = this;
self.widget_parent.sidebar_context().then(function (context) {
var ids = self.widget_parent.get_selected_ids();
self.getParent().sidebar_context().then(function (context) {
var ids = self.getParent().get_selected_ids();
if (ids.length == 0) {
//TODO: make prettier warning?
$("<div />").text(_t("You must choose at least one record.")).dialog({
openerp.web.dialog($("<div />").text(_t("You must choose at least one record.")), {
title: _t("Warning"),
modal: true
});
@ -925,7 +931,7 @@ session.web.Sidebar = session.web.OldWidget.extend({
var additional_context = _.extend({
active_id: ids[0],
active_ids: ids,
active_model: self.widget_parent.dataset.model
active_model: self.getParent().dataset.model
}, context);
self.rpc("/web/action/load", {
action_id: item.action.id,
@ -937,7 +943,7 @@ session.web.Sidebar = session.web.OldWidget.extend({
result.result.flags.new_window = true;
self.do_action(result.result, function () {
// reload view
self.widget_parent.reload();
self.getParent().reload();
});
});
});
@ -1111,8 +1117,8 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
var self = this;
var result_handler = function () {
if (on_closed) { on_closed.apply(null, arguments); }
if (self.widget_parent && self.widget_parent.on_action_executed) {
return self.widget_parent.on_action_executed.apply(null, arguments);
if (self.getParent() && self.getParent().on_action_executed) {
return self.getParent().on_action_executed.apply(null, arguments);
}
};
var context = new session.web.CompoundContext(dataset.get_context(), action_data.context || {});
@ -1184,8 +1190,8 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
this.$element.hide();
},
do_push_state: function(state) {
if (this.widget_parent && this.widget_parent.do_push_state) {
this.widget_parent.do_push_state(state);
if (this.getParent() && this.getParent().do_push_state) {
this.getParent().do_push_state(state);
}
},
do_load_state: function(state, warm) {

View File

@ -17,35 +17,24 @@
</div>
</div>
</t>
<t t-name="Interface">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" class="main_table">
<tr>
<td colspan="2" valign="top">
<div id="oe_header" class="header"></div>
<div id="oe_menu" class="menu"></div>
</td>
</tr>
<tr>
<td colspan="2" valign="top" height="100%">
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="100%">
<tr>
<td valign="top" id="oe_secondary_menu" class="secondary_menu"></td>
<td valign="top" class="oe-application-container">
<div id="oe_app" class="oe-application">
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<div id="oe_footer" class="oe_footer">
<p class="oe_footer_powered">Powered by <a href="http://www.openerp.com">OpenERP</a></p>
<t t-name="WebClient">
<div class="oe_webclient">
<div class="oe_topbar">
<div class="oe_menu_placeholder"/>
<div class="oe_user_menu_placeholder"/>
<div class="oe_systray"/>
</div>
<div class="oe_leftbar">
<a href="#" class="oe_logo"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a>
<div class="oe_secondary_menus_container"/>
<div class="oe_footer">
Powered by <a href="http://www.openerp.com"><span>Open</span>ERP</a>
</div>
</td>
</tr>
</table>
</div>
<div class="oe_application"/>
</div>
</t>
<t t-name="Loading">
<div id="oe_loading">
@ -154,7 +143,7 @@
<option t-att-value="db"><t t-esc="db"/></option>
</t>
</select>
<input t-if="!db_list" name="drop_db" class="required"
<input t-if="!db_list" name="backup_db" class="required"
type="text" autofocus="autofocus"/>
</td>
</tr>
@ -335,81 +324,73 @@
<div id="oe_db_options" class="oe_db_options"></div>
</div>
</t>
<t t-name="Header">
<div>
<a t-att-href="'/' + widget.qs" class="company_logo_link">
<div class="company_logo" />
</a>
</div>
</t>
<t t-name="Header-content">
<h1 class="header_title">
<t t-esc="user.company_id[1]"/> (<t t-esc="widget.session.db"/>)<br/>
<small class="username"><t t-esc="user.name"/></small>
</h1>
<div class="header_corner">
<ul class="block">
<li>
<a t-att-href="'/' + widget.qs" title="Home" class="home"><img t-att-src='_s + "/web/static/src/img/header-home.png"' width="16" height="16" border="0"/></a>
</li>
<li class="preferences">
<a href="javascript:void(0)" title="Preferences" class="preferences"><img t-att-src='_s + "/web/static/src/img/header-preferences.png"' width="16" height="16" border="0"/></a>
</li>
<li>
<a href="javascript:void(0)" title="About" class="about"><img t-att-src='_s + "/web/static/src/img/header-about.png"' width="16" height="16" border="0"/></a>
</li>
</ul>
<div class="block">
<a href="javascript:void(0)" class="logout">LOGOUT</a>
</div>
</div>
<div class="oe-shortcuts"> </div>
</t>
<ul t-name="Shortcuts">
<li t-foreach="shortcuts" t-as="shortcut"
t-att-data-id="shortcut.res_id"
t-att-data-shortcut-id="shortcut.id"
><t t-esc="shortcut.name"/></li>
</ul>
<t t-name="Menu">
<table align="center">
<tr>
<td t-foreach="widget.data.data.children" t-as="menu">
<a href="#" t-att-data-menu="menu.id">
<t t-esc="menu.name"/>
</a>
</td>
</tr>
</table>
<ul class="oe_menu" t-if="widget.data">
<li t-foreach="widget.data.data.children" t-as="menu">
<t t-call="Menu.secondary.link"/>
</li>
</ul>
</t>
<t t-name="Menu.secondary">
<div t-attf-class="oe_toggle_secondary_menu">
<span class="oe_menu_fold" title="Fold menu"><t t-raw="'&amp;laquo;'"/></span>
<span class="oe_menu_unfold" title="Unfold menu"><t t-raw="'&amp;raquo;'"/></span>
</div>
<div t-foreach="widget.data.data.children" t-as="menu" style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu.id">
<t t-foreach="menu.children" t-as="menu">
<t t-set="classname" t-translation="off">oe_secondary_menu_item</t>
<t t-set="level" t-value="0"/>
<t t-call="Menu.secondary.children"/>
<div class="oe_secondary_menu_section">
<t t-esc="menu.name"/>
<!--
Shall the section be still clickable ?
<t t-call="Menu.secondary.link"/>
-->
</div>
<t t-call="Menu.secondary.submenu"/>
</t>
</div>
</t>
<t t-name="Menu.secondary.children">
<t t-set="level" t-value="level + 1"/>
<a href="#" t-att-id="menu.children.length ? 'menu_' + menu.id : undefined"
t-att-class="classname + (menu.children.length ? ' submenu' : ' leaf') + (menu_first and level == 1 ? ' opened' : '')"
t-att-data-menu="menu.children.length ? undefined : menu.id">
<span t-attf-style="padding-left: #{(level - 2) * 20}px"> <t t-esc="menu.name"/></span>
<t t-name="Menu.secondary.submenu">
<ul t-if="menu.children.length" class="oe_secondary_submenu">
<li t-foreach="menu.children" t-as="menu">
<t t-call="Menu.secondary.link"/>
<!--<span class="oe_menu_label">8</span>-->
<t t-call="Menu.secondary.submenu"/>
</li>
</ul>
</t>
<t t-name="Menu.secondary.link">
<a href="#"
t-att-class="menu.children.length ? 'oe_menu_toggler' : 'oe_menu_leaf'"
t-att-data-menu="menu.id"
t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''"
t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''">
<t t-esc="menu.name"/>
</a>
<div t-attf-class="oe_secondary_submenu" t-if="menu.children.length" t-att-style="menu_first and level == 1 ? undefined : 'display: none'">
<t t-foreach="menu.children" t-as="menu">
<t t-set="classname" t-translation="off">oe_secondary_submenu_item</t>
<t t-call="Menu.secondary.children"/>
</t>
</div>
</t>
<t t-name="UserMenu">
<ul class="oe_user_menu oe_topbar_item">
<li class="oe_dropdown">
<a href="#" class="oe_dropdown_toggle">
<img class="oe_topbar_avatar" t-att-data-default-src="_s + '/web/static/src/img/topbar-avatar.png'"/>
<span class="oe_topbar_name"/>
</a>
<ul class="oe_dropdown_options">
<!--<li><a href="#" data-menu="profile">Profile</a></li>-->
<li><a href="#" data-menu="settings">Settings</a></li>
<li><a href="#" data-menu="logout">Log out</a></li>
<li><hr/></li>
<li><a href="#" data-menu="about">About OpenERP</a></li>
<li><hr/></li>
</ul>
</li>
</ul>
</t>
<t t-name="UserMenu.shortcut">
<li t-foreach="shortcuts" t-as="shortcut">
<a href="#" data-menu="shortcut" t-att-data-id="shortcut.res_id" t-att-data-shortcut-id="shortcut.id">
<t t-esc="shortcut.name"/>
</a>
</li>
</t>
<t t-name="ViewManager">
<table class="view-manager-main-table" cellpadding="0" cellspacing="0">
<tbody>
@ -728,7 +709,8 @@
</td>
</tr>
<t t-name="ListView.row.form">
<t t-raw="frame.render()"/>
<th t-if="widget.editable_list.options.selectable"></th>
<th t-if="widget.editable_list.options.isClarkGable"></th>
</t>
<t t-name="FormView">
@ -743,7 +725,6 @@
</t>
</div>
</div>
<t t-raw="frame.render()"/>
</t>
<t t-name="One2Many.formview" t-extend="FormView">
<t t-jquery=".oe_form_buttons" t-operation="inner">
@ -892,7 +873,8 @@
</li>
</ul>
<t t-foreach="widget.pages" t-as="page">
<t t-raw="page.render()"/>
<div t-att-class="page.element_class">
</div>
</t>
</t>
<t t-name="WidgetNotebook.tooltip">
@ -909,9 +891,7 @@
</t>
</t>
<t t-name="WidgetNotebookPage">
<div>
<t t-call="WidgetFrame"/>
</div>
<t t-call="WidgetFrame"/>
</t>
<t t-name="WidgetSeparator">
<div t-if="widget.orientation !== 'vertical'" t-att-class="'separator ' + widget.orientation">
@ -1107,11 +1087,9 @@
<t t-name="FieldReference">
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="oe_frame oe_forms">
<tr>
<td t-attf-class="oe_form_frame_cell oe_form_selection #{widget.selection.element_class}">
<t t-raw="widget.selection.render()"/>
<td t-attf-class="oe_form_frame_cell oe_form_selection oe_form_view_reference_selection">
</td>
<td t-attf-class="oe_form_frame_cell oe_form_many2one #{widget.m2o.element_class}" nowrap="true" style="display: none">
<t t-raw="widget.m2o.render()"/>
<td t-attf-class="oe_form_frame_cell oe_form_many2one oe_form_view_reference_m2o" nowrap="true" style="display: none">
</td>
</tr>
</table>
@ -1128,7 +1106,7 @@
</div>
</t>
<t t-name="FieldBinaryImage">
<table cellpadding="0" cellspacing="0" border="0">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td align="center">
<img t-att-src='_s + "/web/static/src/img/placeholder.png"' class="oe-binary-image"
@ -1523,21 +1501,20 @@
<td t-if="edited and !options.deletable" class="oe-listview-padding"/>
</t>
</t>
<t t-name="ListView.row.frame" t-extend="WidgetFrame">
<t t-jquery="tr">
$(document.createElement('t'))
.append(this.contents())
.attr({
't-foreach': this.attr('t-foreach'),
't-as': this.attr('t-as')
})
.replaceAll(this)
.after($(document.createElement('td')).append(
$(document.createElement('button')).attr({
'class': 'oe-edit-row-save', 'type': 'button'})
.html(' ')))
.unwrap();
<t t-name="ListView.row.frame">
<t t-foreach="widget.table" t-as="row">
<t t-foreach="row" t-as="td">
<td t-att-colspan="td.colspan gt 1 ? td.colspan : undefined"
t-att-valign="td.table ? 'top' : undefined"
t-attf-class="oe_form_frame_cell #{td.classname} #{td.element_class} oe-field-cell"
>
<t t-raw="td.render()"/>
</td>
</t>
</t>
<td>
<button class="oe-edit-row-save" type="button"> </button>
</td>
</t>
<t t-name="view_editor">
<table class="oe_view_editor">

View File

@ -10,6 +10,7 @@ class TestDataSetController(unittest2.TestCase):
self.read = self.request.session.model().read
self.search = self.request.session.model().search
@unittest2.skip
def test_empty_find(self):
self.search.return_value = []
self.read.return_value = []
@ -17,6 +18,7 @@ class TestDataSetController(unittest2.TestCase):
self.assertFalse(self.dataset.do_search_read(self.request, 'fake.model'))
self.read.assert_called_once_with([], False, self.request.context)
@unittest2.skip
def test_regular_find(self):
self.search.return_value = [1, 2, 3]
@ -24,6 +26,7 @@ class TestDataSetController(unittest2.TestCase):
self.read.assert_called_once_with([1, 2, 3], False,
self.request.context)
@unittest2.skip
def test_ids_shortcut(self):
self.search.return_value = [1, 2, 3]
self.read.return_value = [
@ -37,6 +40,7 @@ class TestDataSetController(unittest2.TestCase):
[{'id': 1}, {'id': 2}, {'id': 3}])
self.assertFalse(self.read.called)
@unittest2.skip
def test_get(self):
self.read.return_value = [
{'id': 1, 'name': 'baz'},

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
import mock
import unittest2
import web.controllers.main
import openerpweb.openerpweb
from ..controllers import main
from ..common.session import OpenERPSession
class Placeholder(object):
def __init__(self, **kwargs):
@ -11,17 +12,16 @@ class Placeholder(object):
class LoadTest(unittest2.TestCase):
def setUp(self):
self.menu = web.controllers.main.Menu()
self.menu = main.Menu()
self.menus_mock = mock.Mock()
self.request = Placeholder(
session=openerpweb.openerpweb.OpenERPSession(
model_factory=lambda _session, _name: self.menus_mock))
self.request = Placeholder(session=OpenERPSession())
def tearDown(self):
del self.request
del self.menus_mock
del self.menu
@unittest2.skip
def test_empty(self):
self.menus_mock.search = mock.Mock(return_value=[])
self.menus_mock.read = mock.Mock(return_value=[])
@ -36,6 +36,7 @@ class LoadTest(unittest2.TestCase):
root['children'],
[])
@unittest2.skip
def test_applications_sort(self):
self.menus_mock.search = mock.Mock(return_value=[1, 2, 3])
self.menus_mock.read = mock.Mock(return_value=[
@ -62,6 +63,7 @@ class LoadTest(unittest2.TestCase):
'parent_id': False, 'children': []
}])
@unittest2.skip
def test_deep(self):
self.menus_mock.search = mock.Mock(return_value=[1, 2, 3, 4])
self.menus_mock.read = mock.Mock(return_value=[
@ -100,7 +102,7 @@ class LoadTest(unittest2.TestCase):
class ActionMungerTest(unittest2.TestCase):
def setUp(self):
self.menu = web.controllers.main.Menu()
self.menu = main.Menu()
def test_actual_treeview(self):
action = {
"views": [[False, "tree"], [False, "form"],
@ -111,10 +113,11 @@ class ActionMungerTest(unittest2.TestCase):
}
changed = action.copy()
del action['view_type']
web.controllers.main.fix_view_modes(changed)
main.fix_view_modes(changed)
self.assertEqual(changed, action)
@unittest2.skip
def test_list_view(self):
action = {
"views": [[False, "tree"], [False, "form"],
@ -123,7 +126,7 @@ class ActionMungerTest(unittest2.TestCase):
"view_id": False,
"view_mode": "tree,form,calendar"
}
web.controllers.main.fix_view_modes(action)
main.fix_view_modes(action)
self.assertEqual(action, {
"views": [[False, "list"], [False, "form"],
@ -132,6 +135,7 @@ class ActionMungerTest(unittest2.TestCase):
"view_mode": "list,form,calendar"
})
@unittest2.skip
def test_redundant_views(self):
action = {
@ -141,7 +145,7 @@ class ActionMungerTest(unittest2.TestCase):
"view_id": False,
"view_mode": "tree,form,calendar"
}
web.controllers.main.fix_view_modes(action)
main.fix_view_modes(action)
self.assertEqual(action, {
"views": [[False, "list"], [False, "form"],

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
import random
import unittest2
from ..controllers.main import topological_sort
def sample(population):
return random.sample(
population,
random.randint(0, min(len(population), 5)))
class TestModulesLoading(unittest2.TestCase):
def setUp(self):
self.mods = map(str, range(1000))
def test_topological_sort(self):
random.shuffle(self.mods)
modules = [
(k, sample(self.mods[:i]))
for i, k in enumerate(self.mods)]
random.shuffle(modules)
ms = dict(modules)
seen = set()
sorted_modules = topological_sort(ms)
for module in sorted_modules:
deps = ms[module]
self.assertGreaterEqual(
seen, set(deps),
'Module %s (index %d), ' \
'missing dependencies %s from loaded modules %s' % (
module, sorted_modules.index(module), deps, seen
))
seen.add(module)

View File

@ -6,8 +6,7 @@ import unittest2
import simplejson
import web.controllers.main
import openerpweb.nonliterals
import openerpweb.openerpweb
from ..common import nonliterals, session as s
def field_attrs(fields_view_get, fieldname):
(field,) = filter(lambda f: f['attrs'].get('name') == fieldname,
@ -47,7 +46,7 @@ class DomainsAndContextsTest(unittest2.TestCase):
)
def test_retrieve_nonliteral_domain(self):
session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
session = mock.Mock(spec=s.OpenERPSession)
session.domains_store = {}
domain_string = ("[('month','=',(datetime.date.today() - "
"datetime.timedelta(365/12)).strftime('%%m'))]")
@ -56,9 +55,9 @@ class DomainsAndContextsTest(unittest2.TestCase):
self.view.parse_domains_and_contexts(e, session)
self.assertIsInstance(e.get('domain'), openerpweb.nonliterals.Domain)
self.assertIsInstance(e.get('domain'), nonliterals.Domain)
self.assertEqual(
openerpweb.nonliterals.Domain(
nonliterals.Domain(
session, key=e.get('domain').key).get_domain_string(),
domain_string)
@ -90,7 +89,7 @@ class DomainsAndContextsTest(unittest2.TestCase):
)
def test_retrieve_nonliteral_context(self):
session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
session = mock.Mock(spec=s.OpenERPSession)
session.contexts_store = {}
context_string = ("{'month': (datetime.date.today() - "
"datetime.timedelta(365/12)).strftime('%%m')}")
@ -99,9 +98,9 @@ class DomainsAndContextsTest(unittest2.TestCase):
self.view.parse_domains_and_contexts(e, session)
self.assertIsInstance(e.get('context'), openerpweb.nonliterals.Context)
self.assertIsInstance(e.get('context'), nonliterals.Context)
self.assertEqual(
openerpweb.nonliterals.Context(
nonliterals.Context(
session, key=e.get('context').key).get_context_string(),
context_string)
@ -127,6 +126,8 @@ class AttrsNormalizationTest(unittest2.TestCase):
xml.etree.ElementTree.tostring(transformed),
xml.etree.ElementTree.tostring(pristine)
)
@unittest2.skip
def test_transform_states(self):
element = xml.etree.ElementTree.Element(
'field', states="open,closed")
@ -137,6 +138,7 @@ class AttrsNormalizationTest(unittest2.TestCase):
simplejson.loads(element.get('attrs')),
{'invisible': [['state', 'not in', ['open', 'closed']]]})
@unittest2.skip
def test_transform_invisible(self):
element = xml.etree.ElementTree.Element(
'field', invisible="context.get('invisible_country', False)")
@ -149,12 +151,13 @@ class AttrsNormalizationTest(unittest2.TestCase):
self.view.normalize_attrs(full_context, {'invisible_country': True})
self.assertEqual(full_context.get('invisible'), '1')
@unittest2.skip
def test_transform_invisible_list_column(self):
req = mock.Mock()
req.context = {'set_editable':True, 'set_visible':True,
'gtd_visible':True, 'user_invisible':True}
req.session.evaluation_context = \
openerpweb.openerpweb.OpenERPSession().evaluation_context
s.OpenERPSession().evaluation_context
req.session.model('project.task').fields_view_get.return_value = {
'arch': '''
<tree colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline&lt;current_date) and (state in ('draft','pending','open'))" string="Tasks">
@ -183,13 +186,17 @@ class ListViewTest(unittest2.TestCase):
self.view = web.controllers.main.ListView()
self.request = mock.Mock()
self.request.context = {'set_editable': True}
@unittest2.skip
def test_no_editable_editable_context(self):
self.request.session.model('fake').fields_view_get.return_value = \
{'arch': '<tree><field name="foo"/></tree>'}
view = self.view.fields_view_get(self.request, 'fake', False)
view = self.view.fields_view_get(self.request, 'fake', False, False)
self.assertEqual(view['arch']['attrs']['editable'],
'bottom')
@unittest2.skip
def test_editable_top_editable_context(self):
self.request.session.model('fake').fields_view_get.return_value = \
{'arch': '<tree editable="top"><field name="foo"/></tree>'}
@ -198,6 +205,7 @@ class ListViewTest(unittest2.TestCase):
self.assertEqual(view['arch']['attrs']['editable'],
'top')
@unittest2.skip
def test_editable_bottom_editable_context(self):
self.request.session.model('fake').fields_view_get.return_value = \
{'arch': '<tree editable="bottom"><field name="foo"/></tree>'}

View File

@ -19,5 +19,5 @@
'qweb' : [
"static/src/xml/*.xml",
],
'active': True
'auto_install': True
}

View File

@ -0,0 +1,39 @@
# Czech translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-03-04 12:08+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Czech <cs@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-03-05 04:53+0000\n"
"X-Generator: Launchpad (build 14900)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466
msgid "Responsible"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:504
msgid "Navigator"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr ""

View File

@ -8,15 +8,16 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-02-08 02:18-0600\n"
"Last-Translator: Carlos Vásquez - CLEARCORP <carlos.vasquez@clearcorp.co.cr>\n"
"PO-Revision-Date: 2012-02-16 21:34+0000\n"
"Last-Translator: Carlos Vásquez (CLEARCORP) "
"<carlos.vasquez@clearcorp.co.cr>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-02-07 04:59+0000\n"
"X-Generator: Launchpad (build 14747)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
"Language: es\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
@ -38,4 +39,3 @@ msgstr "Navegador"
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr "&nbsp;"

View File

@ -0,0 +1,41 @@
# Finnish translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-03-19 12:03+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Finnish <fi@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-03-20 05:13+0000\n"
"X-Generator: Launchpad (build 14969)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "kalenteri"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466
#: addons/web_calendar/static/src/js/calendar.js:467
msgid "Responsible"
msgstr "Vastuuhenkilö"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:504
#: addons/web_calendar/static/src/js/calendar.js:505
msgid "Navigator"
msgstr "Navigaattori"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr "&nbsp;"

View File

@ -8,29 +8,29 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2011-10-08 13:39+0000\n"
"Last-Translator: Nicola Riolini - Micronaet <Unknown>\n"
"PO-Revision-Date: 2012-02-16 21:55+0000\n"
"Last-Translator: Davide Corio - agilebg.com <davide.corio@agilebg.com>\n"
"Language-Team: Italian <it@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-02-07 04:59+0000\n"
"X-Generator: Launchpad (build 14747)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr ""
msgstr "Calendario"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466
msgid "Responsible"
msgstr ""
msgstr "Responsabile"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:504
msgid "Navigator"
msgstr ""
msgstr "Navigatore"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5

View File

@ -0,0 +1,39 @@
# Japanese translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-02-22 02:18+0000\n"
"Last-Translator: Masaki Yamaya <Unknown>\n"
"Language-Team: Japanese <ja@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-02-23 04:56+0000\n"
"X-Generator: Launchpad (build 14855)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "カレンダー"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466
msgid "Responsible"
msgstr "責任担当"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:504
msgid "Navigator"
msgstr "ナビゲータ"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr "&nbsp;"

View File

@ -0,0 +1,41 @@
# Georgian translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-03-15 18:25+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Georgian <ka@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-03-16 05:14+0000\n"
"X-Generator: Launchpad (build 14951)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "კალენდარი"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466
#: addons/web_calendar/static/src/js/calendar.js:467
msgid "Responsible"
msgstr "პასუხისმგებელი"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:504
#: addons/web_calendar/static/src/js/calendar.js:505
msgid "Navigator"
msgstr "ნავიგატორი"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr ""

View File

@ -8,19 +8,19 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-02-15 14:37+0000\n"
"PO-Revision-Date: 2012-02-16 14:02+0000\n"
"Last-Translator: Erwin <Unknown>\n"
"Language-Team: Dutch <nl@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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "Kalender"
msgstr "Agenda"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466

View File

@ -0,0 +1,41 @@
# Romanian translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-03-10 13:17+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Romanian <ro@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-03-11 04:56+0000\n"
"X-Generator: Launchpad (build 14914)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "Calendar"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:466
#: addons/web_calendar/static/src/js/calendar.js:467
msgid "Responsible"
msgstr "Responsabil"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:504
#: addons/web_calendar/static/src/js/calendar.js:505
msgid "Navigator"
msgstr "Navigator"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr "&nbsp;"

View File

@ -42,7 +42,7 @@ openerp.web_calendar.CalendarView = openerp.web.View.extend({
this._super();
return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': true}, this.on_loaded);
},
stop: function() {
destroy: function() {
scheduler.clearAll();
this._super();
},
@ -66,6 +66,8 @@ openerp.web_calendar.CalendarView = openerp.web.View.extend({
this.day_length = this.fields_view.arch.attrs.day_length || 8;
this.color_field = this.fields_view.arch.attrs.color;
this.color_string = this.fields_view.fields[this.color_field] ?
this.fields_view.fields[this.color_field].string : _t("Filter");
if (this.color_field && this.selected_filters.length === 0) {
var default_filter;
@ -463,8 +465,9 @@ openerp.web_calendar.CalendarFormDialog = openerp.web.Dialog.extend({
});
openerp.web_calendar.SidebarResponsible = openerp.web.OldWidget.extend({
// TODO: fme: in trunk, rename this class to SidebarFilter
init: function(parent, view) {
var $section = parent.add_section(_t('Responsible'), 'responsible');
var $section = parent.add_section(view.color_string, 'responsible');
this.$div = $('<div></div>');
$section.append(this.$div);
this._super(parent, $section.attr('id'));

View File

@ -14,5 +14,5 @@
'qweb' : [
"static/src/xml/*.xml",
],
'active': True
'auto_install': True
}

View File

@ -0,0 +1,111 @@
# Czech translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-03-04 12:09+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Czech <cs@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-03-05 04:53+0000\n"
"X-Generator: Launchpad (build 14900)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
msgid "Edit Layout"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:109
msgid "Are you sure you want to remove this item ?"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:316
msgid "Uncategorized"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6
msgid "Reset"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8
msgid "Change Layout.."
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10
msgid "Change Layout"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27
msgid "&nbsp;"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28
msgid "Create"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39
msgid "Choose dashboard layout"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:62
msgid "progress:"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:67
msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
msgid "Welcome to OpenERP"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr ""

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-13 17:56+0000\n"
"Last-Translator: Amós Oviedo <Unknown>\n"
"PO-Revision-Date: 2012-02-22 10:34+0000\n"
"Last-Translator: Jorge L Tupac-Yupanqui <Unknown>\n"
"Language-Team: Spanish <es@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-23 04:56+0000\n"
"X-Generator: Launchpad (build 14855)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -36,12 +36,12 @@ msgstr "Sin categoría"
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
msgstr "Ejecutar tarea \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
msgstr "Marcar esta tarea como terminada"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4

View File

@ -7,16 +7,16 @@ msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2012-02-07 10:13+0100\n"
"PO-Revision-Date: 2012-02-08 02:25-0600\n"
"Last-Translator: Carlos Vásquez - CLEARCORP <carlos.vasquez@clearcorp.co.cr>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-16 22:16+0000\n"
"Last-Translator: Freddy Gonzalez <freddy.gonzalez@clearcorp.co.cr>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-02-08 06:39+0000\n"
"X-Generator: Launchpad (build 14747)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
"Language: es\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -33,6 +33,17 @@ msgstr "¿Está seguro/a que desea eliminar este elemento?"
msgid "Uncategorized"
msgstr "Sin categoría"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr "Ejecutar tarea \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr "Marcar esta tarea como terminada"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
@ -75,8 +86,12 @@ msgstr "progreso:"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:67
msgid "Click on the functionalites listed below to launch them and configure your system"
msgstr "Haga clic en las funcionalidades enumeradas abajo para lanzarlas y configurar su sistema"
msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
"Haga clic en las funcionalidades enumeradas abajo para lanzarlas y "
"configurar su sistema"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
@ -97,4 +112,3 @@ msgstr "Esta dirección"
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr "Su usuario:"

View File

@ -0,0 +1,113 @@
# Finnish translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-03-19 12:00+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Finnish <fi@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-03-20 05:13+0000\n"
"X-Generator: Launchpad (build 14969)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
msgid "Edit Layout"
msgstr "Muokkaa näkymää"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:109
msgid "Are you sure you want to remove this item ?"
msgstr "Oletko varma että haluat poistaa tämän osan ?"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:316
msgid "Uncategorized"
msgstr "Luokittelemattomat"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr "Suorita tehtävä \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr "Merkitse tämä tehtävä valmiiksi"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
msgstr "Palauta asettelu.."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6
msgid "Reset"
msgstr "Palauta"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8
msgid "Change Layout.."
msgstr "Muuta asettelu.."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10
msgid "Change Layout"
msgstr "Muuta asettelu"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27
msgid "&nbsp;"
msgstr "&nbsp;"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28
msgid "Create"
msgstr "Luo"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39
msgid "Choose dashboard layout"
msgstr "Valitse työpöydän asetttelu"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:62
msgid "progress:"
msgstr "edistyminen:"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:67
msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
"Klikkaa allalistattuja toimintoja käynnistääksesi ne ja määritelläksesi "
"järjestelmäsi"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
msgid "Welcome to OpenERP"
msgstr "Tervetuloa OpenERP järjestelmään"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr "Muista luoda kirjainmerkki"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr "Tämä osoite"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr "Käyttäjätunnuksesi:"

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-13 23:18+0000\n"
"Last-Translator: Amós Oviedo <Unknown>\n"
"PO-Revision-Date: 2012-02-16 09:13+0000\n"
"Last-Translator: Vicente <jviares@gmail.com>\n"
"Language-Team: Galician <gl@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -36,12 +36,12 @@ msgstr "Sen categorizar"
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
msgstr "Executar Tarefa \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
msgstr "Marcar esta tarefa como feita"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-08 08:52+0000\n"
"Last-Translator: Marijan Rajic <Unknown>\n"
"PO-Revision-Date: 2012-02-22 10:13+0000\n"
"Last-Translator: Goran Kliska <gkliska@gmail.com>\n"
"Language-Team: Croatian <hr@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-23 04:56+0000\n"
"X-Generator: Launchpad (build 14855)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -36,12 +36,12 @@ msgstr "Nekategorizirano"
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
msgstr "Izvrši zadatak \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
msgstr "Označi ovaj zadatak kao obavljen"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4

View File

@ -8,45 +8,45 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2011-10-07 09:00+0000\n"
"PO-Revision-Date: 2012-02-16 21:54+0000\n"
"Last-Translator: Davide Corio - agilebg.com <davide.corio@agilebg.com>\n"
"Language-Team: Italian <it@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
msgid "Edit Layout"
msgstr ""
msgstr "Modifica Layour"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:109
msgid "Are you sure you want to remove this item ?"
msgstr ""
msgstr "Sicuro di voler cancellare questo elemento?"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:316
msgid "Uncategorized"
msgstr ""
msgstr "Non categorizzato"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
msgstr "Esegui task \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
msgstr "Marca questa attività come completata"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
msgstr ""
msgstr "Reimposta Layout.."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6
@ -56,22 +56,22 @@ msgstr "Ripristina"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8
msgid "Change Layout.."
msgstr ""
msgstr "Cambia Layout.."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10
msgid "Change Layout"
msgstr ""
msgstr "Cambia Layout"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27
msgid "&nbsp;"
msgstr ""
msgstr "&nbsp;"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28
msgid "Create"
msgstr ""
msgstr "Crea"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39
@ -89,23 +89,25 @@ msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
"Clicca sulla lista di funzionalità seguenti per eseguirle e configurare il "
"sistema"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
msgid "Welcome to OpenERP"
msgstr ""
msgstr "Benvenuto su OpenERP"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr ""
msgstr "Salva nei bookmark"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr ""
msgstr "Questo url"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr ""
msgstr "Il tuo login:"

View File

@ -0,0 +1,111 @@
# Japanese translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-03-06 06:40+0000\n"
"Last-Translator: Masaki Yamaya <Unknown>\n"
"Language-Team: Japanese <ja@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-03-07 05:38+0000\n"
"X-Generator: Launchpad (build 14907)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
msgid "Edit Layout"
msgstr "レイアウトを編集"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:109
msgid "Are you sure you want to remove this item ?"
msgstr "この項目を取り除きますか?"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:316
msgid "Uncategorized"
msgstr "未分類"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr "タスク \"%s\" を実行"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr "このタスクを完了"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
msgstr "レイアウトをリセット"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6
msgid "Reset"
msgstr "リセット"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8
msgid "Change Layout.."
msgstr "レイアウトを変更…"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10
msgid "Change Layout"
msgstr "レイアウトを変更"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27
msgid "&nbsp;"
msgstr "&nbsp;"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28
msgid "Create"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39
msgid "Choose dashboard layout"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:62
msgid "progress:"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:67
msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
msgid "Welcome to OpenERP"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr ""

View File

@ -0,0 +1,112 @@
# Georgian translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-03-15 18:30+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Georgian <ka@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-03-16 05:14+0000\n"
"X-Generator: Launchpad (build 14951)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
msgid "Edit Layout"
msgstr "განლაგების შეცვლა"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:109
msgid "Are you sure you want to remove this item ?"
msgstr "დარწმუნებული ხართ რომ გსურთ ამ კომპონენტის წაშლა?"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:316
msgid "Uncategorized"
msgstr "კატეგორიის გარეშე"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr "შეასრულე ამოცანა \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr "მონიშნე ეს ამოცანა როგორც დასრულებული"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
msgstr "საწყისი განლაგება"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6
msgid "Reset"
msgstr "ხელახალი კონფიგურაცია"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8
msgid "Change Layout.."
msgstr "განლაგების შეცვლა.."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10
msgid "Change Layout"
msgstr "განლაგების შეცვლა"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27
msgid "&nbsp;"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28
msgid "Create"
msgstr "შექმნა"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39
msgid "Choose dashboard layout"
msgstr "აირჩიეთ საინფორმაციო დაფის განლაგება"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:62
msgid "progress:"
msgstr "პროგრესი:"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:67
msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
"დააწკაპუნეთ ქვემოთ ჩამოთვლილ ფუნქციონალზე რათა გაააქტიუროთ და დააკონფიგურიროთ"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
msgid "Welcome to OpenERP"
msgstr "მოგესალმებით!"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr "ჩაინიშნე ფავორიტებში"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr "ეს URL"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr "თქვენი მოხმარებელი:"

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-15 14:38+0000\n"
"PO-Revision-Date: 2012-03-19 08:25+0000\n"
"Last-Translator: Erwin <Unknown>\n"
"Language-Team: Dutch <nl@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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-03-20 05:13+0000\n"
"X-Generator: Launchpad (build 14969)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -100,14 +100,14 @@ msgstr "Welkom bij OpenERP"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr "Bookmark maken"
msgstr "Vergeet niet een bladwijzer aan te maken van"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr "Deze URL"
msgstr "Deze pagina"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr "U login:"
msgstr "Uw login:"

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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63

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-02-16 05:21+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63

View File

@ -0,0 +1,111 @@
# Romanian translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-03-10 13:19+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Romanian <ro@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-03-11 04:56+0000\n"
"X-Generator: Launchpad (build 14914)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
msgid "Edit Layout"
msgstr "Editare aspect"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:109
msgid "Are you sure you want to remove this item ?"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:316
msgid "Uncategorized"
msgstr "Fără categorie"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr "Marchează acestă sarcină ca realizată"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4
msgid "Reset Layout.."
msgstr "Restează aspect ..."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6
msgid "Reset"
msgstr "Resetare"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8
msgid "Change Layout.."
msgstr "Schimbare aspect..."
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10
msgid "Change Layout"
msgstr "Modificare aspect"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27
msgid "&nbsp;"
msgstr "&nbsp;"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28
msgid "Create"
msgstr "Crează"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39
msgid "Choose dashboard layout"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:62
msgid "progress:"
msgstr "progres:"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:67
msgid ""
"Click on the functionalites listed below to launch them and configure your "
"system"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:110
msgid "Welcome to OpenERP"
msgstr "Bun venit in OpenERP"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:118
msgid "Remember to bookmark"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:119
msgid "This url"
msgstr ""
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:121
msgid "Your login:"
msgstr ""

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-08 07:12+0000\n"
"PO-Revision-Date: 2012-02-17 07:37+0000\n"
"Last-Translator: Aleksei Motsik <Unknown>\n"
"Language-Team: Russian <ru@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-18 05:18+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -36,12 +36,12 @@ msgstr "Без категории"
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
msgstr "Выполнить задачу \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
msgstr "Отметить завершенной"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-14 15:27+0100\n"
"PO-Revision-Date: 2012-02-09 20:10+0000\n"
"PO-Revision-Date: 2012-02-24 11:28+0000\n"
"Last-Translator: Ahmet Altınışık <Unknown>\n"
"Language-Team: Turkish <tr@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-02-15 05:43+0000\n"
"X-Generator: Launchpad (build 14781)\n"
"X-Launchpad-Export-Date: 2012-02-25 05:30+0000\n"
"X-Generator: Launchpad (build 14860)\n"
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:63
@ -36,12 +36,12 @@ msgstr "Sınıflandırılmamış"
#: addons/web_dashboard/static/src/js/dashboard.js:324
#, python-format
msgid "Execute task \"%s\""
msgstr ""
msgstr "Görevi Çalıştır \"%s\""
#. openerp-web
#: addons/web_dashboard/static/src/js/dashboard.js:325
msgid "Mark this task as done"
msgstr ""
msgstr "Bu görevi yapıldı olarak işaretle"
#. openerp-web
#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4

View File

@ -10,7 +10,7 @@ if (!openerp.web_dashboard) {
openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
init: function(view, node) {
this._super(view, node);
this.template = 'DashBoard';
this.form_template = 'DashBoard';
this.actions_attrs = {};
this.action_managers = [];
},
@ -31,18 +31,16 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
this.$element.delegate('.oe-dashboard-column .oe-dashboard-fold', 'click', this.on_fold_action);
this.$element.delegate('.oe-dashboard-column .ui-icon-closethick', 'click', this.on_close_action);
this.actions_attrs = {};
// Init actions
_.each(this.node.children, function(column, column_index) {
_.each(column.children, function(action, action_index) {
delete(action.attrs.width);
delete(action.attrs.height);
delete(action.attrs.colspan);
self.actions_attrs[action.attrs.name] = action.attrs;
self.rpc('/web/action/load', {
action_id: parseInt(action.attrs.name, 10)
}, function(result) {
self.on_load_action(result, column_index + '_' + action_index);
self.on_load_action(result, column_index + '_' + action_index, action.attrs);
});
});
});
@ -58,7 +56,7 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
var qdict = {
current_layout : this.$element.find('.oe-dashboard').attr('data-layout')
};
var $dialog = $('<div>').dialog({
var $dialog = openerp.web.dialog($('<div>'), {
modal: true,
title: _t("Edit Layout"),
width: 'auto',
@ -97,9 +95,9 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
$action = $e.parents('.oe-dashboard-action:first'),
id = parseInt($action.attr('data-id'), 10);
if ($e.is('.ui-icon-minusthick')) {
this.actions_attrs[id].fold = '1';
$action.data('action_attrs').fold = '1';
} else {
delete(this.actions_attrs[id].fold);
delete($action.data('action_attrs').fold);
}
$e.toggleClass('ui-icon-minusthick ui-icon-plusthick');
$action.find('.oe-dashboard-action-content').toggle();
@ -122,7 +120,7 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
var actions = [];
$(this).find('.oe-dashboard-action').each(function() {
var action_id = $(this).attr('data-id'),
new_attrs = _.clone(self.actions_attrs[action_id]);
new_attrs = _.clone($(this).data('action_attrs'));
if (new_attrs.domain) {
new_attrs.domain = new_attrs.domain_string;
delete(new_attrs.domain_string);
@ -143,19 +141,25 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
self.$element.find('.oe-dashboard-link-reset').show();
});
},
on_load_action: function(result, index) {
on_load_action: function(result, index, action_attrs) {
var self = this,
action = result.result,
action_attrs = this.actions_attrs[action.id],
view_mode = action_attrs.view_mode;
if (action_attrs.context) {
action.context = _.extend((action.context || {}), action_attrs.context);
}
if (action_attrs.domain) {
action.domain = action.domain || [];
action.domain.unshift.apply(action.domain, action_attrs.domain);
if (action_attrs.context && action_attrs.context['dashboard_merge_domains_contexts'] === false) {
// TODO: replace this 6.1 workaround by attribute on <action/>
action.context = action_attrs.context || {};
action.domain = action_attrs.domain || [];
} else {
if (action_attrs.context) {
action.context = _.extend((action.context || {}), action_attrs.context);
}
if (action_attrs.domain) {
action.domain = action.domain || [];
action.domain.unshift.apply(action.domain, action_attrs.domain);
}
}
var action_orig = _.extend({ flags : {} }, action);
if (view_mode && view_mode != action.view_mode) {
@ -187,6 +191,7 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
var am = new openerp.web.ActionManager(this),
// FIXME: ideally the dashboard view shall be refactored like kanban.
$action = $('#' + this.view.element_id + '_action_' + index);
$action.parent().data('action_attrs', action_attrs);
this.action_managers.push(am);
am.appendTo($action);
am.do_action(action);
@ -225,7 +230,7 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
});
}
},
render: function() {
renderElement: function() {
// We should start with three columns available
for (var i = this.node.children.length; i < 3; i++) {
this.node.children.push({
@ -234,17 +239,18 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({
children: []
});
}
return QWeb.render(this.template, this);
var rendered = QWeb.render(this.form_template, this);
this.$element.html(rendered);
},
do_reload: function() {
var view_manager = this.view.widget_parent,
action_manager = view_manager.widget_parent;
this.view.stop();
var view_manager = this.view.getParent(),
action_manager = view_manager.getParent();
this.view.destroy();
action_manager.do_action(view_manager.action);
}
});
openerp.web.form.DashBoardLegacy = openerp.web.form.DashBoard.extend({
render: function() {
renderElement: function() {
if (this.node.tag == 'hpaned') {
this.node.attrs.style = '2-1';
} else if (this.node.tag == 'vpaned') {
@ -263,7 +269,7 @@ openerp.web.form.DashBoardLegacy = openerp.web.form.DashBoard.extend({
}
}
});
return this._super(this, arguments);
this._super(this);
}
});
@ -340,7 +346,7 @@ openerp.web_dashboard.ConfigOverview = openerp.web.View.extend({
});
})
.delegate('li:not(.oe-done)', 'click', function () {
self.widget_parent.widget_parent.widget_parent.do_execute_action({
self.getParent().getParent().getParent().do_execute_action({
type: 'object',
name: 'action_launch'
}, self.dataset,
@ -405,7 +411,6 @@ openerp.web_dashboard.ApplicationTiles = openerp.web.OldWidget.extend({
},
start: function() {
var self = this;
openerp.webclient.menu.do_hide_secondary();
var domain = [['application','=',true], ['state','=','installed'], ['name', '!=', 'base']];
var ds = new openerp.web.DataSetSearch(this, 'ir.module.module',{},domain);
ds.read_slice(['id']).then(function(result) {
@ -462,8 +467,8 @@ openerp.web_dashboard.ApplicationInstaller = openerp.web.OldWidget.extend({
});
return r;
},
stop: function() {
this.action_manager.stop();
destroy: function() {
this.action_manager.destroy();
return this._super();
}
});

View File

@ -5,11 +5,11 @@
"version" : "2.0",
"depends" : ["web"],
"js": [
'static/lib/js/raphael-min.js',
'static/lib/js/dracula_graffle.js',
'static/lib/js/dracula_graph.js',
'static/lib/js/dracula_algorithms.js',
'static/src/js/diagram.js'
'static/lib/js/raphael.js',
'static/lib/js/jquery.mousewheel.js',
'static/src/js/vec2.js',
'static/src/js/graph.js',
'static/src/js/diagram.js',
],
'css' : [
"static/src/css/base_diagram.css",
@ -17,5 +17,5 @@
'qweb' : [
"static/src/xml/*.xml",
],
'active': True,
'auto_install': True,
}

View File

@ -115,8 +115,8 @@ class DiagramView(View):
for i, fld in enumerate(visible_node_fields):
n['options'][node_fields_string[i]] = act[fld]
id_model = req.session.model(model).read([id],['name'], req.session.context)[0]['name']
_id, name = req.session.model(model).name_get([id], req.session.context)[0]
return dict(nodes=nodes,
conn=connectors,
id_model=id_model,
name=name,
parent_field=graphs['node_parent_field'])

View File

@ -8,15 +8,16 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-02-08 02:25-0600\n"
"Last-Translator: Carlos Vásquez - CLEARCORP <carlos.vasquez@clearcorp.co.cr>\n"
"PO-Revision-Date: 2012-02-16 21:35+0000\n"
"Last-Translator: Carlos Vásquez (CLEARCORP) "
"<carlos.vasquez@clearcorp.co.cr>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-02-07 04:59+0000\n"
"X-Generator: Launchpad (build 14747)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
"Language: es\n"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:11
@ -41,10 +42,9 @@ msgstr "Crear:"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:231
msgid "Open: "
msgstr "Abrir:"
msgstr "Abrir: "
#. openerp-web
#: addons/web_diagram/static/src/xml/base_diagram.xml:5
msgid "New Node"
msgstr "Nuevo Nodo"

View File

@ -0,0 +1,57 @@
# Finnish translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2012-03-19 11:59+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Finnish <fi@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-03-20 05:13+0000\n"
"X-Generator: Launchpad (build 14969)\n"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:11
msgid "Diagram"
msgstr "Kaavio"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:208
#: addons/web_diagram/static/src/js/diagram.js:224
#: addons/web_diagram/static/src/js/diagram.js:257
msgid "Activity"
msgstr "Aktiviteetti"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:208
#: addons/web_diagram/static/src/js/diagram.js:289
#: addons/web_diagram/static/src/js/diagram.js:308
msgid "Transition"
msgstr "Siirtyminen"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:214
#: addons/web_diagram/static/src/js/diagram.js:262
#: addons/web_diagram/static/src/js/diagram.js:314
msgid "Create:"
msgstr "Luo:"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:231
#: addons/web_diagram/static/src/js/diagram.js:232
#: addons/web_diagram/static/src/js/diagram.js:296
msgid "Open: "
msgstr "Avaa: "
#. openerp-web
#: addons/web_diagram/static/src/xml/base_diagram.xml:5
#: addons/web_diagram/static/src/xml/base_diagram.xml:6
msgid "New Node"
msgstr "Uusi Noodi"

View File

@ -8,39 +8,39 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-06 17:33+0100\n"
"PO-Revision-Date: 2011-10-08 13:41+0000\n"
"Last-Translator: Nicola Riolini - Micronaet <Unknown>\n"
"PO-Revision-Date: 2012-02-16 21:55+0000\n"
"Last-Translator: Davide Corio - agilebg.com <davide.corio@agilebg.com>\n"
"Language-Team: Italian <it@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-02-07 04:59+0000\n"
"X-Generator: Launchpad (build 14747)\n"
"X-Launchpad-Export-Date: 2012-02-17 05:13+0000\n"
"X-Generator: Launchpad (build 14814)\n"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:11
msgid "Diagram"
msgstr ""
msgstr "Diagramma"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:208
msgid "Activity"
msgstr ""
msgstr "Attività"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:208
msgid "Transition"
msgstr ""
msgstr "Transizione"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:214
msgid "Create:"
msgstr ""
msgstr "Crea:"
#. openerp-web
#: addons/web_diagram/static/src/js/diagram.js:231
msgid "Open: "
msgstr ""
msgstr "Apri: "
#. openerp-web
#: addons/web_diagram/static/src/xml/base_diagram.xml:5

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