summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortsabi <csaba.toth@i3rendszerhaz.hu>2012-11-29 12:41:32 +0100
committertsabi <csaba.toth@i3rendszerhaz.hu>2012-11-29 12:41:32 +0100
commit76ca47d4fd0bd178fd7dfd2ea5b534d4e878a6f5 (patch)
tree32339ef0c6869f21bb757f8ab6bce7faf8028c6b
parente2435d4f004a20636aef8a8ee2cc03c8cfc3e78c (diff)
parentc7772fdd82105ddf60cbec2517753bf927ce19ed (diff)
[MERGE] trunk
bzr revid: csaba.toth@i3rendszerhaz.hu-20121129114132-gfe3sn1p5t0mzofm
-rw-r--r--addons/web/__openerp__.py1
-rw-r--r--addons/web/controllers/main.py396
-rw-r--r--addons/web/controllers/testing.py6
-rw-r--r--addons/web/doc/rpc.rst13
-rw-r--r--addons/web/http.py25
-rw-r--r--addons/web/i18n/es_MX.po2067
-rw-r--r--addons/web/i18n/fa.po4
-rw-r--r--addons/web/i18n/fr.po17
-rw-r--r--addons/web/i18n/hu.po140
-rw-r--r--addons/web/i18n/it.po30
-rw-r--r--addons/web/i18n/zh_CN.po86
-rw-r--r--addons/web/nonliterals.py267
-rw-r--r--addons/web/session.py49
-rw-r--r--addons/web/static/lib/py.js/.hg_archival.txt6
-rw-r--r--addons/web/static/lib/py.js/README.rst35
-rw-r--r--addons/web/static/lib/py.js/doc/Makefile153
-rw-r--r--addons/web/static/lib/py.js/doc/builtins.rst55
-rw-r--r--addons/web/static/lib/py.js/doc/conf.py247
-rw-r--r--addons/web/static/lib/py.js/doc/differences.rst64
-rw-r--r--addons/web/static/lib/py.js/doc/index.rst161
-rw-r--r--addons/web/static/lib/py.js/doc/make.bat190
-rw-r--r--addons/web/static/lib/py.js/doc/types.rst248
-rw-r--r--addons/web/static/lib/py.js/doc/utility.rst226
-rw-r--r--addons/web/static/lib/py.js/lib/py.js683
-rw-r--r--addons/web/static/lib/py.js/test/parser.js132
-rw-r--r--addons/web/static/lib/py.js/test/test.js388
-rw-r--r--addons/web/static/src/css/base.css12
-rw-r--r--addons/web/static/src/css/base.sass12
-rw-r--r--addons/web/static/src/js/boot.js2
-rw-r--r--addons/web/static/src/js/chrome.js7
-rw-r--r--addons/web/static/src/js/corelib.js299
-rw-r--r--addons/web/static/src/js/data.js19
-rw-r--r--addons/web/static/src/js/pyeval.js774
-rw-r--r--addons/web/static/src/js/search.js51
-rw-r--r--addons/web/static/src/js/testing.js6
-rw-r--r--addons/web/static/src/js/view_form.js40
-rw-r--r--addons/web/static/src/js/view_tree.js10
-rw-r--r--addons/web/static/src/js/views.js103
-rw-r--r--addons/web/static/src/xml/base.xml4
-rw-r--r--addons/web/static/test/evals.js651
-rw-r--r--addons/web/static/test/search.js26
-rw-r--r--addons/web/static/test/test.html.THIS63
-rw-r--r--addons/web/tests/__init__.py3
-rw-r--r--addons/web/tests/test_dataset.py4
-rw-r--r--addons/web/tests/test_menu.py7
-rw-r--r--addons/web/tests/test_view.py128
-rw-r--r--addons/web_calendar/i18n/es_EC.po4
-rw-r--r--addons/web_calendar/i18n/es_MX.po203
-rw-r--r--addons/web_calendar/i18n/fa.po4
-rw-r--r--addons/web_diagram/i18n/es_MX.po104
-rw-r--r--addons/web_diagram/i18n/fa.po4
-rw-r--r--addons/web_gantt/i18n/es_MX.po32
-rw-r--r--addons/web_gantt/i18n/fa.po4
-rw-r--r--addons/web_gantt/static/src/js/gantt.js9
-rw-r--r--addons/web_graph/i18n/es_EC.po4
-rw-r--r--addons/web_graph/i18n/es_MX.po137
-rw-r--r--addons/web_graph/i18n/fa.po4
-rw-r--r--addons/web_kanban/i18n/es_MX.po146
-rw-r--r--addons/web_kanban/i18n/fa.po4
-rw-r--r--addons/web_kanban/i18n/zh_CN.po48
-rw-r--r--addons/web_view_editor/i18n/es_MX.po149
-rw-r--r--addons/web_view_editor/i18n/fa.po4
-rw-r--r--addons/web_view_editor/i18n/zh_CN.po149
63 files changed, 6789 insertions, 2130 deletions
diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py
index 745df64a913..2303b6a68e8 100644
--- a/addons/web/__openerp__.py
+++ b/addons/web/__openerp__.py
@@ -42,6 +42,7 @@ This module provides the core of the OpenERP Web Client.
"static/lib/py.js/lib/py.js",
"static/src/js/boot.js",
"static/src/js/testing.js",
+ "static/src/js/pyeval.js",
"static/src/js/corelib.js",
"static/src/js/coresetup.js",
"static/src/js/dates.js",
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index def93c16ea4..3f35b3f44ea 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -31,7 +31,6 @@ import openerp
from openerp.tools.translate import _
from .. import http
-from .. import nonliterals
openerpweb = http
#----------------------------------------------------------
@@ -362,40 +361,15 @@ def set_cookie_and_redirect(req, redirect_url):
redirect.set_cookie('instance0|session_id', cookie_val)
return redirect
-def eval_context_and_domain(session, context, domain=None):
- e_context = session.eval_context(context)
- # should we give the evaluated context as an evaluation context to the domain?
- e_domain = session.eval_domain(domain or [])
-
- return e_context, e_domain
-
def load_actions_from_ir_values(req, key, key2, models, meta):
- context = req.session.eval_context(req.context)
Values = req.session.model('ir.values')
- actions = Values.get(key, key2, models, meta, context)
+ actions = Values.get(key, key2, models, meta, req.context)
- return [(id, name, clean_action(req, action, context))
+ return [(id, name, clean_action(req, action))
for id, name, action in actions]
-def clean_action(req, action, context, do_not_eval=False):
+def clean_action(req, action):
action.setdefault('flags', {})
-
- context = context or {}
- eval_ctx = req.session.evaluation_context(context)
-
- if not do_not_eval:
- # values come from the server, we can just eval them
- if action.get('context') and isinstance(action.get('context'), basestring):
- action['context'] = eval( action['context'], eval_ctx ) or {}
-
- if action.get('domain') and isinstance(action.get('domain'), basestring):
- action['domain'] = eval( action['domain'], eval_ctx ) or []
- else:
- if 'context' in action:
- action['context'] = parse_context(action['context'], req.session)
- if 'domain' in action:
- action['domain'] = parse_domain(action['domain'], req.session)
-
action_type = action.setdefault('type', 'ir.actions.act_window_close')
if action_type == 'ir.actions.act_window':
return fix_view_modes(action)
@@ -474,39 +448,6 @@ def fix_view_modes(action):
return action
-def parse_domain(domain, session):
- """ Parses an arbitrary string containing a domain, transforms it
- to either a literal domain or a :class:`nonliterals.Domain`
-
- :param domain: the domain to parse, if the domain is not a string it
- is assumed to be a literal domain and is returned as-is
- :param session: Current OpenERP session
- :type session: openerpweb.OpenERPSession
- """
- if not isinstance(domain, basestring):
- return domain
- try:
- return ast.literal_eval(domain)
- except ValueError:
- # not a literal
- return nonliterals.Domain(session, domain)
-
-def parse_context(context, session):
- """ Parses an arbitrary string containing a context, transforms it
- to either a literal context or a :class:`nonliterals.Context`
-
- :param context: the context to parse, if the context is not a string it
- is assumed to be a literal domain and is returned as-is
- :param session: Current OpenERP session
- :type session: openerpweb.OpenERPSession
- """
- if not isinstance(context, basestring):
- return context
- try:
- return ast.literal_eval(context)
- except ValueError:
- return nonliterals.Context(session, context)
-
def _local_web_translations(trans_file):
messages = []
try:
@@ -912,7 +853,7 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest
def sc_list(self, req):
return req.session.model('ir.ui.view_sc').get_sc(
- req.session._uid, "ir.ui.menu", req.session.eval_context(req.context))
+ req.session._uid, "ir.ui.menu", req.context)
@openerpweb.jsonrequest
def get_lang_list(self, req):
@@ -927,59 +868,6 @@ class Session(openerpweb.Controller):
return module_installed(req)
@openerpweb.jsonrequest
- def eval_domain_and_context(self, req, contexts, domains,
- group_by_seq=None):
- """ Evaluates sequences of domains and contexts, composing them into
- a single context, domain or group_by sequence.
-
- :param list contexts: list of contexts to merge together. Contexts are
- evaluated in sequence, all previous contexts
- are part of their own evaluation context
- (starting at the session context).
- :param list domains: list of domains to merge together. Domains are
- evaluated in sequence and appended to one another
- (implicit AND), their evaluation domain is the
- result of merging all contexts.
- :param list group_by_seq: list of domains (which may be in a different
- order than the ``contexts`` parameter),
- evaluated in sequence, their ``'group_by'``
- key is extracted if they have one.
- :returns:
- a 3-dict of:
-
- context (``dict``)
- the global context created by merging all of
- ``contexts``
-
- domain (``list``)
- the concatenation of all domains
-
- group_by (``list``)
- a list of fields to group by, potentially empty (in which case
- no group by should be performed)
- """
- context, domain = eval_context_and_domain(req.session,
- nonliterals.CompoundContext(*(contexts or [])),
- nonliterals.CompoundDomain(*(domains or [])))
-
- group_by_sequence = []
- for candidate in (group_by_seq or []):
- ctx = req.session.eval_context(candidate, context)
- group_by = ctx.get('group_by')
- if not group_by:
- continue
- elif isinstance(group_by, basestring):
- group_by_sequence.append(group_by)
- else:
- group_by_sequence.extend(group_by)
-
- return {
- 'context': context,
- 'domain': domain,
- 'group_by': group_by_sequence
- }
-
- @openerpweb.jsonrequest
def save_session_action(self, req, the_action):
"""
This method store an action object in the session object and returns an integer
@@ -1048,18 +936,19 @@ class Menu(openerpweb.Controller):
:rtype: list(int)
"""
s = req.session
- context = s.eval_context(req.context)
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']
+ user_menu_id = s.model('res.users').read([s._uid], ['menu_id'],
+ req.context)[0]['menu_id']
menu_domain = [('parent_id', '=', False)]
if user_menu_id:
- domain_string = s.model('ir.actions.act_window').read([user_menu_id[0]], ['domain'], context)[0]['domain']
+ domain_string = s.model('ir.actions.act_window').read(
+ [user_menu_id[0]], ['domain'],req.context)[0]['domain']
if domain_string:
menu_domain = ast.literal_eval(domain_string)
- return Menus.search(menu_domain, 0, False, False, context)
+ return Menus.search(menu_domain, 0, False, False, req.context)
def do_load(self, req):
""" Loads all menu items (all applications and their sub-menus).
@@ -1069,23 +958,30 @@ class Menu(openerpweb.Controller):
:return: the menu root
:rtype: dict('children': menu_nodes)
"""
- 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', 'action', 'needaction_enabled', 'needaction_counter'], context)
- menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, ''], 'children' : menu_roots}
+ fields = ['name', 'sequence', 'parent_id', 'action',
+ 'needaction_enabled', 'needaction_counter']
+ menu_roots = Menus.read(self.do_get_user_roots(req), fields, req.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', 'action', 'needaction_enabled', 'needaction_counter'], context)
+ menu_ids = Menus.search([], 0, False, False, req.context)
+ menu_items = Menus.read(menu_ids, fields, req.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.
menu_items.extend(menu_roots)
# make a tree using parent_id
- menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
+ menu_items_map = dict(
+ (menu_item["id"], menu_item) for menu_item in menu_items)
for menu_item in menu_items:
if menu_item['parent_id']:
parent = menu_item['parent_id'][0]
@@ -1135,12 +1031,10 @@ class DataSet(openerpweb.Controller):
"""
Model = req.session.model(model)
- context, domain = eval_context_and_domain(
- req.session, req.context, domain)
-
- ids = Model.search(domain, offset or 0, limit or False, sort or False, context)
+ ids = Model.search(domain, offset or 0, limit or False, sort or False,
+ req.context)
if limit and len(ids) == limit:
- length = Model.search_count(domain, context)
+ length = Model.search_count(domain, req.context)
else:
length = len(ids) + (offset or 0)
if fields and fields == ['id']:
@@ -1150,7 +1044,7 @@ class DataSet(openerpweb.Controller):
'records': [{'id': id} for id in ids]
}
- records = Model.read(ids, fields or False, context)
+ records = Model.read(ids, fields or False, req.context)
records.sort(key=lambda obj: ids.index(obj['id']))
return {
'length': length,
@@ -1161,37 +1055,15 @@ class DataSet(openerpweb.Controller):
def load(self, req, model, id, fields):
m = req.session.model(model)
value = {}
- r = m.read([id], False, req.session.eval_context(req.context))
+ r = m.read([id], False, req.context)
if r:
value = r[0]
return {'value': value}
def call_common(self, req, model, method, args, domain_id=None, context_id=None):
- has_domain = domain_id is not None and domain_id < len(args)
- has_context = context_id is not None and context_id < len(args)
-
- domain = args[domain_id] if has_domain else []
- context = args[context_id] if has_context else {}
- c, d = eval_context_and_domain(req.session, context, domain)
- if has_domain:
- args[domain_id] = d
- if has_context:
- args[context_id] = c
-
return self._call_kw(req, model, method, args, {})
-
- def _call_kw(self, req, model, method, args, kwargs):
- for i in xrange(len(args)):
- if isinstance(args[i], nonliterals.BaseContext):
- args[i] = req.session.eval_context(args[i])
- elif isinstance(args[i], nonliterals.BaseDomain):
- args[i] = req.session.eval_domain(args[i])
- for k in kwargs.keys():
- if isinstance(kwargs[k], nonliterals.BaseContext):
- kwargs[k] = req.session.eval_context(kwargs[k])
- elif isinstance(kwargs[k], nonliterals.BaseDomain):
- kwargs[k] = req.session.eval_domain(kwargs[k])
+ def _call_kw(self, req, model, method, args, kwargs):
# Temporary implements future display_name special field for model#read()
if method == 'read' and kwargs.get('context') and kwargs['context'].get('future_display_name'):
if 'display_name' in args[1]:
@@ -1205,38 +1077,8 @@ class DataSet(openerpweb.Controller):
return getattr(req.session.model(model), method)(*args, **kwargs)
@openerpweb.jsonrequest
- def onchange(self, req, model, method, args, context_id=None):
- """ Support method for handling onchange calls: behaves much like call
- with the following differences:
-
- * Does not take a domain_id
- * Is aware of the return value's structure, and will parse the domains
- if needed in order to return either parsed literal domains (in JSON)
- or non-literal domain instances, allowing those domains to be used
- from JS
-
- :param req:
- :type req: web.common.http.JsonRequest
- :param str model: object type on which to call the method
- :param str method: name of the onchange handler method
- :param list args: arguments to call the onchange handler with
- :param int context_id: index of the context object in the list of
- arguments
- :return: result of the onchange call with all domains parsed
- """
- result = self.call_common(req, model, method, args, context_id=context_id)
- if not result or 'domain' not in result:
- return result
-
- result['domain'] = dict(
- (k, parse_domain(v, req.session))
- for k, v in result['domain'].iteritems())
-
- return result
-
- @openerpweb.jsonrequest
def call(self, req, model, method, args, domain_id=None, context_id=None):
- return self.call_common(req, model, method, args, domain_id, context_id)
+ return self._call_kw(req, model, method, args, {})
@openerpweb.jsonrequest
def call_kw(self, req, model, method, args, kwargs):
@@ -1244,10 +1086,9 @@ class DataSet(openerpweb.Controller):
@openerpweb.jsonrequest
def call_button(self, req, model, method, args, domain_id=None, context_id=None):
- context = req.session.eval_context(req.context)
- action = self.call_common(req, model, method, args, domain_id, context_id)
+ action = self._call_kw(req, model, method, args, {})
if isinstance(action, dict) and action.get('type') != '':
- return clean_action(req, action, context)
+ return clean_action(req, action)
return False
@openerpweb.jsonrequest
@@ -1283,12 +1124,9 @@ class View(openerpweb.Controller):
def fields_view_get(self, req, model, view_id, view_type,
transform=True, toolbar=False, submenu=False):
Model = req.session.model(model)
- context = req.session.eval_context(req.context)
- fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
+ fvg = Model.fields_view_get(view_id, view_type, req.context, toolbar, submenu)
# todo fme?: check that we should pass the evaluated context here
- self.process_view(req.session, fvg, context, transform, (view_type == 'kanban'))
- if toolbar and transform:
- self.process_toolbar(req, fvg['toolbar'])
+ self.process_view(req.session, fvg, req.context, transform, (view_type == 'kanban'))
return fvg
def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
@@ -1305,12 +1143,8 @@ class View(openerpweb.Controller):
arch = fvg['arch']
fvg['arch_string'] = arch
- if transform:
- evaluation_context = session.evaluation_context(context or {})
- xml = self.transform_view(arch, session, evaluation_context)
- else:
- xml = ElementTree.fromstring(arch)
- fvg['arch'] = xml2json_from_elementtree(xml, preserve_whitespaces)
+ fvg['arch'] = xml2json_from_elementtree(
+ ElementTree.fromstring(arch), preserve_whitespaces)
if 'id' in fvg['fields']:
# Special case for id's
@@ -1319,29 +1153,8 @@ class View(openerpweb.Controller):
id_field['type'] = 'id'
for field in fvg['fields'].itervalues():
- if field.get('views'):
- for view in field["views"].itervalues():
- self.process_view(session, view, None, transform)
- if field.get('domain'):
- field["domain"] = parse_domain(field["domain"], session)
- if field.get('context'):
- field["context"] = parse_context(field["context"], session)
-
- def process_toolbar(self, req, toolbar):
- """
- The toolbar is a mapping of section_key: [action_descriptor]
-
- We need to clean all those actions in order to ensure correct
- round-tripping
- """
- for actions in toolbar.itervalues():
- for action in actions:
- if 'context' in action:
- action['context'] = parse_context(
- action['context'], req.session)
- if 'domain' in action:
- action['domain'] = parse_domain(
- action['domain'], req.session)
+ for view in field.get("views", {}).itervalues():
+ self.process_view(session, view, None, transform)
@openerpweb.jsonrequest
def add_custom(self, req, view_id, arch):
@@ -1350,57 +1163,22 @@ class View(openerpweb.Controller):
'user_id': req.session._uid,
'ref_id': view_id,
'arch': arch
- }, req.session.eval_context(req.context))
+ }, req.context)
return {'result': True}
@openerpweb.jsonrequest
def undo_custom(self, req, view_id, reset=False):
CustomView = req.session.model('ir.ui.view.custom')
- context = req.session.eval_context(req.context)
vcustom = CustomView.search([('user_id', '=', req.session._uid), ('ref_id' ,'=', view_id)],
- 0, False, False, context)
+ 0, False, False, req.context)
if vcustom:
if reset:
- CustomView.unlink(vcustom, context)
+ CustomView.unlink(vcustom, req.context)
else:
- CustomView.unlink([vcustom[0]], context)
+ CustomView.unlink([vcustom[0]], req.context)
return {'result': True}
return {'result': False}
- def transform_view(self, view_string, session, context=None):
- # transform nodes on the fly via iterparse, instead of
- # doing it statically on the parsing result
- parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
- root = None
- for event, elem in parser:
- if event == "start":
- if root is None:
- root = elem
- self.parse_domains_and_contexts(elem, session)
- return root
-
- def parse_domains_and_contexts(self, elem, session):
- """ Converts domains and contexts from the view into Python objects,
- either literals if they can be parsed by literal_eval or a special
- placeholder object if the domain or context refers to free variables.
-
- :param elem: the current node being parsed
- :type param: xml.etree.ElementTree.Element
- :param session: OpenERP session object, used to store and retrieve
- non-literal objects
- :type session: openerpweb.openerpweb.OpenERPSession
- """
- for el in ['domain', 'filter_domain']:
- domain = elem.get(el, '').strip()
- if domain:
- elem.set(el, parse_domain(domain, session))
- elem.set(el + '_string', domain)
- for el in ['context', 'default_get']:
- context_string = elem.get(el, '').strip()
- if context_string:
- elem.set(el, parse_context(context_string, session))
- elem.set(el + '_string', context_string)
-
@openerpweb.jsonrequest
def load(self, req, model, view_id, view_type, toolbar=False):
return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
@@ -1414,50 +1192,6 @@ class TreeView(View):
req,'action', 'tree_but_open',[(model, id)],
False)
-class SearchView(View):
- _cp_path = "/web/searchview"
-
- @openerpweb.jsonrequest
- def load(self, req, model, view_id):
- fields_view = self.fields_view_get(req, model, view_id, 'search')
- return {'fields_view': fields_view}
-
- @openerpweb.jsonrequest
- def fields_get(self, req, model):
- Model = req.session.model(model)
- fields = Model.fields_get(False, req.session.eval_context(req.context))
- for field in fields.values():
- # shouldn't convert the views too?
- if field.get('domain'):
- field["domain"] = parse_domain(field["domain"], req.session)
- if field.get('context'):
- field["context"] = parse_context(field["context"], req.session)
- return {'fields': fields}
-
- @openerpweb.jsonrequest
- def get_filters(self, req, model):
- logger = logging.getLogger(__name__ + '.SearchView.get_filters')
- Model = req.session.model("ir.filters")
- filters = Model.get_filters(model)
- for filter in filters:
- try:
- parsed_context = parse_context(filter["context"], req.session)
- filter["context"] = (parsed_context
- if not isinstance(parsed_context, 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, nonliterals.BaseDomain)
- else req.session.eval_domain(parsed_domain))
- except Exception:
- logger.exception(_("Failed to parse custom filter %s in %s"),
- filter['name'], model)
- filter['disabled'] = True
- del filter['context']
- del filter['domain']
- return filters
-
class Binary(openerpweb.Controller):
_cp_path = "/web/binary"
@@ -1465,7 +1199,6 @@ class Binary(openerpweb.Controller):
def image(self, req, model, id, field, **kw):
last_update = '__last_update'
Model = req.session.model(model)
- context = req.session.eval_context(req.context)
headers = [('Content-Type', 'image/png')]
etag = req.httprequest.headers.get('If-None-Match')
hashed_session = hashlib.md5(req.session_id).hexdigest()
@@ -1476,22 +1209,22 @@ class Binary(openerpweb.Controller):
if not id and hashed_session == etag:
return werkzeug.wrappers.Response(status=304)
else:
- date = Model.read([id], [last_update], context)[0].get(last_update)
+ date = Model.read([id], [last_update], req.context)[0].get(last_update)
if hashlib.md5(date).hexdigest() == etag:
return werkzeug.wrappers.Response(status=304)
retag = hashed_session
try:
if not id:
- res = Model.default_get([field], context).get(field)
+ res = Model.default_get([field], req.context).get(field)
image_base64 = res
else:
- res = Model.read([id], [last_update, field], context)[0]
+ res = Model.read([id], [last_update, field], req.context)[0]
retag = hashlib.md5(res.get(last_update)).hexdigest()
image_base64 = res.get(field)
if kw.get('resize'):
- resize = kw.get('resize').split(',');
+ resize = kw.get('resize').split(',')
if len(resize) == 2 and int(resize[0]) and int(resize[1]):
width = int(resize[0])
height = int(resize[1])
@@ -1533,14 +1266,13 @@ class Binary(openerpweb.Controller):
:returns: :class:`werkzeug.wrappers.Response`
"""
Model = req.session.model(model)
- context = req.session.eval_context(req.context)
fields = [field]
if filename_field:
fields.append(filename_field)
if id:
- res = Model.read([int(id)], fields, context)[0]
+ res = Model.read([int(id)], fields, req.context)[0]
else:
- res = Model.default_get(fields, context)
+ res = Model.default_get(fields, req.context)
filecontent = base64.b64decode(res.get(field, ''))
if not filecontent:
return req.not_found()
@@ -1559,9 +1291,8 @@ class Binary(openerpweb.Controller):
field = jdata['field']
id = jdata.get('id', None)
filename_field = jdata.get('filename_field', None)
- context = jdata.get('context', dict())
+ context = jdata.get('context', {})
- context = req.session.eval_context(context)
Model = req.session.model(model)
fields = [field]
if filename_field:
@@ -1600,7 +1331,6 @@ class Binary(openerpweb.Controller):
@openerpweb.httprequest
def upload_attachment(self, req, callback, model, id, ufile):
- context = req.session.eval_context(req.context)
Model = req.session.model('ir.attachment')
try:
out = """<script language="javascript" type="text/javascript">
@@ -1613,7 +1343,7 @@ class Binary(openerpweb.Controller):
'datas_fname': ufile.filename,
'res_model': model,
'res_id': int(id)
- }, context)
+ }, req.context)
args = {
'filename': ufile.filename,
'id': attachment_id
@@ -1626,12 +1356,9 @@ class Action(openerpweb.Controller):
_cp_path = "/web/action"
@openerpweb.jsonrequest
- def load(self, req, action_id, do_not_eval=False, eval_context=None):
+ def load(self, req, action_id, do_not_eval=False):
Actions = req.session.model('ir.actions.actions')
value = False
- context = req.session.eval_context(req.context)
- eval_context = req.session.eval_context(nonliterals.CompoundContext(context, eval_context or {}))
-
try:
action_id = int(action_id)
except ValueError:
@@ -1642,25 +1369,24 @@ class Action(openerpweb.Controller):
except Exception:
action_id = 0 # force failed read
- base_action = Actions.read([action_id], ['type'], context)
+ base_action = Actions.read([action_id], ['type'], req.context)
if base_action:
ctx = {}
action_type = base_action[0]['type']
if action_type == 'ir.actions.report.xml':
ctx.update({'bin_size': True})
- ctx.update(context)
+ ctx.update(req.context)
action = req.session.model(action_type).read([action_id], False, ctx)
if action:
- value = clean_action(req, action[0], eval_context, do_not_eval)
+ value = clean_action(req, action[0])
return value
@openerpweb.jsonrequest
def run(self, req, action_id):
- context = req.session.eval_context(req.context)
return_action = req.session.model('ir.actions.server').run(
- [action_id], req.session.eval_context(req.context))
+ [action_id], req.context)
if return_action:
- return clean_action(req, return_action, context)
+ return clean_action(req, return_action)
else:
return False
@@ -1683,7 +1409,7 @@ class Export(View):
def fields_get(self, req, model):
Model = req.session.model(model)
- fields = Model.fields_get(False, req.session.eval_context(req.context))
+ fields = Model.fields_get(False, req.context)
return fields
@openerpweb.jsonrequest
@@ -1835,12 +1561,11 @@ class Export(View):
'import_compat')(
simplejson.loads(data))
- context = req.session.eval_context(req.context)
Model = req.session.model(model)
- ids = ids or Model.search(domain, 0, False, False, context)
+ ids = ids or Model.search(domain, 0, False, False, req.context)
field_names = map(operator.itemgetter('name'), fields)
- import_data = Model.export_data(ids, field_names, context).get('datas',[])
+ import_data = Model.export_data(ids, field_names, req.context).get('datas',[])
if import_compat:
columns_headers = field_names
@@ -1945,9 +1670,8 @@ class Reports(View):
action = simplejson.loads(action)
report_srv = req.session.proxy("report")
- context = req.session.eval_context(
- nonliterals.CompoundContext(
- req.context or {}, action[ "context"]))
+ context = dict(req.context)
+ context.update(action["context"])
report_data = {}
report_ids = context["active_ids"]
diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py
index 3c7c4899b6c..86940f41583 100644
--- a/addons/web/controllers/testing.py
+++ b/addons/web/controllers/testing.py
@@ -11,7 +11,7 @@ from mako.template import Template
from openerp.modules import module
from .main import module_topological_sort
-from ..http import Controller, httprequest
+from .. import http
NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
<html>
@@ -82,10 +82,10 @@ TESTING = Template(u"""<!DOCTYPE html>
</html>
""")
-class TestRunnerController(Controller):
+class TestRunnerController(http.Controller):
_cp_path = '/web/tests'
- @httprequest
+ @http.httprequest
def index(self, req, mod=None, **kwargs):
ms = module.get_modules()
manifests = dict(
diff --git a/addons/web/doc/rpc.rst b/addons/web/doc/rpc.rst
index db6fcb4fa4b..8c48a2f6eaa 100644
--- a/addons/web/doc/rpc.rst
+++ b/addons/web/doc/rpc.rst
@@ -257,16 +257,17 @@ method you want to call) and a mapping of attributes to values (applied
as keyword arguments on the Python method [#]_). This function fetches
the return value of the Python methods, converted to JSON.
-For instance, to call the ``eval_domain_and_context`` of the
-:class:`~web.controllers.main.Session` controller:
+For instance, to call the ``resequence`` of the
+:class:`~web.controllers.main.DataSet` controller:
.. code-block:: javascript
- openerp.connection.rpc('/web/session/eval_domain_and_context', {
- domains: ds,
- contexts: cs
+ openerp.connection.rpc('/web/dataset/resequence', {
+ model: some_model,
+ ids: array_of_ids,
+ offset: 42
}).then(function (result) {
- // handle result
+ // resequenced on server
});
.. [#] with a small twist: SQLAlchemy's ``orm.query.Query.group_by``
diff --git a/addons/web/http.py b/addons/web/http.py
index fc80cb03864..d0b04428f0d 100644
--- a/addons/web/http.py
+++ b/addons/web/http.py
@@ -17,7 +17,6 @@ import tempfile
import threading
import time
import traceback
-import urllib
import urlparse
import uuid
import xmlrpclib
@@ -33,7 +32,6 @@ import werkzeug.wsgi
import openerp
-import nonliterals
import session
_logger = logging.getLogger(__name__)
@@ -95,12 +93,12 @@ class WebRequest(object):
if not self.session:
self.session = session.OpenERPSession()
self.httpsession[self.session_id] = self.session
- self.context = self.params.pop('context', None)
+ self.context = self.params.pop('context', {})
self.debug = self.params.pop('debug', False) is not False
# Determine self.lang
lang = self.params.get('lang', None)
if lang is None:
- lang = self.session.eval_context(self.context).get('lang')
+ lang = self.context.get('lang')
if lang is None:
lang = self.httprequest.cookies.get('lang')
if lang is None:
@@ -112,6 +110,11 @@ class WebRequest(object):
# we use _ as seprator where RFC2616 uses '-'
self.lang = lang.replace('-', '_')
+def reject_nonliteral(dct):
+ if '__ref' in dct:
+ raise ValueError(
+ "Non literal contexts can not be sent to the server anymore (%r)" % (dct,))
+ return dct
class JsonRequest(WebRequest):
""" JSON-RPC2 over HTTP.
@@ -182,9 +185,9 @@ class JsonRequest(WebRequest):
try:
# Read POST content or POST Form Data named "request"
if requestf:
- self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
+ self.jsonrequest = simplejson.load(requestf, object_hook=reject_nonliteral)
else:
- self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
+ self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
self.init(self.jsonrequest.get("params", {}))
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("--> %s.%s\n%s", method.im_class.__name__, method.__name__, pprint.pformat(self.jsonrequest))
@@ -233,10 +236,10 @@ class JsonRequest(WebRequest):
# We need then to manage http sessions manually.
response['httpsessionid'] = self.httpsession.sid
mime = 'application/javascript'
- body = "%s(%s);" % (jsonp, simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),)
+ body = "%s(%s);" % (jsonp, simplejson.dumps(response),)
else:
mime = 'application/json'
- body = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
+ body = simplejson.dumps(response)
r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
return r
@@ -402,8 +405,10 @@ def session_context(request, session_store, session_lock, sid):
for k, v in request.session.iteritems():
stored = in_store.get(k)
if stored and isinstance(v, session.OpenERPSession):
- v.contexts_store.update(stored.contexts_store)
- v.domains_store.update(stored.domains_store)
+ if hasattr(v, 'contexts_store'):
+ del v.contexts_store
+ if hasattr(v, 'domains_store'):
+ del v.domains_store
if not hasattr(v, 'jsonp_requests'):
v.jsonp_requests = {}
v.jsonp_requests.update(getattr(
diff --git a/addons/web/i18n/es_MX.po b/addons/web/i18n/es_MX.po
new file mode 100644
index 00000000000..37cc8570bd7
--- /dev/null
+++ b/addons/web/i18n/es_MX.po
@@ -0,0 +1,2067 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 15:46+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:249
+#, python-format
+msgid "Restore Database"
+msgstr "Restaurar base de datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:134
+#, python-format
+msgid "Default language:"
+msgstr "Idioma predeterminado:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:326
+#, python-format
+msgid "Loading"
+msgstr "Cargando"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:590
+#, python-format
+msgid "%d minutes ago"
+msgstr "Hace %d minutos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:1069
+#, python-format
+msgid "You must choose at least one record."
+msgstr "Debe seleccionar al menos un registro."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:618
+#, python-format
+msgid "Still loading...<br />Please be patient."
+msgstr "Aún cargando...<br />por favor, tenga paciencia."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:619
+#, python-format
+msgid "Don't leave yet,<br />it's still loading..."
+msgstr "No se vaya,<br />Aún cargando..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1834
+#, python-format
+msgid "%(field)s %(operator)s \"%(value)s\""
+msgstr "%(field)s %(operator)s \"%(value)s\""
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1886
+#: code:addons/web/static/src/js/search.js:1920
+#: code:addons/web/static/src/js/search.js:1944
+#, python-format
+msgid "less or equal than"
+msgstr "menor o igual que"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:596
+#, python-format
+msgid "%d months ago"
+msgstr "hace %d meses"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:774
+#, python-format
+msgid "Invalid Search"
+msgstr "Búsqueda incorrecta"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:597
+#, python-format
+msgid "about a year ago"
+msgstr "hace un año"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:118
+#: code:addons/web/static/src/xml/base.xml:168
+#: code:addons/web/static/src/xml/base.xml:283
+#, python-format
+msgid "Master password:"
+msgstr "Contraseña principal"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1270
+#, python-format
+msgid "Button"
+msgstr "Botón"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:275
+#, python-format
+msgid "Change Master Password"
+msgstr "Cambiar contraseña principal"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:800
+#, python-format
+msgid "View Log (%s)"
+msgstr "Ver Registro (%s)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1656
+#, python-format
+msgid "Remove All"
+msgstr "Quitar Todos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:593
+#, python-format
+msgid "a day ago"
+msgstr "Hace un día"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1655
+#, python-format
+msgid "Remove"
+msgstr "Eliminar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1394
+#, python-format
+msgid "Search %(field)s at: %(value)s"
+msgstr "Buscar %(field)s en: %(value)s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1738
+#, python-format
+msgid "Does your file have titles?"
+msgstr "¿Tiene su archivo títulos?"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1289
+#, python-format
+msgid "Method:"
+msgstr "Método:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:1417
+#, python-format
+msgid "%(page)d/%(page_count)d"
+msgstr "%(page)d/%(page_count)d"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:549
+#, python-format
+msgid "Restored"
+msgstr "Restaurada"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:592
+#, python-format
+msgid "%d hours ago"
+msgstr "Hace %d horas"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4175
+#: code:addons/web/static/src/js/view_form.js:4306
+#, python-format
+msgid "Add: "
+msgstr "Agregar: "
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2821
+#, python-format
+msgid "Search More..."
+msgstr "Buscar más..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1754
+#, python-format
+msgid "Latin 1"
+msgstr "Latin 1"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:528
+#, python-format
+msgid "XML ID:"
+msgstr "ID XML:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:260
+#: code:addons/web/static/src/js/chrome.js:269
+#: code:addons/web/static/src/js/chrome.js:449
+#: code:addons/web/static/src/js/chrome.js:788
+#: code:addons/web/static/src/js/view_form.js:576
+#: code:addons/web/static/src/js/view_form.js:1939
+#: code:addons/web/static/src/xml/base.xml:1701
+#, python-format
+msgid "Ok"
+msgstr "Aceptar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:1127
+#, python-format
+msgid "Uploading..."
+msgstr "Subiendo..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:126
+#, python-format
+msgid "Load Demonstration data:"
+msgstr "Cargar datos de demostración:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:516
+#, python-format
+msgid "View"
+msgstr "Vista"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2880
+#, python-format
+msgid "Search: "
+msgstr "Buscar: "
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4857
+#, python-format
+msgid "Save As..."
+msgstr "Guardar como…"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4982
+#, python-format
+msgid "Could not display the selected image."
+msgstr "No se pudo mostrar la imagen seleccionada."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:217
+#, python-format
+msgid "Backup Database"
+msgstr "Respaldar Base de Datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:906
+#, python-format
+msgid "Size:"
+msgstr "Tamaño:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1172
+#: code:addons/web/static/src/xml/base.xml:1174
+#, python-format
+msgid "Save As"
+msgstr "Guardar como"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:620
+#, python-format
+msgid ""
+"You may not believe it,<br />but the application is actually loading..."
+msgstr "No lo vas a creer,<br />pero la aplicación sigue cargando..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:509
+#, python-format
+msgid "Database backed up successfully"
+msgstr "Base de datos guardada correctamente"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1757
+#, python-format
+msgid ""
+"For use if CSV files have titles on multiple lines, skips more than a single "
+"line during import"
+msgstr ""
+"Para su uso si los archivos CSV tienen títulos en varias líneas, se omiten "
+"más de una sola línea durante la importación"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:514
+#, python-format
+msgid "Technical translation"
+msgstr "Traducción técnica"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1552
+#, python-format
+msgid "Save current filter"
+msgstr "Salvar este filtro"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1786
+#, python-format
+msgid "Here is a preview of the file we could not import:"
+msgstr "Esta es una vista previa del archivo que no se pudo importar:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1746
+#, python-format
+msgid "Delimiter:"
+msgstr "Delimitador:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:589
+#, python-format
+msgid "about a minute ago"
+msgstr "hace un minuto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1723
+#, python-format
+msgid "1. Import a .CSV file"
+msgstr "1. Importar un archivo .CSV"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:257
+#, python-format
+msgid "File:"
+msgstr "Archivo:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1413
+#, python-format
+msgid "-- Actions --"
+msgstr "-- Acciones --"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:251
+#: code:addons/web/static/src/xml/base.xml:311
+#, python-format
+msgid "Restore"
+msgstr "Restaurar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:197
+#: code:addons/web/static/src/xml/base.xml:162
+#: code:addons/web/static/src/xml/base.xml:308
+#, python-format
+msgid "Duplicate"
+msgstr "Duplicar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/formats.js:180
+#, python-format
+msgid "(%d records)"
+msgstr "(%d registros)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1293
+#, python-format
+msgid "not a valid number"
+msgstr "no es un número válido"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:922
+#, python-format
+msgid "Change default:"
+msgstr "Cambiar valor por defecto:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:506
+#, python-format
+msgid "Toggle Form Layout Outline"
+msgstr "Cambiar esquema de diseño"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:683
+#: code:addons/web/static/src/js/views.js:1069
+#, python-format
+msgid "Warning"
+msgstr "Advertencia"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:517
+#, python-format
+msgid "Edit SearchView"
+msgstr "Editar Vista de búsqueda"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:207
+#: code:addons/web/static/src/xml/base.xml:239
+#: code:addons/web/static/src/xml/base.xml:261
+#, python-format
+msgid "Master Password:"
+msgstr "Contraseña principal:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1975
+#, python-format
+msgid "is true"
+msgstr "es verdadero"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1871
+#: code:addons/web/static/src/js/search.js:1881
+#: code:addons/web/static/src/js/search.js:1915
+#: code:addons/web/static/src/js/search.js:1939
+#, python-format
+msgid "is equal to"
+msgstr "es igual a"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:926
+#, python-format
+msgid "On change:"
+msgstr "Al cambiar:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:3883
+#, python-format
+msgid "Add an item"
+msgstr "Agregar un elemento"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1568
+#, python-format
+msgid "Advanced Search"
+msgstr "Búsqueda Avanzada"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1166
+#: code:addons/web/static/src/xml/base.xml:1323
+#, python-format
+msgid "Select"
+msgstr "Seleccionar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1872
+#: code:addons/web/static/src/js/search.js:1882
+#: code:addons/web/static/src/js/search.js:1916
+#: code:addons/web/static/src/js/search.js:1940
+#, python-format
+msgid "is not equal to"
+msgstr "no es igual a"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:549
+#, python-format
+msgid "Database restored successfully"
+msgstr "Base de datos restaurada correctamente"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:590
+#, python-format
+msgid "Add..."
+msgstr "Agregar..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:400
+#, python-format
+msgid "Preferences"
+msgstr "Preferencias"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1931
+#, python-format
+msgid "Confirm"
+msgstr "Confirmar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:622
+#, python-format
+msgid "Maybe you should consider reloading the application by pressing F5..."
+msgstr "Quizá debería considerar recargar la aplicación presionando F5..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:17
+#: code:addons/web/static/src/xml/base.xml:112
+#: code:addons/web/static/src/xml/base.xml:307
+#: code:addons/web/static/src/xml/base.xml:764
+#: code:addons/web/static/src/xml/base.xml:1325
+#, python-format
+msgid "Create"
+msgstr "Crear"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:186
+#, python-format
+msgid "Drop Database"
+msgstr "Eliminar Base de Datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1883
+#: code:addons/web/static/src/js/search.js:1917
+#: code:addons/web/static/src/js/search.js:1941
+#, python-format
+msgid "greater than"
+msgstr "mayor que"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1884
+#: code:addons/web/static/src/js/search.js:1918
+#: code:addons/web/static/src/js/search.js:1942
+#, python-format
+msgid "less than"
+msgstr "menor que"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:2184
+#, python-format
+msgid "Download \"%s\""
+msgstr "Descargar \"%s\""
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:410
+#, python-format
+msgid "Version"
+msgstr "Versión"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:540
+#, python-format
+msgid "Latest Modification Date:"
+msgstr "Última fecha de modificación:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1455
+#, python-format
+msgid "M2O search fields do not currently handle multiple default values"
+msgstr ""
+"Los campos de búsqueda M2O (Muchos a uno) no soportan múltiples valores por "
+"defecto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:581
+#, python-format
+msgid "Delete this attachment"
+msgstr "Borrar este adjunto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:413
+#, python-format
+msgid "OpenERP is a trademark of the"
+msgstr "OpenERP es una marca registrada de la"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:885
+#, python-format
+msgid "(nolabel)"
+msgstr "(sin etiqueta)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:337
+#, python-format
+msgid "Unlimited"
+msgstr "Ilimitado"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:972
+#: code:addons/web/static/src/xml/base.xml:350
+#, python-format
+msgid "More"
+msgstr "Más"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:73
+#, python-format
+msgid "Username"
+msgstr "Nombre de usuario"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2912
+#, python-format
+msgid "Add %s"
+msgstr "Agregar %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1724
+#, python-format
+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 ""
+"Seleccione un archivo .CSV para importar. Si necesita un ejemplo de archivo "
+"para importar,\n"
+" debería usar la herramienta de exportación con la opción de "
+"\"Importación Compatible\"."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:77
+#, python-format
+msgid "Form"
+msgstr "Formulario"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:784
+#: code:addons/web/static/src/xml/base.xml:511
+#, python-format
+msgid "Fields View Get"
+msgstr "Obtener Campos de Vista"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1273
+#, python-format
+msgid "(no string)"
+msgstr "(sin cadena)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:146
+#, python-format
+msgid "Admin password:"
+msgstr "Contraseña de Admin:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:150
+#, python-format
+msgid "Confirm password:"
+msgstr "Confirmar contraseña:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:594
+#, python-format
+msgid "%d days ago"
+msgstr "Hace %d días"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1802
+#, python-format
+msgid "Name:"
+msgstr "Nombre:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1728
+#, python-format
+msgid "CSV File:"
+msgstr "Archivo CSV:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1743
+#, python-format
+msgid "Advanced"
+msgstr "Avanzado"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:416
+#, python-format
+msgid "OpenERP.com"
+msgstr "OpenERP.com"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:325
+#, python-format
+msgid "New Password:"
+msgstr "Contraseña Nueva:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1628
+#, python-format
+msgid "Import-Compatible Export"
+msgstr "Exportación Compatible a Importación."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1640
+#, python-format
+msgid "Fields to export"
+msgstr "Campos a exportar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/data_export.js:374
+#, python-format
+msgid "Please select fields to export..."
+msgstr "Por favor, seleccione los campos a exportar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1557
+#, python-format
+msgid "Share with all users"
+msgstr "Compartir con todos los usuarios"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:595
+#, python-format
+msgid "about a month ago"
+msgstr "hace un mes"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_tree.js:11
+#, python-format
+msgid "Tree"
+msgstr "Árbol"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1549
+#, python-format
+msgid "Custom Filters"
+msgstr "Filtros personalizados"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:287
+#, python-format
+msgid "New master password:"
+msgstr "Nueva contraseña principal:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:503
+#, python-format
+msgid "Debug View#"
+msgstr "Depurar Vista#"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1976
+#, python-format
+msgid "is false"
+msgstr "es falso"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1285
+#, python-format
+msgid "Button Type:"
+msgstr "Tipo de Botón"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1629
+#, python-format
+msgid "Export all Data"
+msgstr "Exportar todos los datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:414
+#, python-format
+msgid "OpenERP SA Company"
+msgstr "Compañía OpenERP SA"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:1137
+#, python-format
+msgid "Do you really want to delete this attachment ?"
+msgstr "¿De verdad quiere eliminar este adjunto?"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:1018
+#, python-format
+msgid "Unknown m2m command "
+msgstr "Comando m2m desconocido "
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1014
+#, python-format
+msgid "Select date"
+msgstr "Seleccionar fecha"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1555
+#, python-format
+msgid "Custom Filter"
+msgstr "Filtro Personalizado"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:399
+#, python-format
+msgid "About OpenERP"
+msgstr "Acerca de OpenERP"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:196
+#: code:addons/web/static/src/js/view_list.js:356
+#: code:addons/web/static/src/xml/base.xml:1713
+#, python-format
+msgid "Delete"
+msgstr "Borrar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:160
+#, python-format
+msgid "Duplicate Database"
+msgstr "Duplicar Base de Datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1674
+#, python-format
+msgid "Unknown field %s in domain %s"
+msgstr "Campo desconocido %s en el dominio %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:277
+#: code:addons/web/static/src/xml/base.xml:335
+#, python-format
+msgid "Change Password"
+msgstr "Cambiar Contraseña"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:817
+#, python-format
+msgid "Incorrect value for field %(fieldname)s: [%(value)s] is %(message)s"
+msgstr ""
+"Valor incorrecto para el campo %(fieldname)s: [%(value)s] es %(message)s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:305
+#, python-format
+msgid "Database Management"
+msgstr "Administración de Bases de Datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4982
+#, python-format
+msgid "Image"
+msgstr "Imágen"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:81
+#, python-format
+msgid "Manage Databases"
+msgstr "Adminstrar Bases de datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1377
+#, python-format
+msgid "Advanced Search..."
+msgstr "Búsqueda avanzada..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:172
+#, python-format
+msgid "Original database name:"
+msgstr "Nombre original de la BD"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1279
+#, python-format
+msgid "not a valid integer"
+msgstr "entero no válido"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:82
+#: code:addons/web/static/src/xml/base.xml:436
+#, python-format
+msgid "Powered by"
+msgstr "Con tecnología de"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4926
+#: code:addons/web/static/src/js/view_list.js:2172
+#, python-format
+msgid "Download"
+msgstr "Descargar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1376
+#: code:addons/web/static/src/xml/base.xml:922
+#, python-format
+msgid "Yes"
+msgstr "Sí"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1666
+#, python-format
+msgid "Unknown operator %s in domain %s"
+msgstr "Operador desconocido %s en el dominio %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:512
+#: code:addons/web/static/src/xml/base.xml:1416
+#, python-format
+msgid "Manage Filters"
+msgstr "Administrar Filtros"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:77
+#, python-format
+msgid "Log in"
+msgstr "Iniciar Sesión"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:350
+#, python-format
+msgid "Invalid database name"
+msgstr "Nombre de base de datos incorrecto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1642
+#, python-format
+msgid "Save fields list"
+msgstr "Guardar lista de campos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1869
+#, python-format
+msgid "contains"
+msgstr "contiene"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:414
+#, python-format
+msgid "Group"
+msgstr "Grupo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1377
+#, python-format
+msgid "No"
+msgstr "No"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:3779
+#, python-format
+msgid "The o2m record must be saved before an action can be used"
+msgstr ""
+"El registro o2m debe ser guardado antes de que alguna acción pueda ser usada."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:879
+#, python-format
+msgid "Unhandled widget"
+msgstr "Widget no controlado"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:591
+#, python-format
+msgid "about an hour ago"
+msgstr "hace una hora"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:934
+#, python-format
+msgid "Selection:"
+msgstr "Selección:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1773
+#, python-format
+msgid "--- Don't Import ---"
+msgstr "--- No importar ---"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4124
+#: code:addons/web/static/src/js/view_form.js:4265
+#: code:addons/web/static/src/xml/base.xml:1356
+#: code:addons/web/static/src/xml/base.xml:1654
+#, python-format
+msgid "Add"
+msgstr "Agregar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:122
+#: code:addons/web/static/src/xml/base.xml:176
+#: code:addons/web/static/src/xml/base.xml:265
+#, python-format
+msgid "New database name:"
+msgstr "Nombre de la nueva base de datos:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1219
+#, python-format
+msgid "...Upload in progress..."
+msgstr "...Carga en progreso..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1717
+#, python-format
+msgid "Import"
+msgstr "Importar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:534
+#, python-format
+msgid "Creation Date:"
+msgstr "Fecha de Creación:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1573
+#, python-format
+msgid "Add a condition"
+msgstr "Agregar una condicion"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:617
+#, python-format
+msgid "Still loading..."
+msgstr "Aún cargando..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1619
+#, python-format
+msgid ""
+"This wizard will export all data that matches the current search criteria to "
+"a CSV file.\n"
+" You can export all data or only the fields that can be "
+"reimported after modification."
+msgstr ""
+"Este asistente le exportará todos los datos que coincidan con los criterios "
+"de búsqueda actual a un archivo CSV.\n"
+" Puede exportar todos los datos o sólo los campos que pueden "
+"importarse de nuevo después de la modificación."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4822
+#, python-format
+msgid "File upload"
+msgstr "Subir Archivos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:3778
+#, python-format
+msgid "Action Button"
+msgstr "Botón Accion"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:829
+#, python-format
+msgid "All users"
+msgstr "Todos los usuarios"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:598
+#, python-format
+msgid "%d years ago"
+msgstr "hace %d años"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:291
+#, python-format
+msgid "Confirm new master password:"
+msgstr "Confirmar nueva contraseña principal:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1071
+#, python-format
+msgid "Save default"
+msgstr "Guardar por defecto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:621
+#, python-format
+msgid "Take a minute to get a coffee,<br />because it's loading..."
+msgstr "Tómese un minuto para un café,<br />porque sigue cargando..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:408
+#, python-format
+msgid "Activate the developer mode"
+msgstr "Activar modo desarrollador"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:324
+#, python-format
+msgid "Loading (%d)"
+msgstr "Cargando (%d)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4857
+#, python-format
+msgid "The field is empty, there's nothing to save !"
+msgstr "¡El campo está vacío, nada que guardar!"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:515
+#, python-format
+msgid "Manage Views"
+msgstr "Administrar Vistas"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:509
+#, python-format
+msgid "Backed"
+msgstr "Guardado"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1559
+#, python-format
+msgid "Use by default"
+msgstr "Usar por defecto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1554
+#, python-format
+msgid "Filter name"
+msgstr "Nombre del filtro"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:4821
+#, python-format
+msgid "The selected file exceed the maximum file size of %s."
+msgstr "El archivo seleccionado excede el tamaño máximo de %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1110
+#, python-format
+msgid "GroupBy"
+msgstr "Agrupar por"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1750
+#, python-format
+msgid "Encoding:"
+msgstr "Codificación:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:683
+#, python-format
+msgid "You must select at least one record."
+msgstr "Debe seleccionar al menos un registro"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1757
+#, python-format
+msgid "Lines to skip"
+msgstr "Líneas a omitir"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2834
+#, python-format
+msgid "Create \"<strong>%s</strong>\""
+msgstr "Crear \"<strong>%s</strong>\""
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/data_export.js:361
+#, python-format
+msgid "Please select fields to save export list..."
+msgstr ""
+"Por favor, seleccione los campos para guardar la lista de exportación..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/data_export.js:126
+#, python-format
+msgid "Please enter save field list name"
+msgstr "Por favor, introduzca el nombre de la lista de campos a guardar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1064
+#, python-format
+msgid "Set Default"
+msgstr "Establecer Por defecto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:930
+#, python-format
+msgid "Relation:"
+msgstr "Relación:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:588
+#, python-format
+msgid "less than a minute ago"
+msgstr "hace menos de un minuto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:774
+#, python-format
+msgid "triggered from search view"
+msgstr "Disparador activado desde la vista de búsqueda"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:413
+#, python-format
+msgid "Copyright © 2004-TODAY OpenERP SA. All Rights Reserved."
+msgstr "Copyright © 2004-HOY OpenERP SA. Todos los derechos reservados."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:977
+#, python-format
+msgid "Filter"
+msgstr "Filtro"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:902
+#, python-format
+msgid "Widget:"
+msgstr "Widget:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:803
+#, python-format
+msgid "Condition:"
+msgstr "Condición:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1638
+#, python-format
+msgid "Available fields"
+msgstr "Campos disponibles"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1626
+#, python-format
+msgid "Export Type:"
+msgstr "Tipo de Exportación:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1712
+#, python-format
+msgid "Unsupported operator %s in domain %s"
+msgstr "Operador no soportado %s en el dominio %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1784
+#, python-format
+msgid "The import failed due to:"
+msgstr "La importación falló debido a:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:219
+#: code:addons/web/static/src/xml/base.xml:310
+#, python-format
+msgid "Backup"
+msgstr "Copia de Seguridad"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:509
+#, python-format
+msgid "JS Tests"
+msgstr "Pruebas JS"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:518
+#, python-format
+msgid "Edit Action"
+msgstr "Editar Acción"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:525
+#, python-format
+msgid "ID:"
+msgstr "ID:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:926
+#, python-format
+msgid "Filter on: %s"
+msgstr "Filtro en: %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:971
+#, python-format
+msgid "Print"
+msgstr "Imprimir"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:822
+#, python-format
+msgid "Only you"
+msgstr "Sólo usted"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:519
+#, python-format
+msgid "Edit Workflow"
+msgstr "Editar Flujo de trabajo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2844
+#, python-format
+msgid "Create and Edit..."
+msgstr "Crear y Editar..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2880
+#: code:addons/web/static/src/js/view_form.js:3731
+#, python-format
+msgid "Create: "
+msgstr "Crear: "
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:586
+#: code:addons/web/static/src/xml/base.xml:1249
+#, python-format
+msgid "/web/binary/upload_attachment"
+msgstr "/web/binary/upload_attachment"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:510
+#, python-format
+msgid "View Fields"
+msgstr "Ver Campos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1423
+#, python-format
+msgid "(Any existing filter with the same name will be replaced)"
+msgstr "(Cualquier filtro existente con el mismo nombre será reemplazado)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:1934
+#: code:addons/web/static/src/xml/base.xml:1326
+#: code:addons/web/static/src/xml/base.xml:1809
+#, python-format
+msgid "Cancel"
+msgstr "Cancelar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/data_export.js:11
+#: code:addons/web/static/src/js/view_form.js:1070
+#: code:addons/web/static/src/xml/base.xml:1343
+#, python-format
+msgid "Close"
+msgstr "Cerrar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1667
+#, python-format
+msgid "Name"
+msgstr "Nombre"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/coresetup.js:616
+#: code:addons/web/static/src/xml/base.xml:9
+#, python-format
+msgid "Loading..."
+msgstr "Cargando…"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:572
+#, python-format
+msgid "Do you really want to remove these records?"
+msgstr "¿Está seguro que quiere eliminar estos registros?"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:890
+#, python-format
+msgid "Field:"
+msgstr "Campo:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:910
+#, python-format
+msgid "Context:"
+msgstr "Contexto:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:537
+#, python-format
+msgid "Latest Modification by:"
+msgstr "Última Modificación por:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:726
+#: code:addons/web/static/src/xml/base.xml:767
+#: code:addons/web/static/src/xml/base.xml:1331
+#: code:addons/web/static/src/xml/base.xml:1561
+#, python-format
+msgid "Save"
+msgstr "Guardar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1961
+#, python-format
+msgid "is not"
+msgstr "no es"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:520
+#, python-format
+msgid "Print Workflow"
+msgstr "Imprimir flujo de trabajo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1934
+#: code:addons/web/static/src/js/search.js:1960
+#, python-format
+msgid "is"
+msgstr "es"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/data_export.js:6
+#, python-format
+msgid "Export Data"
+msgstr "Exportar información"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:284
+#: code:addons/web/static/src/js/chrome.js:1121
+#, python-format
+msgid "Client Error"
+msgstr "Error del cliente"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:914
+#, python-format
+msgid "Domain:"
+msgstr "Dominio:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:505
+#, python-format
+msgid "View Log (perm_read)"
+msgstr "Ver Registro (perm_read)"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1753
+#, python-format
+msgid "UTF-8"
+msgstr "UTF-8"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1280
+#, python-format
+msgid "Special:"
+msgstr "Especial:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:788
+#, python-format
+msgid ""
+"Warning, the record has been modified, your changes will be discarded.\n"
+"\n"
+"Are you sure you want to leave this page ?"
+msgstr ""
+"Alerta, el registro ha sido modificado, sus cambios serán descartados.\n"
+"\n"
+"¿Está seguro de que quiere dejar esta pagina?"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:531
+#, python-format
+msgid "Creation User:"
+msgstr "Usuario de Creación:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:68
+#: code:addons/web/static/src/xml/base.xml:194
+#: code:addons/web/static/src/xml/base.xml:226
+#, python-format
+msgid "Database:"
+msgstr "Base de datos:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:786
+#, python-format
+msgid "Default:"
+msgstr "Por defecto:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:1318
+#, python-format
+msgid "Undefined"
+msgstr "Sin definir"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1734
+#, python-format
+msgid "Import Options"
+msgstr "Opciones de importación"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:82
+#: code:addons/web/static/src/xml/base.xml:437
+#, python-format
+msgid "OpenERP"
+msgstr "OpenERP"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:416
+#, python-format
+msgid "For more information visit"
+msgstr "Para más información visite"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:773
+#, python-format
+msgid "Do you really want to delete this record?"
+msgstr "¿Realmente desea eliminar este registro?"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1334
+#, python-format
+msgid "Save & Close"
+msgstr "Guardar y Cerrar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1807
+#, python-format
+msgid "Quick Add"
+msgstr "Agregar rapidamente"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1132
+#: code:addons/web/static/src/xml/base.xml:1189
+#, python-format
+msgid "Uploading ..."
+msgstr "Cargando..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:426
+#, python-format
+msgid "%d / %d"
+msgstr "%d / %d"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1808
+#, python-format
+msgid "Add All Info..."
+msgstr "Agregar toda la información..."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:75
+#: code:addons/web/static/src/xml/base.xml:312
+#, python-format
+msgid "Password"
+msgstr "Contraseña"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1632
+#, python-format
+msgid "Export Formats"
+msgstr "Formatos de Exportación"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1731
+#, python-format
+msgid "2. Check your file format"
+msgstr "2. Compruebe el formato de archivo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/chrome.js:1015
+#, python-format
+msgid "About"
+msgstr "Acerca de"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/views.js:842
+#, python-format
+msgid "Model %s fields"
+msgstr "Campos del Modelo %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1380
+#, python-format
+msgid "Search Again"
+msgstr "Volver a buscar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1664
+#: code:addons/web/static/src/xml/base.xml:1403
+#, python-format
+msgid "Filters"
+msgstr "Filtros"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1404
+#, python-format
+msgid "-- Filters --"
+msgstr "-- Filtros --"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:516
+#: code:addons/web/static/src/xml/base.xml:761
+#: code:addons/web/static/src/xml/base.xml:1127
+#, python-format
+msgid "Edit"
+msgstr "Editar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1179
+#: code:addons/web/static/src/xml/base.xml:1181
+#, python-format
+msgid "Clear"
+msgstr "Limpiar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:188
+#: code:addons/web/static/src/xml/base.xml:309
+#, python-format
+msgid "Drop"
+msgstr "Eliminar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:8
+#, python-format
+msgid "List"
+msgstr "Lista"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:918
+#, python-format
+msgid "Modifiers:"
+msgstr "Modificadores:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1414
+#, python-format
+msgid "Add Advanced Filter"
+msgstr "Añadir Filtro Avanzado"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1699
+#, python-format
+msgid "Save as:"
+msgstr "Guardar como:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:1312
+#, python-format
+msgid ""
+"Grouping on field '%s' is not possible because that field does not appear in "
+"the list view."
+msgstr ""
+"Agrupar por el campo '%s' no es posible porque este no aparece en la vista "
+"de lista."
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:507
+#, python-format
+msgid "Set Defaults"
+msgstr "Establecer por defecto"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1245
+#, python-format
+msgid "Search %(field)s for: %(value)s"
+msgstr "Buscar %(field)s para: %(value)s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1870
+#, python-format
+msgid "doesn't contain"
+msgstr "no contiene"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:729
+#: code:addons/web/static/src/xml/base.xml:769
+#: code:addons/web/static/src/xml/base.xml:1340
+#, python-format
+msgid "Discard"
+msgstr "Descartar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1226
+#, python-format
+msgid "Delete this file"
+msgstr "Borrar este archivo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:415
+#, python-format
+msgid "Licenced under the terms of"
+msgstr "Con licencia bajo los términos de"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1415
+#, python-format
+msgid "Save Filter"
+msgstr "Guardar Filtro"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_form.js:2999
+#: code:addons/web/static/src/js/view_form.js:3642
+#: code:addons/web/static/src/js/view_form.js:3759
+#: code:addons/web/static/src/js/view_form.js:4199
+#: code:addons/web/static/src/js/view_form.js:4324
+#, python-format
+msgid "Open: "
+msgstr "Abrir: "
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1293
+#, python-format
+msgid "Action ID:"
+msgstr "ID de Acción:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:110
+#, python-format
+msgid "Create Database"
+msgstr "Crear base de datos"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1380
+#, python-format
+msgid "Search"
+msgstr "Buscar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:415
+#, python-format
+msgid "GNU Affero General Public License"
+msgstr "GNU Affero General Public License"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1744
+#, python-format
+msgid "Separator:"
+msgstr "Separador:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1705
+#, python-format
+msgid "Saved exports:"
+msgstr "Exportaciones guardadas:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:401
+#, python-format
+msgid "Log out"
+msgstr "Cerrar sesión"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1086
+#, python-format
+msgid "Group by: %s"
+msgstr "Agrupar por: %s"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:330
+#, python-format
+msgid "Confirm Password:"
+msgstr "Confirmar contraseña:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:728
+#: code:addons/web/static/src/xml/base.xml:768
+#: code:addons/web/static/src/xml/base.xml:1325
+#: code:addons/web/static/src/xml/base.xml:1333
+#: code:addons/web/static/src/xml/base.xml:1579
+#, python-format
+msgid "or"
+msgstr "ó"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/search.js:1885
+#: code:addons/web/static/src/js/search.js:1919
+#: code:addons/web/static/src/js/search.js:1943
+#, python-format
+msgid "greater or equal than"
+msgstr "mayor o igual que"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:320
+#, python-format
+msgid "Old Password:"
+msgstr "Contraseña anterior:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:301
+#, python-format
+msgid "Back to Login"
+msgstr "Volver al Inicio de sesión"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1421
+#, python-format
+msgid "Filter Name:"
+msgstr "Nombre del Filtro"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/view_list.js:355
+#: code:addons/web/static/src/xml/base.xml:1614
+#, python-format
+msgid "Export"
+msgstr "Exportar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/formats.js:113
+#, python-format
+msgid "Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb"
+msgstr "Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1246
+#, python-format
+msgid "File"
+msgstr "Archivo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:898
+#, python-format
+msgid "Type:"
+msgstr "Tipo:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1574
+#, python-format
+msgid "Apply"
+msgstr "Aplicar"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:894
+#, python-format
+msgid "Object:"
+msgstr "Objeto:"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/xml/base.xml:1335
+#, python-format
+msgid "Save & New"
+msgstr "Guardar y Nuevo"
+
+#. module: web
+#. openerp-web
+#: code:addons/web/static/src/js/data_export.js:12
+#, python-format
+msgid "Export To File"
+msgstr "Exportar a fichero"
diff --git a/addons/web/i18n/fa.po b/addons/web/i18n/fa.po
index 2378d6fc763..c9dfa110644 100644
--- a/addons/web/i18n/fa.po
+++ b/addons/web/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web
#. openerp-web
diff --git a/addons/web/i18n/fr.po b/addons/web/i18n/fr.po
index 913336f5669..116374831cb 100644
--- a/addons/web/i18n/fr.po
+++ b/addons/web/i18n/fr.po
@@ -8,14 +8,15 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-11-24 01:23+0000\n"
-"PO-Revision-Date: 2012-11-21 16:26+0000\n"
-"Last-Translator: Quentin THEURET <Unknown>\n"
+"PO-Revision-Date: 2012-11-28 09:28+0000\n"
+"Last-Translator: Frederic Clementi - Camptocamp.com "
+"<frederic.clementi@camptocamp.com>\n"
"Language-Team: French <fr@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-11-25 06:40+0000\n"
-"X-Generator: Launchpad (build 16293)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web
#. openerp-web
@@ -36,7 +37,7 @@ msgstr "Langue par défaut :"
#: code:addons/web/static/src/js/chrome.js:326
#, python-format
msgid "Loading"
-msgstr ""
+msgstr "Chargement ..."
#. module: web
#. openerp-web
@@ -50,21 +51,21 @@ msgstr "il y a %d minutes"
#: code:addons/web/static/src/js/views.js:1069
#, python-format
msgid "You must choose at least one record."
-msgstr "Vous devez choisir au moins un enregistrement"
+msgstr "Vous devez sélectionner au moins un enregistrement"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/coresetup.js:618
#, python-format
msgid "Still loading...<br />Please be patient."
-msgstr ""
+msgstr "Le chargement continue... <br />Soyez patient SVP."
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/coresetup.js:619
#, python-format
msgid "Don't leave yet,<br />it's still loading..."
-msgstr ""
+msgstr "Ne partez pas,<br />c'est presque terminé..."
#. module: web
#. openerp-web
diff --git a/addons/web/i18n/hu.po b/addons/web/i18n/hu.po
index ec6012b8463..9cde07fb6e3 100644
--- a/addons/web/i18n/hu.po
+++ b/addons/web/i18n/hu.po
@@ -8,21 +8,21 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-11-24 01:23+0000\n"
-"PO-Revision-Date: 2012-11-23 10:31+0000\n"
+"PO-Revision-Date: 2012-11-28 10:21+0000\n"
"Last-Translator: Juhász Krisztián <Unknown>\n"
"Language-Team: Hungarian <hu@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-25 06:41+0000\n"
-"X-Generator: Launchpad (build 16293)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:249
#, python-format
msgid "Restore Database"
-msgstr ""
+msgstr "Adatbázis Visszaállítása"
#. module: web
#. openerp-web
@@ -36,7 +36,7 @@ msgstr "Alapértelmezett nyelv:"
#: code:addons/web/static/src/js/chrome.js:326
#, python-format
msgid "Loading"
-msgstr ""
+msgstr "Betöltés"
#. module: web
#. openerp-web
@@ -57,21 +57,21 @@ msgstr "Választani kell legalább egy rekordot!"
#: code:addons/web/static/src/js/coresetup.js:618
#, python-format
msgid "Still loading...<br />Please be patient."
-msgstr ""
+msgstr "Még tölt...<br />Várjon türelemmel."
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/coresetup.js:619
#, python-format
msgid "Don't leave yet,<br />it's still loading..."
-msgstr ""
+msgstr "Még ne menjen el, <br />még tölt..."
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/search.js:1834
#, python-format
msgid "%(field)s %(operator)s \"%(value)s\""
-msgstr ""
+msgstr "%(field)s %(operator)s \"%(value)s\""
#. module: web
#. openerp-web
@@ -87,7 +87,7 @@ msgstr "kisebb vagy egyenlő, mint"
#: code:addons/web/static/src/js/coresetup.js:596
#, python-format
msgid "%d months ago"
-msgstr ""
+msgstr "%d hónapja"
#. module: web
#. openerp-web
@@ -101,7 +101,7 @@ msgstr "Helytelen keresés"
#: code:addons/web/static/src/js/coresetup.js:597
#, python-format
msgid "about a year ago"
-msgstr ""
+msgstr "kb. egy éve"
#. module: web
#. openerp-web
@@ -110,28 +110,28 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:283
#, python-format
msgid "Master password:"
-msgstr ""
+msgstr "Mester jelszó"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1270
#, python-format
msgid "Button"
-msgstr ""
+msgstr "Gomb"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:275
#, python-format
msgid "Change Master Password"
-msgstr ""
+msgstr "Mester jelszó változtatása"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/views.js:800
#, python-format
msgid "View Log (%s)"
-msgstr ""
+msgstr "Logok nézete (%s)"
#. module: web
#. openerp-web
@@ -145,7 +145,7 @@ msgstr "Összes eltávolítása"
#: code:addons/web/static/src/js/coresetup.js:593
#, python-format
msgid "a day ago"
-msgstr ""
+msgstr "egy napja"
#. module: web
#. openerp-web
@@ -166,21 +166,21 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1738
#, python-format
msgid "Does your file have titles?"
-msgstr ""
+msgstr "A fájljának van címe?"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1289
#, python-format
msgid "Method:"
-msgstr ""
+msgstr "Módzser:"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_list.js:1417
#, python-format
msgid "%(page)d/%(page_count)d"
-msgstr ""
+msgstr "%(page)d/%(page_count)d"
#. module: web
#. openerp-web
@@ -250,14 +250,14 @@ msgstr "Feltöltés"
#: code:addons/web/static/src/xml/base.xml:126
#, python-format
msgid "Load Demonstration data:"
-msgstr ""
+msgstr "Bemutató adatok betöltése:"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:516
#, python-format
msgid "View"
-msgstr ""
+msgstr "Nézet"
#. module: web
#. openerp-web
@@ -271,7 +271,7 @@ msgstr "Keresés: "
#: code:addons/web/static/src/js/view_form.js:4857
#, python-format
msgid "Save As..."
-msgstr ""
+msgstr "Mentés másként..."
#. module: web
#. openerp-web
@@ -285,14 +285,14 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:217
#, python-format
msgid "Backup Database"
-msgstr ""
+msgstr "Adatbázis mentése"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:906
#, python-format
msgid "Size:"
-msgstr ""
+msgstr "Méret:"
#. module: web
#. openerp-web
@@ -359,7 +359,7 @@ msgstr ""
#: code:addons/web/static/src/js/coresetup.js:589
#, python-format
msgid "about a minute ago"
-msgstr ""
+msgstr "kb. egy perce"
#. module: web
#. openerp-web
@@ -373,7 +373,7 @@ msgstr ".CSV fájl importálása"
#: code:addons/web/static/src/xml/base.xml:257
#, python-format
msgid "File:"
-msgstr ""
+msgstr "Fájl:"
#. module: web
#. openerp-web
@@ -388,7 +388,7 @@ msgstr "-- Műveletek --"
#: code:addons/web/static/src/xml/base.xml:311
#, python-format
msgid "Restore"
-msgstr ""
+msgstr "Visszaállít"
#. module: web
#. openerp-web
@@ -404,14 +404,14 @@ msgstr "Duplikálás"
#: code:addons/web/static/src/js/formats.js:180
#, python-format
msgid "(%d records)"
-msgstr ""
+msgstr "(%d records)"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/search.js:1293
#, python-format
msgid "not a valid number"
-msgstr ""
+msgstr "nem érvényes szám"
#. module: web
#. openerp-web
@@ -440,7 +440,7 @@ msgstr "Figyelmeztetés"
#: code:addons/web/static/src/xml/base.xml:517
#, python-format
msgid "Edit SearchView"
-msgstr ""
+msgstr "Keresési nézet szerkesztése"
#. module: web
#. openerp-web
@@ -449,7 +449,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:261
#, python-format
msgid "Master Password:"
-msgstr ""
+msgstr "Mester Jelszó:"
#. module: web
#. openerp-web
@@ -495,7 +495,7 @@ msgstr "Részletes Keresés"
#: code:addons/web/static/src/xml/base.xml:1323
#, python-format
msgid "Select"
-msgstr ""
+msgstr "Kiválaszt"
#. module: web
#. openerp-web
@@ -558,7 +558,7 @@ msgstr "Létrehozás"
#: code:addons/web/static/src/xml/base.xml:186
#, python-format
msgid "Drop Database"
-msgstr ""
+msgstr "Adatbázis eldobása"
#. module: web
#. openerp-web
@@ -692,7 +692,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:146
#, python-format
msgid "Admin password:"
-msgstr ""
+msgstr "Adminisztrátor jelszó:"
#. module: web
#. openerp-web
@@ -713,14 +713,14 @@ msgstr "%d nappal ezelőtt"
#: code:addons/web/static/src/xml/base.xml:1802
#, python-format
msgid "Name:"
-msgstr ""
+msgstr "Név:"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1728
#, python-format
msgid "CSV File:"
-msgstr ""
+msgstr "CSV Fájl:"
#. module: web
#. openerp-web
@@ -734,7 +734,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:416
#, python-format
msgid "OpenERP.com"
-msgstr ""
+msgstr "OpenERP.com"
#. module: web
#. openerp-web
@@ -748,7 +748,7 @@ msgstr "Új jelszó:"
#: code:addons/web/static/src/xml/base.xml:1628
#, python-format
msgid "Import-Compatible Export"
-msgstr ""
+msgstr "Import-kompatibilis Export"
#. module: web
#. openerp-web
@@ -776,14 +776,14 @@ msgstr "Mindenkinek elérhető"
#: code:addons/web/static/src/js/coresetup.js:595
#, python-format
msgid "about a month ago"
-msgstr ""
+msgstr "kb. egy hónapja"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_tree.js:11
#, python-format
msgid "Tree"
-msgstr ""
+msgstr "Fa"
#. module: web
#. openerp-web
@@ -797,7 +797,7 @@ msgstr "Egyedi szűrők"
#: code:addons/web/static/src/xml/base.xml:287
#, python-format
msgid "New master password:"
-msgstr ""
+msgstr "Új mester jelszó:"
#. module: web
#. openerp-web
@@ -832,7 +832,7 @@ msgstr "Összes adat exportálása"
#: code:addons/web/static/src/xml/base.xml:414
#, python-format
msgid "OpenERP SA Company"
-msgstr ""
+msgstr "OpenERP SA Company"
#. module: web
#. openerp-web
@@ -853,7 +853,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1014
#, python-format
msgid "Select date"
-msgstr ""
+msgstr "Dátum választása"
#. module: web
#. openerp-web
@@ -883,7 +883,7 @@ msgstr "Törlés"
#: code:addons/web/static/src/xml/base.xml:160
#, python-format
msgid "Duplicate Database"
-msgstr ""
+msgstr "Adatbázis másolása"
#. module: web
#. openerp-web
@@ -912,7 +912,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:305
#, python-format
msgid "Database Management"
-msgstr ""
+msgstr "Adatbázis Kezelő"
#. module: web
#. openerp-web
@@ -940,7 +940,7 @@ msgstr "Részletes keresés…"
#: code:addons/web/static/src/xml/base.xml:172
#, python-format
msgid "Original database name:"
-msgstr ""
+msgstr "Eredeti adatbázis neve:"
#. module: web
#. openerp-web
@@ -1056,14 +1056,14 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:934
#, python-format
msgid "Selection:"
-msgstr ""
+msgstr "Kiválasztás:"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1773
#, python-format
msgid "--- Don't Import ---"
-msgstr ""
+msgstr "--- Ne importáljon ---"
#. module: web
#. openerp-web
@@ -1089,7 +1089,7 @@ msgstr "Új adatbázis neve:"
#: code:addons/web/static/src/xml/base.xml:1219
#, python-format
msgid "...Upload in progress..."
-msgstr ""
+msgstr "...Feltöltés folyamatban..."
#. module: web
#. openerp-web
@@ -1117,7 +1117,7 @@ msgstr ""
#: code:addons/web/static/src/js/coresetup.js:617
#, python-format
msgid "Still loading..."
-msgstr ""
+msgstr "Még tölt..."
#. module: web
#. openerp-web
@@ -1139,7 +1139,7 @@ msgstr ""
#: code:addons/web/static/src/js/view_form.js:4822
#, python-format
msgid "File upload"
-msgstr ""
+msgstr "Fájl feltöltés"
#. module: web
#. openerp-web
@@ -1160,7 +1160,7 @@ msgstr "Minden felhasználó"
#: code:addons/web/static/src/js/coresetup.js:598
#, python-format
msgid "%d years ago"
-msgstr ""
+msgstr "%d éve"
#. module: web
#. openerp-web
@@ -1188,7 +1188,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:408
#, python-format
msgid "Activate the developer mode"
-msgstr ""
+msgstr "Fejlesztői nézet aktiválása"
#. module: web
#. openerp-web
@@ -1230,14 +1230,14 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1554
#, python-format
msgid "Filter name"
-msgstr ""
+msgstr "Szűrő neve"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_form.js:4821
#, python-format
msgid "The selected file exceed the maximum file size of %s."
-msgstr ""
+msgstr "A kiválasztott fájl meghaladja a maximális %s fájlméretet."
#. module: web
#. openerp-web
@@ -1258,7 +1258,7 @@ msgstr ""
#: code:addons/web/static/src/js/view_list.js:683
#, python-format
msgid "You must select at least one record."
-msgstr ""
+msgstr "Legalább egy a rekordot válasszon ki."
#. module: web
#. openerp-web
@@ -1307,7 +1307,7 @@ msgstr ""
#: code:addons/web/static/src/js/coresetup.js:588
#, python-format
msgid "less than a minute ago"
-msgstr ""
+msgstr "kevesebb, mint egy perce"
#. module: web
#. openerp-web
@@ -1328,7 +1328,7 @@ msgstr ""
#: code:addons/web/static/src/js/search.js:977
#, python-format
msgid "Filter"
-msgstr ""
+msgstr "Szűrő"
#. module: web
#. openerp-web
@@ -1378,7 +1378,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:310
#, python-format
msgid "Backup"
-msgstr ""
+msgstr "Biztonsági mentés"
#. module: web
#. openerp-web
@@ -1399,7 +1399,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:525
#, python-format
msgid "ID:"
-msgstr ""
+msgstr "ID:"
#. module: web
#. openerp-web
@@ -1511,7 +1511,7 @@ msgstr "Biztos, hogy törölni akarja ezeket a rekordokat?"
#: code:addons/web/static/src/xml/base.xml:890
#, python-format
msgid "Field:"
-msgstr ""
+msgstr "Mező:"
#. module: web
#. openerp-web
@@ -1579,7 +1579,7 @@ msgstr "Kliens hiba"
#: code:addons/web/static/src/xml/base.xml:914
#, python-format
msgid "Domain:"
-msgstr ""
+msgstr "Domain:"
#. module: web
#. openerp-web
@@ -1593,7 +1593,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1753
#, python-format
msgid "UTF-8"
-msgstr ""
+msgstr "UTF-8"
#. module: web
#. openerp-web
@@ -1655,7 +1655,7 @@ msgstr "Importálási beállítások"
#: code:addons/web/static/src/xml/base.xml:437
#, python-format
msgid "OpenERP"
-msgstr ""
+msgstr "OpenERP"
#. module: web
#. openerp-web
@@ -1698,7 +1698,7 @@ msgstr "Feltöltés..."
#: code:addons/web/static/src/js/view_form.js:426
#, python-format
msgid "%d / %d"
-msgstr ""
+msgstr "%d / %d"
#. module: web
#. openerp-web
@@ -1862,7 +1862,7 @@ msgstr "Elvetés"
#: code:addons/web/static/src/xml/base.xml:1226
#, python-format
msgid "Delete this file"
-msgstr ""
+msgstr "Törölje ezt a fájlt"
#. module: web
#. openerp-web
@@ -1901,7 +1901,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:110
#, python-format
msgid "Create Database"
-msgstr ""
+msgstr "Adatbázis létrehozása"
#. module: web
#. openerp-web
@@ -1922,7 +1922,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1744
#, python-format
msgid "Separator:"
-msgstr ""
+msgstr "Elválasztó:"
#. module: web
#. openerp-web
@@ -1961,7 +1961,7 @@ msgstr "Jelszó megerősítése:"
#: code:addons/web/static/src/xml/base.xml:1579
#, python-format
msgid "or"
-msgstr ""
+msgstr "vagy"
#. module: web
#. openerp-web
@@ -1999,7 +1999,7 @@ msgstr "Szűrő neve:"
#: code:addons/web/static/src/xml/base.xml:1614
#, python-format
msgid "Export"
-msgstr ""
+msgstr "Export"
#. module: web
#. openerp-web
@@ -2013,7 +2013,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1246
#, python-format
msgid "File"
-msgstr ""
+msgstr "Fájl"
#. module: web
#. openerp-web
diff --git a/addons/web/i18n/it.po b/addons/web/i18n/it.po
index 9d07bb39547..83041e53b7f 100644
--- a/addons/web/i18n/it.po
+++ b/addons/web/i18n/it.po
@@ -8,35 +8,35 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-11-24 01:23+0000\n"
-"PO-Revision-Date: 2012-10-22 14:36+0000\n"
-"Last-Translator: Marius Marolla <mariusmarolla@areablu.net>\n"
+"PO-Revision-Date: 2012-11-28 20:09+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-11-25 06:41+0000\n"
-"X-Generator: Launchpad (build 16293)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:249
#, python-format
msgid "Restore Database"
-msgstr ""
+msgstr "Ripristina Database"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:134
#, python-format
msgid "Default language:"
-msgstr "Lingua predefinita:"
+msgstr "Lingua Predefinita:"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/chrome.js:326
#, python-format
msgid "Loading"
-msgstr ""
+msgstr "Caricamento"
#. module: web
#. openerp-web
@@ -71,7 +71,7 @@ msgstr ""
#: code:addons/web/static/src/js/search.js:1834
#, python-format
msgid "%(field)s %(operator)s \"%(value)s\""
-msgstr ""
+msgstr "%(field)s %(operator)s \"%(value)s\""
#. module: web
#. openerp-web
@@ -124,7 +124,7 @@ msgstr "Pulsante"
#: code:addons/web/static/src/xml/base.xml:275
#, python-format
msgid "Change Master Password"
-msgstr ""
+msgstr "Cambia Master Password"
#. module: web
#. openerp-web
@@ -243,7 +243,7 @@ msgstr "Ok"
#: code:addons/web/static/src/js/views.js:1127
#, python-format
msgid "Uploading..."
-msgstr ""
+msgstr "Caricando..."
#. module: web
#. openerp-web
@@ -271,21 +271,21 @@ msgstr "Cerca: "
#: code:addons/web/static/src/js/view_form.js:4857
#, python-format
msgid "Save As..."
-msgstr ""
+msgstr "Salva come..."
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_form.js:4982
#, python-format
msgid "Could not display the selected image."
-msgstr ""
+msgstr "Impossibile visualizzare l'immagine selezionata."
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:217
#, python-format
msgid "Backup Database"
-msgstr ""
+msgstr "Backup Database"
#. module: web
#. openerp-web
@@ -340,7 +340,7 @@ msgstr "Traduzione tecnica"
#: code:addons/web/static/src/xml/base.xml:1552
#, python-format
msgid "Save current filter"
-msgstr ""
+msgstr "Salva il filtro corrente"
#. module: web
#. openerp-web
@@ -406,7 +406,7 @@ msgstr "Duplica"
#: code:addons/web/static/src/js/formats.js:180
#, python-format
msgid "(%d records)"
-msgstr ""
+msgstr "(%d records)"
#. module: web
#. openerp-web
diff --git a/addons/web/i18n/zh_CN.po b/addons/web/i18n/zh_CN.po
index 045e21d475a..cb3db00cb00 100644
--- a/addons/web/i18n/zh_CN.po
+++ b/addons/web/i18n/zh_CN.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-11-24 01:23+0000\n"
-"PO-Revision-Date: 2012-11-26 02:16+0000\n"
-"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
+"PO-Revision-Date: 2012-11-29 02:05+0000\n"
+"Last-Translator: digitalsatori <digisatori@gmail.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-11-27 05:45+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web
#. openerp-web
@@ -159,7 +159,7 @@ msgstr "删除"
#: code:addons/web/static/src/js/search.js:1394
#, python-format
msgid "Search %(field)s at: %(value)s"
-msgstr ""
+msgstr "搜索 %(field)s : %(value)s"
#. module: web
#. openerp-web
@@ -425,7 +425,7 @@ msgstr "修改默认值:"
#: code:addons/web/static/src/xml/base.xml:506
#, python-format
msgid "Toggle Form Layout Outline"
-msgstr ""
+msgstr "切换表单布局视图"
#. module: web
#. openerp-web
@@ -604,14 +604,14 @@ msgstr "最后修改时间:"
#: code:addons/web/static/src/js/search.js:1455
#, python-format
msgid "M2O search fields do not currently handle multiple default values"
-msgstr ""
+msgstr "多对一搜索字段目前不支持多个默认值"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:581
#, python-format
msgid "Delete this attachment"
-msgstr ""
+msgstr "删除这个附件"
#. module: web
#. openerp-web
@@ -640,7 +640,7 @@ msgstr "不限"
#: code:addons/web/static/src/xml/base.xml:350
#, python-format
msgid "More"
-msgstr ""
+msgstr "更多"
#. module: web
#. openerp-web
@@ -654,7 +654,7 @@ msgstr "用户名"
#: code:addons/web/static/src/js/view_form.js:2912
#, python-format
msgid "Add %s"
-msgstr ""
+msgstr "增加 %s"
#. module: web
#. openerp-web
@@ -715,7 +715,7 @@ msgstr "%d 天前"
#: code:addons/web/static/src/xml/base.xml:1802
#, python-format
msgid "Name:"
-msgstr ""
+msgstr "名称:"
#. module: web
#. openerp-web
@@ -729,7 +729,7 @@ msgstr "CSV 文件:"
#: code:addons/web/static/src/js/search.js:1743
#, python-format
msgid "Advanced"
-msgstr ""
+msgstr "高级选项"
#. module: web
#. openerp-web
@@ -750,7 +750,7 @@ msgstr "新密码:"
#: code:addons/web/static/src/xml/base.xml:1628
#, python-format
msgid "Import-Compatible Export"
-msgstr ""
+msgstr "兼容导入格式导出"
#. module: web
#. openerp-web
@@ -771,7 +771,7 @@ msgstr "请选择要导出的字段..."
#: code:addons/web/static/src/xml/base.xml:1557
#, python-format
msgid "Share with all users"
-msgstr ""
+msgstr "与所有用户共享"
#. module: web
#. openerp-web
@@ -792,7 +792,7 @@ msgstr "树形"
#: code:addons/web/static/src/xml/base.xml:1549
#, python-format
msgid "Custom Filters"
-msgstr ""
+msgstr "自定义过滤"
#. module: web
#. openerp-web
@@ -841,14 +841,14 @@ msgstr "OpenERP SA 公司"
#: code:addons/web/static/src/js/views.js:1137
#, python-format
msgid "Do you really want to delete this attachment ?"
-msgstr ""
+msgstr "请确认是否要删除这个附件?"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_list.js:1018
#, python-format
msgid "Unknown m2m command "
-msgstr ""
+msgstr "未知的m2m命令 "
#. module: web
#. openerp-web
@@ -862,14 +862,14 @@ msgstr "选择日期"
#: code:addons/web/static/src/js/search.js:1555
#, python-format
msgid "Custom Filter"
-msgstr ""
+msgstr "自定义筛选"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:399
#, python-format
msgid "About OpenERP"
-msgstr ""
+msgstr "关于 OpenERP"
#. module: web
#. openerp-web
@@ -885,7 +885,7 @@ msgstr "删除"
#: code:addons/web/static/src/xml/base.xml:160
#, python-format
msgid "Duplicate Database"
-msgstr ""
+msgstr "复制数据库"
#. module: web
#. openerp-web
@@ -914,14 +914,14 @@ msgstr "字段值有误。%(fieldname)s: [%(value)s] : %(message)s"
#: code:addons/web/static/src/xml/base.xml:305
#, python-format
msgid "Database Management"
-msgstr ""
+msgstr "数据库管理"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_form.js:4982
#, python-format
msgid "Image"
-msgstr ""
+msgstr "图像"
#. module: web
#. openerp-web
@@ -935,14 +935,14 @@ msgstr "管理数据库"
#: code:addons/web/static/src/xml/base.xml:1377
#, python-format
msgid "Advanced Search..."
-msgstr ""
+msgstr "高级搜索..."
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:172
#, python-format
msgid "Original database name:"
-msgstr ""
+msgstr "原来的数据库名"
#. module: web
#. openerp-web
@@ -1037,7 +1037,7 @@ msgstr "否"
#: code:addons/web/static/src/js/view_form.js:3779
#, python-format
msgid "The o2m record must be saved before an action can be used"
-msgstr ""
+msgstr "o2m记录必须先保存才能被一个动作使用"
#. module: web
#. openerp-web
@@ -1065,7 +1065,7 @@ msgstr "下拉列表:"
#: code:addons/web/static/src/xml/base.xml:1773
#, python-format
msgid "--- Don't Import ---"
-msgstr ""
+msgstr "--- 无法导入 ---"
#. module: web
#. openerp-web
@@ -1091,7 +1091,7 @@ msgstr "新数据库名称:"
#: code:addons/web/static/src/xml/base.xml:1219
#, python-format
msgid "...Upload in progress..."
-msgstr ""
+msgstr "... 上传中"
#. module: web
#. openerp-web
@@ -1112,14 +1112,14 @@ msgstr "创建日期:"
#: code:addons/web/static/src/xml/base.xml:1573
#, python-format
msgid "Add a condition"
-msgstr ""
+msgstr "添加条件"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/coresetup.js:617
#, python-format
msgid "Still loading..."
-msgstr ""
+msgstr "正在加载..."
#. module: web
#. openerp-web
@@ -1139,14 +1139,14 @@ msgstr ""
#: code:addons/web/static/src/js/view_form.js:4822
#, python-format
msgid "File upload"
-msgstr ""
+msgstr "文件上传"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_form.js:3778
#, python-format
msgid "Action Button"
-msgstr ""
+msgstr "动作按钮"
#. module: web
#. openerp-web
@@ -1160,7 +1160,7 @@ msgstr "所有用户"
#: code:addons/web/static/src/js/coresetup.js:598
#, python-format
msgid "%d years ago"
-msgstr ""
+msgstr "%d 年前"
#. module: web
#. openerp-web
@@ -1182,13 +1182,15 @@ msgstr "保存默认值"
#, python-format
msgid "Take a minute to get a coffee,<br />because it's loading..."
msgstr ""
+"稍安勿躁, <br />\r\n"
+"大菜烹饪中 : )"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:408
#, python-format
msgid "Activate the developer mode"
-msgstr ""
+msgstr "激活开发者模式"
#. module: web
#. openerp-web
@@ -1202,7 +1204,7 @@ msgstr "正在加载(%d)"
#: code:addons/web/static/src/js/view_form.js:4857
#, python-format
msgid "The field is empty, there's nothing to save !"
-msgstr ""
+msgstr "字段为空,没有需要保存的值!"
#. module: web
#. openerp-web
@@ -1223,28 +1225,28 @@ msgstr "已备份"
#: code:addons/web/static/src/xml/base.xml:1559
#, python-format
msgid "Use by default"
-msgstr ""
+msgstr "默认使用"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1554
#, python-format
msgid "Filter name"
-msgstr ""
+msgstr "过滤规则名称"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/view_form.js:4821
#, python-format
msgid "The selected file exceed the maximum file size of %s."
-msgstr ""
+msgstr "所选文件超出了文件的最大值设定:%s"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/search.js:1110
#, python-format
msgid "GroupBy"
-msgstr ""
+msgstr "分组"
#. module: web
#. openerp-web
@@ -1258,7 +1260,7 @@ msgstr "编码:"
#: code:addons/web/static/src/js/view_list.js:683
#, python-format
msgid "You must select at least one record."
-msgstr ""
+msgstr "请至少选择一条记录"
#. module: web
#. openerp-web
@@ -1272,7 +1274,7 @@ msgstr "要跳过的行数"
#: code:addons/web/static/src/js/view_form.js:2834
#, python-format
msgid "Create \"<strong>%s</strong>\""
-msgstr ""
+msgstr "创建 \"<strong>%s</strong>\""
#. module: web
#. openerp-web
@@ -1328,7 +1330,7 @@ msgstr "版权所有 © 2004-TODAY OpenERP SA。保留所有权利。"
#: code:addons/web/static/src/js/search.js:977
#, python-format
msgid "Filter"
-msgstr ""
+msgstr "过滤条件"
#. module: web
#. openerp-web
@@ -1385,7 +1387,7 @@ msgstr "备份"
#: code:addons/web/static/src/xml/base.xml:509
#, python-format
msgid "JS Tests"
-msgstr ""
+msgstr "JS 测试"
#. module: web
#. openerp-web
diff --git a/addons/web/nonliterals.py b/addons/web/nonliterals.py
deleted file mode 100644
index 4ee6d122f44..00000000000
--- a/addons/web/nonliterals.py
+++ /dev/null
@@ -1,267 +0,0 @@
-# -*- coding: utf-8 -*-
-""" Manages the storage and lifecycle of non-literal domains and contexts
-(and potentially other structures) which have to be evaluated with client data,
-but still need to be safely round-tripped to and from the browser (and thus
-can't be sent there themselves).
-"""
-import binascii
-import hashlib
-import simplejson.encoder
-
-__all__ = ['Domain', 'Context', 'NonLiteralEncoder', 'non_literal_decoder', 'CompoundDomain', 'CompoundContext']
-
-#: 48 bits should be sufficient to have almost no chance of collision
-#: with a million hashes, according to hg@67081329d49a
-SHORT_HASH_BYTES_SIZE = 6
-
-class NonLiteralEncoder(simplejson.encoder.JSONEncoder):
- def default(self, object):
- if not isinstance(object, (BaseDomain, BaseContext)):
- return super(NonLiteralEncoder, self).default(object)
- if isinstance(object, Domain):
- return {
- '__ref': 'domain',
- '__id': object.key,
- '__debug': object.get_domain_string()
- }
- elif isinstance(object, Context):
- return {
- '__ref': 'context',
- '__id': object.key,
- '__debug': object.get_context_string()
- }
- elif isinstance(object, CompoundDomain):
- return {
- '__ref': 'compound_domain',
- '__domains': object.domains,
- '__eval_context': object.get_eval_context()
- }
- elif isinstance(object, CompoundContext):
- return {
- '__ref': 'compound_context',
- '__contexts': object.contexts,
- '__eval_context': object.get_eval_context()
- }
- raise TypeError('Could not encode unknown non-literal %s' % object)
-
-_ALLOWED_KEYS = frozenset(['__ref', "__id", '__domains', '__debug',
- '__contexts', '__eval_context'])
-
-def non_literal_decoder(dct):
- """ Decodes JSON dicts into :class:`Domain` and :class:`Context` based on
- magic attribute tags.
- """
- if '__ref' in dct:
- for x in dct:
- if x not in _ALLOWED_KEYS:
- raise ValueError("'%s' key not allowed in non literal domain/context" % x)
- if dct['__ref'] == 'domain':
- return Domain(None, key=dct['__id'])
- elif dct['__ref'] == 'context':
- return Context(None, key=dct['__id'])
- elif dct["__ref"] == "compound_domain":
- cdomain = CompoundDomain()
- for el in dct["__domains"]:
- cdomain.domains.append(el)
- cdomain.set_eval_context(dct.get("__eval_context"))
- return cdomain
- elif dct["__ref"] == "compound_context":
- ccontext = CompoundContext()
- for el in dct["__contexts"]:
- ccontext.contexts.append(el)
- ccontext.set_eval_context(dct.get("__eval_context"))
- return ccontext
- return dct
-
-# TODO: use abstract base classes if 2.6+?
-class BaseDomain(object):
- def evaluate(self, context=None):
- raise NotImplementedError('Non literals must implement evaluate()')
-
-class BaseContext(object):
- def evaluate(self, context=None):
- raise NotImplementedError('Non literals must implement evaluate()')
-
-class Domain(BaseDomain):
- def __init__(self, session, domain_string=None, key=None):
- """ Uses session information to store the domain string and map it to a
- domain key, which can be safely round-tripped to the client.
-
- If initialized with a domain string, will generate a key for that
- string and store the domain string out of the way. When initialized
- with a key, considers this key is a reference to an existing domain
- string.
-
- :param session: the OpenERP Session to use when evaluating the domain
- :type session: web.common.session.OpenERPSession
- :param str domain_string: a non-literal domain in string form
- :param str key: key used to retrieve the domain string
- """
- if domain_string and key:
- raise ValueError("A nonliteral domain can not take both a key "
- "and a domain string")
-
- self.session = session
- if domain_string:
- self.key = binascii.hexlify(
- hashlib.sha256(domain_string).digest()[:SHORT_HASH_BYTES_SIZE])
- self.session.domains_store[self.key] = domain_string
- elif key:
- self.key = key
-
- def get_domain_string(self):
- """ Retrieves the domain string linked to this non-literal domain in
- the provided session.
- """
- return self.session.domains_store[self.key]
-
- def evaluate(self, context=None):
- """ Forces the evaluation of the linked domain, using the provided
- context (as well as the session's base context), and returns the
- evaluated result.
- """
- ctx = self.session.evaluation_context(context)
-
- try:
- return eval(self.get_domain_string(), SuperDict(ctx))
- except NameError as e:
- raise ValueError('Error during evaluation of this domain: "%s", message: "%s"' % (self.get_domain_string(), e.message))
-
-class Context(BaseContext):
- def __init__(self, session, context_string=None, key=None):
- """ Uses session information to store the context string and map it to
- a key (stored in a secret location under a secret mountain), which can
- be safely round-tripped to the client.
-
- If initialized with a context string, will generate a key for that
- string and store the context string out of the way. When initialized
- with a key, considers this key is a reference to an existing context
- string.
-
- :param session: the OpenERP Session to use when evaluating the context
- :type session: web.common.session.OpenERPSession
- :param str context_string: a non-literal context in string form
- :param str key: key used to retrieve the context string
- """
- if context_string and key:
- raise ValueError("A nonliteral domain can not take both a key "
- "and a domain string")
-
- self.session = session
-
- if context_string:
- self.key = binascii.hexlify(
- hashlib.sha256(context_string).digest()[:SHORT_HASH_BYTES_SIZE])
- self.session.contexts_store[self.key] = context_string
- elif key:
- self.key = key
-
- def get_context_string(self):
- """ Retrieves the context string linked to this non-literal context in
- the provided session.
- """
- return self.session.contexts_store[self.key]
-
- def evaluate(self, context=None):
- """ Forces the evaluation of the linked context, using the provided
- context (as well as the session's base context), and returns the
- evaluated result.
- """
- ctx = self.session.evaluation_context(context)
-
- try:
- return eval(self.get_context_string(), SuperDict(ctx))
- except NameError as e:
- raise ValueError('Error during evaluation of this context: "%s", message: "%s"' % (self.get_context_string(), e.message))
-
-class SuperDict(dict):
- def __getattr__(self, name):
- try:
- return self[name]
- except KeyError:
- raise AttributeError(name)
- def __getitem__(self, key):
- tmp = super(SuperDict, self).__getitem__(key)
- if isinstance(tmp, dict):
- return SuperDict(tmp)
- return tmp
-
-class CompoundDomain(BaseDomain):
- def __init__(self, *domains):
- self.domains = []
- self.session = None
- self.eval_context = None
- for domain in domains:
- self.add(domain)
-
- def evaluate(self, context=None):
- ctx = dict(context or {})
- eval_context = self.get_eval_context()
- if eval_context:
- eval_context = self.session.eval_context(eval_context)
- ctx.update(eval_context)
- final_domain = []
- for domain in self.domains:
- if not isinstance(domain, (list, BaseDomain)):
- raise TypeError(
- "Domain %r is not a list or a nonliteral Domain" % domain)
-
- if isinstance(domain, list):
- final_domain.extend(domain)
- continue
-
- domain.session = self.session
- final_domain.extend(domain.evaluate(ctx))
- return final_domain
-
- def add(self, domain):
- self.domains.append(domain)
- return self
-
- def set_eval_context(self, eval_context):
- self.eval_context = eval_context
- return self
-
- def get_eval_context(self):
- return self.eval_context
-
-class CompoundContext(BaseContext):
- def __init__(self, *contexts):
- self.contexts = []
- self.eval_context = None
- self.session = None
- for context in contexts:
- self.add(context)
-
- def evaluate(self, context=None):
- ctx = dict(context or {})
- eval_context = self.get_eval_context()
- if eval_context:
- eval_context = self.session.eval_context(eval_context)
- ctx.update(eval_context)
- final_context = {}
- for context_to_eval in self.contexts:
- if not isinstance(context_to_eval, (dict, BaseContext)):
- raise TypeError(
- "Context %r is not a dict or a nonliteral Context" % context_to_eval)
-
- if isinstance(context_to_eval, dict):
- final_context.update(context_to_eval)
- continue
-
- ctx.update(final_context)
-
- context_to_eval.session = self.session
- final_context.update(context_to_eval.evaluate(ctx))
- return final_context
-
- def add(self, context):
- self.contexts.append(context)
- return self
-
- def set_eval_context(self, eval_context):
- self.eval_context = eval_context
- return self
-
- def get_eval_context(self):
- return self.eval_context
diff --git a/addons/web/session.py b/addons/web/session.py
index e57d3da5b45..523aaa9f1fa 100644
--- a/addons/web/session.py
+++ b/addons/web/session.py
@@ -10,8 +10,6 @@ import xmlrpclib
import openerp
-import nonliterals
-
_logger = logging.getLogger(__name__)
#----------------------------------------------------------
@@ -81,8 +79,6 @@ class OpenERPSession(object):
self._password = False
self._suicide = False
self.context = {}
- self.contexts_store = {}
- self.domains_store = {}
self.jsonp_requests = {} # FIXME use a LRU
def send(self, service_name, method, *args):
@@ -222,49 +218,4 @@ class OpenERPSession(object):
d['context'] = d
return d
- def eval_context(self, context_to_eval, context=None):
- """ Evaluates the provided context_to_eval in the context (haha) of
- the context. Also merges the evaluated context with the session's context.
-
- :param context_to_eval: a context to evaluate. Must be a dict or a
- non-literal context. If it's a dict, will be
- returned as-is
- :type context_to_eval: openerpweb.nonliterals.Context
- :returns: the evaluated context
- :rtype: dict
-
- :raises: ``TypeError`` if ``context_to_eval`` is neither a dict nor
- a Context
- """
- ctx = dict(
- self.base_eval_context,
- **(context or {}))
-
- # adding the context of the session to send to the openerp server
- ccontext = nonliterals.CompoundContext(self.context, context_to_eval or {})
- ccontext.session = self
- return ccontext.evaluate(ctx)
-
- def eval_domain(self, domain, context=None):
- """ Evaluates the provided domain using the provided context
- (merged with the session's evaluation context)
-
- :param domain: an OpenERP domain as a list or as a
- :class:`openerpweb.nonliterals.Domain` instance
-
- In the second case, it will be evaluated and returned.
- :type domain: openerpweb.nonliterals.Domain
- :param dict context: the context to use in the evaluation, if any.
- :returns: the evaluated domain
- :rtype: list
-
- :raises: ``TypeError`` if ``domain`` is neither a list nor a Domain
- """
- if isinstance(domain, list):
- return domain
-
- cdomain = nonliterals.CompoundDomain(domain)
- cdomain.session = self
- return cdomain.evaluate(context or {})
-
# vim:et:ts=4:sw=4:
diff --git a/addons/web/static/lib/py.js/.hg_archival.txt b/addons/web/static/lib/py.js/.hg_archival.txt
index 35e98c8501d..33dcdd88830 100644
--- a/addons/web/static/lib/py.js/.hg_archival.txt
+++ b/addons/web/static/lib/py.js/.hg_archival.txt
@@ -1,5 +1,5 @@
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
-node: 1758bfec1ec1dcff95dcc4c72269cc0e3d000afd
+node: ccf62e5d0291f66a39f4049f3e880004e59c3654
branch: default
-latesttag: 0.5
-latesttagdistance: 11
+latesttag: 0.7
+latesttagdistance: 4
diff --git a/addons/web/static/lib/py.js/README.rst b/addons/web/static/lib/py.js/README.rst
index 046db7153d1..7601318df6d 100644
--- a/addons/web/static/lib/py.js/README.rst
+++ b/addons/web/static/lib/py.js/README.rst
@@ -1,14 +1,7 @@
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
------
@@ -60,7 +53,7 @@ Builtins
Same as tuple (``list`` is currently an alias for ``tuple``)
``dict``
- Implements just about nothing
+ Implements trivial getting and setting, nothing beyond that.
Note that most methods are probably missing from all of these.
@@ -69,17 +62,17 @@ 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``.
+ Pretty much complete (including operator fallbacks), although the
+ behavior is currently undefined if an operation does not return
+ either a ``py.bool`` or ``NotImplemented``.
- 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.
+ ``__hash__`` is supported (and used), but it should return **a
+ javascript string**. ``py.js``'s dict build on javascript objects,
+ reimplementing numeral hashing is worthless complexity at this
+ point.
Boolean conversion
Implementing ``__nonzero__`` should work.
@@ -93,6 +86,12 @@ Descriptor protocol
As with attributes, ``__delete__`` is not implemented.
Callable objects
+ Work, although the handling of arguments isn't exactly nailed
+ down. For now, callables get two (javascript) arguments ``args``
+ and ``kwargs``, holding (respectively) positional and keyword
+ arguments.
+
+ Conflicts are *not* handled at this point.
Collections Abstract Base Classes
Container is the only implemented ABC protocol (ABCs themselves
@@ -119,8 +118,8 @@ implementation:
``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.
+ these in a variant of ``py.def``, to behave as Python's (bound)
+ methods.
Why
===
diff --git a/addons/web/static/lib/py.js/doc/Makefile b/addons/web/static/lib/py.js/doc/Makefile
new file mode 100644
index 00000000000..cb7d658e796
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyjs.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyjs.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/pyjs"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyjs"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/addons/web/static/lib/py.js/doc/builtins.rst b/addons/web/static/lib/py.js/doc/builtins.rst
new file mode 100644
index 00000000000..6958d83b884
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/builtins.rst
@@ -0,0 +1,55 @@
+.. default-domain: python
+
+.. _builtins:
+
+Supported Python builtins
+=========================
+
+.. function:: py.type(object)
+
+ Gets the class of a provided object, if possible.
+
+ .. note:: currently doesn't work correctly when called on a class
+ object, will return the class itself (also, classes
+ don't currently have a type).
+
+.. js:function:: py.type(name, bases, dict)
+
+ Not exactly a builtin as this form is solely javascript-level
+ (currently). Used to create new ``py.js`` types. See :doc:`types`
+ for its usage.
+
+.. data:: py.None
+
+.. data:: py.True
+
+.. data:: py.False
+
+.. data:: py.NotImplemented
+
+.. class:: py.object
+
+ Base class for all types, even implicitly (if no bases are
+ provided to :js:func:`py.type`)
+
+.. class:: py.bool([object])
+
+.. class:: py.float([object])
+
+.. class:: py.str([object])
+
+.. class:: py.unicode([object])
+
+.. class:: py.tuple()
+
+.. class:: py.list()
+
+.. class:: py.dict()
+
+.. function:: py.len(object)
+
+.. function:: py.isinstance(object, type)
+
+.. function:: py.issubclass(type, other_type)
+
+.. class:: py.classmethod
diff --git a/addons/web/static/lib/py.js/doc/conf.py b/addons/web/static/lib/py.js/doc/conf.py
new file mode 100644
index 00000000000..287f21af577
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/conf.py
@@ -0,0 +1,247 @@
+# -*- coding: utf-8 -*-
+#
+# py.js documentation build configuration file, created by
+# sphinx-quickstart on Sun Sep 9 19:36:23 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.todo']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'py.js'
+copyright = u'2012, Xavier Morel'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.6'
+# The full version, including alpha/beta/rc tags.
+release = '0.6'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# Default sphinx domain
+default_domain = 'js'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+# default code-block highlighting
+highlight_language = 'javascript'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pyjsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'pyjs.tex', u'py.js Documentation',
+ u'Xavier Morel', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'pyjs', u'py.js Documentation',
+ [u'Xavier Morel'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'pyjs', u'py.js Documentation',
+ u'Xavier Morel', 'pyjs', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/addons/web/static/lib/py.js/doc/differences.rst b/addons/web/static/lib/py.js/doc/differences.rst
new file mode 100644
index 00000000000..d81acd240a2
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/differences.rst
@@ -0,0 +1,64 @@
+Differences with Python
+=======================
+
+* ``py.js`` completely ignores old-style classes as well as their
+ lookup details. All ``py.js`` types should be considered matching
+ the behavior of new-style classes
+
+* New types can only have a single base. This is due to ``py.js``
+ implementing its types on top of Javascript's, and javascript being
+ a single-inheritance language.
+
+ This may change if ``py.js`` ever reimplements its object model from
+ scratch.
+
+* Piggybacking on javascript's object model also means metaclasses are
+ not available (:js:func:`py.type` is a function)
+
+* A python-level function (created through :js:class:`py.PY_def`) set
+ on a new type will not become a method, it'll remain a function.
+
+* :js:func:`py.PY_parseArgs` supports keyword-only arguments (though
+ it's a Python 3 feature)
+
+* Because the underlying type is a javascript ``String``, there
+ currently is no difference between :js:class:`py.str` and
+ :js:class:`py.unicode`. As a result, there also is no difference
+ between :js:func:`__str__` and :js:func:`__unicode__`.
+
+Unsupported features
+--------------------
+
+These are Python features which are not supported at all in ``py.js``,
+usually because they don't make sense or there is no way to support them
+
+* The ``__delattr__``, ``__delete__`` and ``__delitem__``: as
+ ``py.js`` only handles expressions and these are accessed via the
+ ``del`` statement, there would be no way to call them.
+
+* ``__del__`` the lack of cross-platform GC hook means there is no way
+ to know when an object is deallocated.
+
+* ``__slots__`` are not handled
+
+* Dedicated (and deprecated) slicing special methods are unsupported
+
+Missing features
+----------------
+
+These are Python features which are missing because they haven't been
+implemented yet:
+
+* Class-binding of descriptors doesn't currently work.
+
+* Instance and subclass checks can't be customized
+
+* "poor" comparison methods (``__cmp__`` and ``__rcmp__``) are not
+ supported and won't be falled-back to.
+
+* ``__coerce__`` is currently supported
+
+* Context managers are not currently supported
+
+* Unbound methods are not supported, instance methods can only be
+ accessed from instances.
diff --git a/addons/web/static/lib/py.js/doc/index.rst b/addons/web/static/lib/py.js/doc/index.rst
new file mode 100644
index 00000000000..1b9167529b8
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/index.rst
@@ -0,0 +1,161 @@
+.. py.js documentation master file, created by
+ sphinx-quickstart on Sun Sep 9 19:36:23 2012.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+py.js, a Python expressions parser and evaluator
+================================================
+
+``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, its
+specification document is the `Python 2.7 Expressions spec
+<http://docs.python.org/reference/expressions.html>`_ (along with the
+lexical analysis part) as well as the Python builtins.
+
+
+.. toctree::
+ :maxdepth: 2
+
+ builtins
+ types
+ utility
+ differences
+
+Usage
+-----
+
+To evaluate a Python expression, simply call
+:func:`py.eval`. :func:`py.eval` takes a mandatory Python expression
+parameter, as a string, and an optional evaluation context (namespace
+for the expression's free variables), and returns a javascript value::
+
+ > py.eval("t in ('a', 'b', 'c') and foo", {t: 'c', foo: true});
+ true
+
+If the expression needs to be repeatedly evaluated, or the result of
+the expression is needed in its "python" form without being converted
+back to javascript, you can use the underlying triplet of functions
+:func:`py.tokenize`, :func:`py.parse` and :func:`py.evaluate`
+directly.
+
+API
+---
+
+Core functions
+++++++++++++++
+
+.. function:: py.eval(expr[, context])
+
+ "Do everything" function, to use for one-shot evaluation of Python
+ expressions. Chains tokenizing, parsing and evaluating the
+ expression then :ref:`converts the result back to javascript
+ <convert-js>`
+
+ :param expr: Python expression to evaluate
+ :type expr: String
+ :param context: evaluation context for the expression's free
+ variables
+ :type context: Object
+ :returns: the expression's result, converted back to javascript
+
+.. function:: py.tokenize(expr)
+
+ Expression tokenizer
+
+ :param expr: Python expression to tokenize
+ :type expr: String
+ :returns: token stream
+
+.. function:: py.parse(tokens)
+
+ Parses a token stream and returns the corresponding parse tree.
+
+ The parse tree is stateless and can be memoized and reused for
+ frequently evaluated expressions.
+
+ :param tokens: token stream from :func:`py.tokenize`
+ :returns: parse tree
+
+.. function:: py.evaluate(tree[, context])
+
+ Evaluates the expression represented by the provided parse tree,
+ using the provided context for the exprssion's free variables.
+
+ :param tree: parse tree returned by :func:`py.parse`
+ :param context: evaluation context
+ :returns: the "python object" resulting from the expression's
+ evaluation
+ :rtype: :class:`py.object`
+
+.. _convert-py:
+
+Conversions from Javascript to Python
++++++++++++++++++++++++++++++++++++++
+
+``py.js`` will automatically attempt to convert non-:class:`py.object`
+values into their ``py.js`` equivalent in the following situations:
+
+* Values passed through the context of :func:`py.eval` or
+ :func:`py.evaluate`
+
+* Attributes accessed directly on objects
+
+* Values of mappings passed to :class:`py.dict`
+
+Notably, ``py.js`` will *not* attempt an automatic conversion of
+values returned by functions or methods, these must be
+:class:`py.object` instances.
+
+The automatic conversions performed by ``py.js`` are the following:
+
+* ``null`` is converted to :data:`py.None`
+
+* ``true`` is converted to :data:`py.True`
+
+* ``false`` is converted to :data:`py.False`
+
+* numbers are converted to :class:`py.float`
+
+* strings are converted to :class:`py.str`
+
+* functions are wrapped into :class:`py.PY_dev`
+
+* ``Array`` instances are converted to :class:`py.list`
+
+The rest generates an error, except for ``undefined`` which
+specifically generates a ``NameError``.
+
+.. _convert-js:
+
+Conversions from Python to Javascript
++++++++++++++++++++++++++++++++++++++
+
+py.js types (extensions of :js:class:`py.object`) can be converted
+back to javascript by calling their :js:func:`py.object.toJSON`
+method.
+
+The default implementation raises an error, as arbitrary objects can
+not be converted back to javascript.
+
+Most built-in objects provide a :js:func:`py.object.toJSON`
+implementation out of the box.
+
+Javascript-level exceptions
++++++++++++++++++++++++++++
+
+Javascript allows throwing arbitrary things, but runtimes don't seem
+to provide any useful information (when they ever do) if what is
+thrown isn't a direct instance of ``Error``. As a result, while
+``py.js`` tries to match the exception-throwing semantics of Python it
+only ever throws bare ``Error`` at the javascript-level. Instead, it
+prefixes the error message with the name of the Python expression, a
+colon, a space, and the actual message.
+
+For instance, where Python would throw ``KeyError("'foo'")`` when
+accessing an invalid key on a ``dict``, ``py.js`` will throw
+``Error("KeyError: 'foo'")``.
+
+.. _Python Data Model: http://docs.python.org/reference/datamodel.html
+
diff --git a/addons/web/static/lib/py.js/doc/make.bat b/addons/web/static/lib/py.js/doc/make.bat
new file mode 100644
index 00000000000..c3db032e01a
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyjs.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyjs.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/addons/web/static/lib/py.js/doc/types.rst b/addons/web/static/lib/py.js/doc/types.rst
new file mode 100644
index 00000000000..61d950c6ec6
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/types.rst
@@ -0,0 +1,248 @@
+Implementing a custom type
+==========================
+
+To implement a custom python-level type, one can use the
+:func:`py.type` builtin. At the JS-level, it is a function with the
+same signature as the :py:class:`type` builtin [#bases]_. It returns a
+child type of its one base (or :py:class:`py.object` if no base is
+provided).
+
+The ``dict`` parameter to :func:`py.type` can contain any
+attribute, javascript-level or python-level: the default
+``__getattribute__`` implementation will ensure they are converted to
+Python-level attributes if needed. Most methods are also wrapped and
+converted to :ref:`types-methods-python`, although there are a number
+of special cases:
+
+* Most "magic methods" of the data model ("dunder" methods) remain
+ javascript-level. See :ref:`the listing of magic methods and their
+ signatures <types-methods-dunder>`. As a result, they do not respect
+ the :ref:`types-methods-python-call`
+
+* The ``toJSON`` and ``fromJSON`` methods are special-cased to remain
+ javascript-level and don't follow the
+ :ref:`types-methods-python-call`
+
+* Functions which have been wrapped explicitly (via
+ :class:`py.PY_def`, :py:class:`py.classmethod` or
+ :py:class:`py.staticmethod`) are associated to the class
+ untouched. But due to their wrapper, they will use the
+ :ref:`types-methods-python-call` anyway
+
+.. _types-methods-python:
+
+Python-level callable
+---------------------
+
+Wrapped javascript function *or* the :func:`__call__` method itself
+follow the :ref:`types-methods-python-call`. As a result, they can't
+(easily) be called directly from javascript code. Because
+:func:`__new__` and :func:`__init__` follow from :func:`__call__`,
+they also follow the :ref:`types-methods-python-call`.
+
+:func:`py.PY_call` should be used when interacting with them from
+javascript is necessary.
+
+Because ``__call__`` follows the :ref:`types-methods-python-call`,
+instantiating a ``py.js`` type from javascript requires using
+:func:`py.PY_call`.
+
+.. _types-methods-python-call:
+
+Python calling conventions
+++++++++++++++++++++++++++
+
+The python-level arguments should be considered completely opaque,
+they should be interacted with through :func:`py.PY_parseArgs` (to
+extract python-level arguments to javascript implementation code) and
+:func:`py.PY_call` (to call :ref:`types-methods-python` from
+javascript code).
+
+A callable following the :ref:`types-methods-python-call` *must*
+return a ``py.js`` object, an error will be generated when failing to
+do so.
+
+.. todo:: arguments forwarding when e.g. overriding methods?
+
+.. _types-methods-dunder:
+
+Magic methods
+-------------
+
+``py.js`` doesn't support calling magic ("dunder") methods of the
+datamodel from Python code, and these methods remain javascript-level
+(they don't follow the :ref:`types-methods-python-call`).
+
+Here is a list of the understood datamodel methods, refer to `the
+relevant Python documentation
+<http://docs.python.org/reference/datamodel.html?highlight=data%20model#special-method-names>`_
+for their roles.
+
+Basic customization
++++++++++++++++++++
+
+.. function:: __hash__()
+
+ :returns: String
+
+.. function:: __eq__(other)
+
+ The default implementation tests for identity
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+.. function:: __ne__(other)
+
+ The default implementation calls :func:`__eq__` and reverses
+ its result.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+.. function:: __lt__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+
+.. function:: __le__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+
+.. function:: __ge__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+
+.. function:: __gt__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+.. function:: __str__()
+
+ Simply calls :func:`__unicode__`. This method should not be
+ overridden, :func:`__unicode__` should be overridden instead.
+
+ :returns: :py:class:`py.str`
+
+.. function:: __unicode__()
+
+ :returns: :py:class:`py.unicode`
+
+.. function:: __nonzero__()
+
+ The default implementation always returns :data:`py.True`
+
+ :returns: :py:class:`py.bool`
+
+Customizing attribute access
+++++++++++++++++++++++++++++
+
+.. function:: __getattribute__(name)
+
+ :param String name: name of the attribute, as a javascript string
+ :returns: :py:class:`py.object`
+
+.. function:: __getattr__(name)
+
+ :param String name: name of the attribute, as a javascript string
+ :returns: :py:class:`py.object`
+
+.. function:: __setattr__(name, value)
+
+ :param String name: name of the attribute, as a javascript string
+ :param value: :py:class:`py.object`
+
+Implementing descriptors
+++++++++++++++++++++++++
+
+.. function:: __get__(instance)
+
+ .. note:: readable descriptors don't currently handle "owner
+ classes"
+
+ :param instance: :py:class:`py.object`
+ :returns: :py:class:`py.object`
+
+.. function:: __set__(instance, value)
+
+ :param instance: :py:class:`py.object`
+ :param value: :py:class:`py.object`
+
+Emulating Numeric Types
++++++++++++++++++++++++
+
+* Non-in-place binary numeric methods (e.g. ``__add__``, ``__mul__``,
+ ...) should all be supported including reversed calls (in case the
+ primary call is not available or returns
+ :py:data:`py.NotImplemented`). They take a single
+ :py:class:`py.object` parameter and return a single
+ :py:class:`py.object` parameter.
+
+* Unary operator numeric methods are all supported:
+
+ .. function:: __pos__()
+
+ :returns: :py:class:`py.object`
+
+ .. function:: __neg__()
+
+ :returns: :py:class:`py.object`
+
+ .. function:: __invert__()
+
+ :returns: :py:class:`py.object`
+
+* For non-operator numeric methods, support is contingent on the
+ corresponding :ref:`builtins <builtins>` being implemented
+
+Emulating container types
++++++++++++++++++++++++++
+
+.. function:: __len__()
+
+ :returns: :py:class:`py.int`
+
+.. function:: __getitem__(name)
+
+ :param name: :py:class:`py.object`
+ :returns: :py:class:`py.object`
+
+.. function:: __setitem__(name, value)
+
+ :param name: :py:class:`py.object`
+ :param value: :py:class:`py.object`
+
+.. function:: __iter__()
+
+ :returns: :py:class:`py.object`
+
+.. function:: __reversed__()
+
+ :returns: :py:class:`py.object`
+
+.. function:: __contains__(other)
+
+ :param other: :py:class:`py.object`
+ :returns: :py:class:`py.bool`
+
+.. [#bases] with the limitation that, because :ref:`py.js builds its
+ object model on top of javascript's
+ <details-object-model>`, only one base is allowed.
diff --git a/addons/web/static/lib/py.js/doc/utility.rst b/addons/web/static/lib/py.js/doc/utility.rst
new file mode 100644
index 00000000000..86cfb1cfe36
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/utility.rst
@@ -0,0 +1,226 @@
+Utility functions for interacting with ``py.js`` objects
+========================================================
+
+Essentially the ``py.js`` version of the Python C API, these functions
+are used to implement new ``py.js`` types or to interact with existing
+ones.
+
+They are prefixed with ``PY_``.
+
+.. function:: py.PY_parseArgs(arguments, format)
+
+ Arguments parser converting from the :ref:`user-defined calling
+ conventions <types-methods-python-call>` to a JS object mapping
+ argument names to values. It serves the same role as
+ `PyArg_ParseTupleAndKeywords`_.
+
+ ::
+
+ var args = py.PY_parseArgs(
+ arguments, ['foo', 'bar', ['baz', 3], ['qux', "foo"]]);
+
+ roughly corresponds to the argument spec:
+
+ .. code-block:: python
+
+ def func(foo, bar, baz=3, qux="foo"):
+ pass
+
+ .. note:: a significant difference is that "default values" will
+ be re-evaluated at each call, since they are within the
+ function.
+
+ :param arguments: array-like objects holding the args and kwargs
+ passed to the callable, generally the
+ ``arguments`` of the caller.
+
+ :param format: mapping declaration to the actual arguments of the
+ function. A javascript array composed of five
+ possible types of elements:
+
+ * The literal string ``'*'`` marks all following
+ parameters as keyword-only, regardless of them
+ having a default value or not [#kwonly]_. Can
+ only be present once in the parameters list.
+
+ * A string prefixed by ``*``, marks the positional
+ variadic parameter for the function: gathers all
+ provided positional arguments left and makes all
+ following parameters keyword-only
+ [#star-args]_. ``*args`` is incompatible with
+ ``*``.
+
+ * A string prefixed with ``**``, marks the
+ positional keyword variadic parameter for the
+ function: gathers all provided keyword arguments
+ left and closes the argslist. If present, this
+ must be the last parameter of the format list.
+
+ * A string defines a required parameter, accessible
+ positionally or through keyword
+
+ * A pair of ``[String, py.object]`` defines an
+ optional parameter and its default value.
+
+ For simplicity, when not using optional parameters
+ it is possible to use a simple string as the format
+ (using space-separated elements). The string will
+ be split on whitespace and processed as a normal
+ format array.
+
+ :returns: a javascript object mapping argument names to values
+
+ :raises: ``TypeError`` if the provided arguments don't match the
+ format
+
+.. class:: py.PY_def(fn)
+
+ Type wrapping javascript functions into py.js callables. The
+ wrapped function follows :ref:`the py.js calling conventions
+ <types-methods-python-call>`
+
+ :param Function fn: the javascript function to wrap
+ :returns: a callable py.js object
+
+Object Protocol
+---------------
+
+.. function:: py.PY_hasAttr(o, attr_name)
+
+ Returns ``true`` if ``o`` has the attribute ``attr_name``,
+ otherwise returns ``false``. Equivalent to Python's ``hasattr(o,
+ attr_name)``
+
+ :param o: A :class:`py.object`
+ :param attr_name: a javascript ``String``
+ :rtype: ``Boolean``
+
+.. function:: py.PY_getAttr(o, attr_name)
+
+ Retrieve an attribute ``attr_name`` from the object ``o``. Returns
+ the attribute value on success, raises ``AttributeError`` on
+ failure. Equivalent to the python expression ``o.attr_name``.
+
+ :param o: A :class:`py.object`
+ :param attr_name: a javascript ``String``
+ :returns: A :class:`py.object`
+ :raises: ``AttributeError``
+
+.. function:: py.PY_str(o)
+
+ Computes a string representation of ``o``, returns the string
+ representation. Equivalent to ``str(o)``
+
+ :param o: A :class:`py.object`
+ :returns: :class:`py.str`
+
+.. function:: py.PY_isInstance(inst, cls)
+
+ Returns ``true`` if ``inst`` is an instance of ``cls``, ``false``
+ otherwise.
+
+.. function:: py.PY_isSubclass(derived, cls)
+
+ Returns ``true`` if ``derived`` is ``cls`` or a subclass thereof.
+
+.. function:: py.PY_call(callable[, args][, kwargs])
+
+ Call an arbitrary python-level callable from javascript.
+
+ :param callable: A ``py.js`` callable object (broadly speaking,
+ either a class or an object with a ``__call__``
+ method)
+
+ :param args: javascript Array of :class:`py.object`, used as
+ positional arguments to ``callable``
+
+ :param kwargs: javascript Object mapping names to
+ :class:`py.object`, used as named arguments to
+ ``callable``
+
+ :returns: nothing or :class:`py.object`
+
+.. function:: py.PY_isTrue(o)
+
+ Returns ``true`` if the object is considered truthy, ``false``
+ otherwise. Equivalent to ``bool(o)``.
+
+ :param o: A :class:`py.object`
+ :rtype: Boolean
+
+.. function:: py.PY_not(o)
+
+ Inverse of :func:`py.PY_isTrue`.
+
+.. function:: py.PY_size(o)
+
+ If ``o`` is a sequence or mapping, returns its length. Otherwise,
+ raises ``TypeError``.
+
+ :param o: A :class:`py.object`
+ :returns: ``Number``
+ :raises: ``TypeError`` if the object doesn't have a length
+
+Number Protocol
+---------------
+
+.. function:: py.PY_add(o1, o2)
+
+ Returns the result of adding ``o1`` and ``o2``, equivalent to
+ ``o1 + o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_subtract(o1, o2)
+
+ Returns the result of subtracting ``o2`` from ``o1``, equivalent
+ to ``o1 - o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_multiply(o1, o2)
+
+ Returns the result of multiplying ``o1`` by ``o2``, equivalent to
+ ``o1 * o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_divide(o1, o2)
+
+ Returns the result of dividing ``o1`` by ``o2``, equivalent to
+ ``o1 / o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_negative(o)
+
+ Returns the negation of ``o``, equivalent to ``-o``.
+
+ :param o: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_positive(o)
+
+ Returns the "positive" of ``o``, equivalent to ``+o``.
+
+ :param o: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. [#kwonly] Python 2, which py.js currently implements, does not
+ support Python-level keyword-only parameters (it can be
+ done through the C-API), but it seemed neat and easy
+ enough so there.
+
+.. [#star-args] due to this and contrary to Python 2, py.js allows
+ arguments other than ``**kwargs`` to follow ``*args``.
+
+.. _PyArg_ParseTupleAndKeywords:
+ http://docs.python.org/c-api/arg.html#PyArg_ParseTupleAndKeywords
diff --git a/addons/web/static/lib/py.js/lib/py.js b/addons/web/static/lib/py.js/lib/py.js
index a8c952679af..854232a0d80 100644
--- a/addons/web/static/lib/py.js/lib/py.js
+++ b/addons/web/static/lib/py.js/lib/py.js
@@ -148,14 +148,14 @@ var py = {};
comparator('>'); comparator('>=');
comparator('<>'); comparator('!='); comparator('==');
- infix('|', 70); infix('^', 80), infix('&', 90);
+ infix('|', 70); infix('^', 80); infix('&', 90);
infix('<<', 100); infix('>>', 100);
infix('+', 110); infix('-', 110);
infix('*', 120); infix('/', 120);
- infix('//', 120), infix('%', 120);
+ infix('//', 120); infix('%', 120);
prefix('-', 130); prefix('+', 130); prefix('~', 130);
@@ -261,7 +261,7 @@ var py = {};
var Name = '[a-zA-Z_]\\w*';
- var DecNumber = '\\d+';
+ var DecNumber = '\\d+(L|l)?';
var IntNumber = DecNumber;
var PointFloat = group('\\d+\\.\\d*', '\\.\\d+');
var FloatNumber = PointFloat;
@@ -273,12 +273,16 @@ var py = {};
var Special = '[:;.,`@]';
var Funny = group(Operator, Bracket, Special);
- var ContStr = group("'[^']*'", '"[^"]*"');
+ var ContStr = group("[uU]?'([^']*)'", '[uU]?"([^"]*)"');
var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);
+ var number_pattern = new RegExp('^' + Number + '$');
+ var string_pattern = new RegExp('^' + ContStr + '$');
+ var name_pattern = new RegExp('^' + Name + '$');
+ var strip = new RegExp('^' + Whitespace);
return function tokenize(s) {
- var max=s.length, tokens = [];
+ var max=s.length, tokens = [], start, end = undefined;
// /g flag makes repeated exec() have memory
var pseudoprog = new RegExp(PseudoToken, 'g');
@@ -294,18 +298,20 @@ var py = {};
+ '; parsed so far: ' + tokens);
}
- var start = pseudomatch.index, end = pseudoprog.lastIndex;
+ start = pseudomatch.index;
+ end = pseudoprog.lastIndex;
// strip leading space caught by Whitespace
- var token = s.slice(start, end).replace(new RegExp('^' + Whitespace), '');
+ var token = s.slice(start, end).replace(strip, '');
var initial = token[0];
- if (/\d/.test(initial) || (initial === '.' && token !== '.')) {
+ if (number_pattern.test(token)) {
tokens.push(create(symbols['(number)'], {
value: parseFloat(token)
}));
- } else if (/'|"/.test(initial)) {
+ } else if (string_pattern.test(token)) {
+ var m = string_pattern.exec(token);
tokens.push(create(symbols['(string)'], {
- value: token.slice(1, -1)
+ value: m[2] || m[3]
}));
} else if (token in symbols) {
var symbol;
@@ -320,7 +326,7 @@ var py = {};
symbol = symbols[token];
}
tokens.push(create(symbol));
- } else if (/[_a-zA-Z]/.test(initial)) {
+ } else if (name_pattern.test(token)) {
tokens.push(create(symbols['(name)'], {
value: token
}));
@@ -369,24 +375,26 @@ var py = {};
return py.False;
}
- if (val instanceof py.object
- || val === py.object
- || py.issubclass.__call__([val, py.object]) === py.True) {
+ var fn = function () {};
+ fn.prototype = py.object;
+ if (py.PY_isInstance(val, py.object)
+ || py.PY_isSubclass(val, py.object)) {
return val;
}
switch (typeof val) {
case 'number':
- return new py.float(val);
+ return py.float.fromJSON(val);
case 'string':
- return new py.str(val);
+ return py.str.fromJSON(val);
case 'function':
- return new py.def(val);
+ return py.PY_def.fromJSON(val);
}
switch(val.constructor) {
case Object:
- var o = new py.object();
+ // TODO: why py.object instead of py.dict?
+ var o = py.PY_call(py.object);
for (var prop in val) {
if (val.hasOwnProperty(prop)) {
o[prop] = val[prop];
@@ -394,46 +402,275 @@ var py = {};
}
return o;
case Array:
- var a = new py.list();
- a.values = val;
+ var a = py.PY_call(py.list);
+ a._values = val;
return a;
}
throw new Error("Could not convert " + val + " to a pyval");
}
+
+ // JSAPI, JS-level utility functions for implementing new py.js
+ // types
+ py.py = {};
+
+ py.PY_parseArgs = function PY_parseArgs(argument, format) {
+ var out = {};
+ var args = argument[0];
+ var kwargs = {};
+ for (var k in argument[1]) {
+ if (!argument[1].hasOwnProperty(k)) { continue; }
+ kwargs[k] = argument[1][k];
+ }
+ if (typeof format === 'string') {
+ format = format.split(/\s+/);
+ }
+ var name = function (spec) {
+ if (typeof spec === 'string') {
+ return spec;
+ } else if (spec instanceof Array && spec.length === 2) {
+ return spec[0];
+ }
+ throw new Error(
+ "TypeError: unknown format specification " +
+ JSON.stringify(spec));
+ };
+ var spec;
+ // TODO: ensure all format arg names are actual names?
+ for(var i=0; i<args.length; ++i) {
+ spec = format[i];
+ // spec list ended, or specs switching to keyword-only
+ if (!spec || spec === '*') {
+ throw new Error(
+ "TypeError: function takes exactly " + (i-1) +
+ " positional arguments (" + args.length +
+ " given")
+ } else if(/^\*\w/.test(spec)) {
+ // *args, final
+ out[name(spec.slice(1))] = args.slice(i);
+ break;
+ }
+
+ out[name(spec)] = args[i];
+ }
+ for(var j=i; j<format.length; ++j) {
+ spec = format[j];
+ var n = name(spec);
+
+ if (n in out) {
+ throw new Error(
+ "TypeError: function got multiple values " +
+ "for keyword argument '" + kwarg + "'");
+ }
+ if (/^\*\*\w/.test(n)) {
+ // **kwarg
+ out[n.slice(2)] = kwargs;
+ kwargs = {};
+ break;
+ }
+ if (n in kwargs) {
+ out[n] = kwargs[n];
+ // Remove from args map
+ delete kwargs[n];
+ }
+ }
+ // Ensure all keyword arguments were consumed
+ for (var key in kwargs) {
+ throw new Error(
+ "TypeError: function got an unexpected keyword argument '"
+ + key + "'");
+ }
+
+ // Fixup args count if there's a kwonly flag (or an *args)
+ var kwonly = 0;
+ for(var k = 0; k < format.length; ++k) {
+ if (/^\*/.test(format[k])) { kwonly = 1; break; }
+ }
+ // Check that all required arguments have been matched, add
+ // optional values
+ for(var k = 0; k < format.length; ++k) {
+ spec = format[k];
+ var n = name(spec);
+ // kwonly, va_arg or matched argument
+ if (/^\*/.test(n) || n in out) { continue; }
+ // Unmatched required argument
+ if (!(spec instanceof Array)) {
+ throw new Error(
+ "TypeError: function takes exactly " + (format.length - kwonly)
+ + " arguments");
+ }
+ // Set default value
+ out[n] = spec[1];
+ }
+
+ return out;
+ };
+
+ py.PY_hasAttr = function (o, attr_name) {
+ try {
+ py.PY_getAttr(o, attr_name);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ };
+ py.PY_getAttr = function (o, attr_name) {
+ return PY_ensurepy(o.__getattribute__(attr_name));
+ };
+ py.PY_str = function (o) {
+ var v = o.__str__();
+ if (py.PY_isInstance(v, py.str)) {
+ return v;
+ }
+ var typename;
+ if (v.__class__) { // py type
+ typename = v.__class__.__name__;
+ } else if(typeof v !== 'object') { // JS primitive
+ typename = typeof v;
+ } else { // JS object
+ typename = v.constructor.name;
+ }
+ throw new Error(
+ 'TypeError: __str__ returned non-string (type '+typename+')');
+ };
+ py.PY_isInstance = function (inst, cls) {
+ var fn = function () {};
+ fn.prototype = cls;
+ return inst instanceof fn;
+ };
+ py.PY_isSubclass = function (derived, cls) {
+ var fn = function () {};
+ fn.prototype = cls;
+ return derived === cls || derived instanceof fn;
+ };
+ py.PY_call = function (callable, args, kwargs) {
+ if (!args) {
+ args = []; kwargs = {};
+ } else if (typeof args === 'object' && !(args instanceof Array)) {
+ kwargs = args;
+ args = [];
+ } else if (!kwargs) {
+ kwargs = {};
+ }
+ if (callable.__is_type) {
+ // class hack
+ var instance = callable.__new__.call(callable, args, kwargs);
+ var typ = function () {};
+ typ.prototype = callable;
+ if (instance instanceof typ) {
+ instance.__init__.call(instance, args, kwargs);
+ }
+ return instance
+ }
+ return callable.__call__(args, kwargs);
+ };
+ py.PY_isTrue = function (o) {
+ var res = o.__nonzero__();
+ if (res === py.True) {
+ return true;
+ }
+ if (res === py.False) {
+ return false;
+ }
+ throw new Error(
+ "TypeError: __nonzero__ should return bool, returned "
+ + res.__class__.__name__);
+ };
+ py.PY_not = function (o) {
+ return !py.PY_isTrue(o);
+ };
+ py.PY_size = function (o) {
+ if (!o.__len__) {
+ throw new Error(
+ "TypeError: object of type '" +
+ o.__class__.__name__ +
+ "' has no len()");
+ }
+ var v = o.__len__();
+ if (typeof v !== 'number') {
+ throw new Error("TypeError: a number is required");
+ }
+ return v;
+ };
+ py.PY_add = function (o1, o2) {
+ return PY_op(o1, o2, '+');
+ };
+ py.PY_subtract = function (o1, o2) {
+ return PY_op(o1, o2, '-');
+ };
+ py.PY_multiply = function (o1, o2) {
+ return PY_op(o1, o2, '*');
+ };
+ py.PY_divide = function (o1, o2) {
+ return PY_op(o1, o2, '/');
+ };
+ py.PY_negative = function (o) {
+ if (!o.__neg__) {
+ throw new Error(
+ "TypeError: bad operand for unary -: '"
+ + o.__class__.__name
+ + "'");
+ }
+ return o.__neg__();
+ };
+ py.PY_positive = function (o) {
+ if (!o.__pos__) {
+ throw new Error(
+ "TypeError: bad operand for unary +: '"
+ + o.__class__.__name
+ + "'");
+ }
+ return o.__pos__();
+ };
+
// Builtins
- py.type = function type(constructor, base, dict) {
- var proto;
- if (!base) {
- base = py.object;
+ py.type = function type(name, bases, dict) {
+ if (typeof name !== 'string') {
+ throw new Error("ValueError: a class name should be a string");
}
- proto = constructor.prototype = create(base.prototype);
- proto.constructor = constructor;
+ if (!bases || bases.length === 0) {
+ bases = [py.object];
+ } else if (bases.length > 1) {
+ throw new Error("ValueError: can't provide multiple bases for a "
+ + "new type");
+ }
+ var base = bases[0];
+ var ClassObj = create(base);
if (dict) {
- for(var k in dict) {
+ for (var k in dict) {
if (!dict.hasOwnProperty(k)) { continue; }
- proto[k] = dict[k];
+ ClassObj[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;
+ ClassObj.__class__ = ClassObj;
+ ClassObj.__name__ = name;
+ ClassObj.__bases__ = bases;
+ ClassObj.__is_type = true;
+
+ return ClassObj;
+ };
+ py.type.__call__ = function () {
+ var args = py.PY_parseArgs(arguments, ['object']);
+ return args.object.__class__;
};
var hash_counter = 0;
- py.object = py.type(function object() {}, {}, {
+ py.object = py.type('object', [{}], {
+ __new__: function () {
+ // If ``this`` isn't the class object, this is going to be
+ // beyond fucked up
+ var inst = create(this);
+ inst.__is_type = false;
+ return inst;
+ },
+ __init__: function () {},
// Basic customization
__hash__: function () {
if (this._hash) {
return this._hash;
}
- return this._hash = hash_counter++;
+ // tagged counter, to avoid collisions with e.g. number hashes
+ return this._hash = '\0\0\0' + String(hash_counter++);
},
__eq__: function (other) {
return (this === other) ? py.True : py.False;
@@ -453,8 +690,7 @@ var py = {};
return this.__unicode__();
},
__unicode__: function () {
- // TODO: return python string
- return '<object ' + this.constructor.name + '>';
+ return py.str.fromJSON('<' + this.__class__.__name__ + ' object>');
},
__nonzero__: function () {
return py.True;
@@ -465,13 +701,13 @@ var py = {};
var val = this[name];
if (typeof val === 'object' && '__get__' in val) {
// TODO: second argument should be class
- return val.__get__(this);
+ return val.__get__(this, py.PY_call(py.type, [this]));
}
if (typeof val === 'function' && !this.hasOwnProperty(name)) {
// val is a method from the class
- return new PY_instancemethod(val, this);
+ return PY_instancemethod.fromJSON(val, this);
}
- return PY_ensurepy(val);
+ return val;
}
if ('__getattr__' in this) {
return this.__getattr__(name);
@@ -491,137 +727,181 @@ var py = {};
throw new Error(this.constructor.name + ' can not be converted to JSON');
}
});
- var NoneType = py.type(function NoneType() {}, py.object, {
+ var NoneType = py.type('NoneType', null, {
__nonzero__: function () { return py.False; },
toJSON: function () { return null; }
});
- py.None = new NoneType();
- var NotImplementedType = py.type(function NotImplementedType(){});
- py.NotImplemented = new NotImplementedType();
+ py.None = py.PY_call(NoneType);
+ var NotImplementedType = py.type('NotImplementedType', null, {});
+ py.NotImplemented = py.PY_call(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, {
+ py.bool = py.type('bool', null, {
+ __new__: function () {
+ if (!booleans_initialized) {
+ return py.object.__new__.apply(this);
+ }
+
+ var ph = {};
+ var args = py.PY_parseArgs(arguments, [['value', ph]]);
+ if (args.value === ph) {
+ return py.False;
+ }
+ return py.PY_isTrue(args.value) ? py.True : py.False;
+ },
+ __str__: function () {
+ return py.str.fromJSON((this === py.True) ? "True" : "False");
+ },
__nonzero__: function () { return this; },
+ fromJSON: function (val) { return val ? py.True : py.False },
toJSON: function () { return this === py.True; }
});
- py.True = new py.bool();
- py.False = new py.bool();
+ py.True = py.PY_call(py.bool);
+ py.False = py.PY_call(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, {
+ py.float = py.type('float', null, {
+ __init__: function () {
+ var placeholder = {};
+ var args = py.PY_parseArgs(arguments, [['value', placeholder]]);
+ var value = args.value;
+ if (value === placeholder) {
+ this._value = 0; return;
+ }
+ if (py.PY_isInstance(value, py.float)) {
+ this._value = value._value;
+ }
+ if (py.PY_isInstance(value, py.object) && '__float__' in value) {
+ var res = value.__float__();
+ if (py.PY_isInstance(res, py.float)) {
+ this._value = res._value;
+ return;
+ }
+ throw new Error('TypeError: __float__ returned non-float (type ' +
+ res.__class__.__name__ + ')');
+ }
+ throw new Error('TypeError: float() argument must be a string or a number');
+ },
+ __str__: function () {
+ return py.str.fromJSON(String(this._value));
+ },
__eq__: function (other) {
return this._value === other._value ? py.True : py.False;
},
__lt__: function (other) {
- if (!(other instanceof py.float)) { return py.NotImplemented; }
+ if (!py.PY_isInstance(other, 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; }
+ if (!py.PY_isInstance(other, 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; }
+ if (!py.PY_isInstance(other, 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; }
+ if (!py.PY_isInstance(other, 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);
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value + other._value);
},
__neg__: function () {
- return new py.float(-this._value);
+ return py.float.fromJSON(-this._value);
},
__sub__: function (other) {
- if (!(other instanceof py.float)) { return py.NotImplemented; }
- return new py.float(this._value - other._value);
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value - other._value);
},
__mul__: function (other) {
- if (!(other instanceof py.float)) { return py.NotImplemented; }
- return new py.float(this._value * other._value);
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value * other._value);
},
__div__: function (other) {
- if (!(other instanceof py.float)) { return py.NotImplemented; }
- return new py.float(this._value / other._value);
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value / other._value);
},
__nonzero__: function () {
return this._value ? py.True : py.False;
},
+ fromJSON: function (v) {
+ if (!(typeof v === 'number')) {
+ throw new Error('py.float.fromJSON can only take numbers');
+ }
+ var instance = py.PY_call(py.float);
+ instance._value = v;
+ return instance;
+ },
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, {
+ py.str = py.type('str', null, {
+ __init__: function () {
+ var placeholder = {};
+ var args = py.PY_parseArgs(arguments, [['value', placeholder]]);
+ var s = args.value;
+ if (s === placeholder) { this._value = ''; return; }
+ this._value = py.PY_str(s)._value;
+ },
+ __hash__: function () {
+ return '\1\0\1' + this._value;
+ },
+ __str__: function () {
+ return this;
+ },
__eq__: function (other) {
- if (other instanceof py.str && this._value === other._value) {
+ if (py.PY_isInstance(other, py.str)
+ && this._value === other._value) {
return py.True;
}
return py.False;
},
__lt__: function (other) {
- if (!(other instanceof py.str)) { return py.NotImplemented; }
+ if (py.PY_not(py.PY_call(py.isinstance, [other, 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; }
+ if (!py.PY_isInstance(other, 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; }
+ if (!py.PY_isInstance(other, 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; }
+ if (!py.PY_isInstance(other, 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);
+ if (!py.PY_isInstance(other, py.str)) {
+ return py.NotImplemented;
+ }
+ return py.str.fromJSON(this._value + other._value);
},
__nonzero__: function () {
return this._value.length ? py.True : py.False;
@@ -629,37 +909,74 @@ var py = {};
__contains__: function (s) {
return (this._value.indexOf(s._value) !== -1) ? py.True : py.False;
},
+ fromJSON: function (s) {
+ if (typeof s === 'string') {
+ var instance = py.PY_call(py.str);
+ instance._value = s;
+ return instance;
+ }
+ throw new Error("str.fromJSON can only take strings");
+ },
toJSON: function () {
return this._value;
}
});
- py.tuple = py.type(function tuple() {}, null, {
+ py.tuple = py.type('tuple', null, {
+ __init__: function () {
+ this._values = [];
+ },
__contains__: function (value) {
- for(var i=0, len=this.values.length; i<len; ++i) {
- if (this.values[i].__eq__(value) === py.True) {
+ 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;
},
__getitem__: function (index) {
- return PY_ensurepy(this.values[index.toJSON()]);
+ return PY_ensurepy(this._values[index.toJSON()]);
},
toJSON: function () {
var out = [];
- for (var i=0; i<this.values.length; ++i) {
- out.push(this.values[i].toJSON());
+ 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, {
+ py.dict = py.type('dict', null, {
+ __init__: function () {
+ this._store = {};
+ },
+ __getitem__: function (key) {
+ var h = key.__hash__();
+ if (!(h in this._store)) {
+ throw new Error("KeyError: '" + key.toJSON() + "'");
+ }
+ return this._store[h][1];
+ },
__setitem__: function (key, value) {
this._store[key.__hash__()] = [key, value];
},
+ get: function () {
+ var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]);
+ var h = args.k.__hash__();
+ if (!(h in this._store)) {
+ return args.d;
+ }
+ return this._store[h][1];
+ },
+ fromJSON: function (d) {
+ var instance = py.PY_call(py.dict);
+ for (var k in (d || {})) {
+ if (!d.hasOwnProperty(k)) { continue; }
+ instance.__setitem__(
+ py.str.fromJSON(k),
+ PY_ensurepy(d[k]));
+ }
+ return instance;
+ },
toJSON: function () {
var out = {};
for(var k in this._store) {
@@ -669,30 +986,56 @@ var py = {};
return out;
}
});
- py.def = py.type(function def(nativefunc) {
- this._inst = null;
- this._func = nativefunc;
- }, py.object, {
- __call__: function (args, kwargs) {
+ py.PY_def = py.type('function', null, {
+ __call__: function () {
// don't want to rewrite __call__ for instancemethod
- return this._func.call(this._inst, args, kwargs);
+ return this._func.apply(this._inst, arguments);
+ },
+ fromJSON: function (nativefunc) {
+ var instance = py.PY_call(py.PY_def);
+ instance._inst = null;
+ instance._func = nativefunc;
+ return instance;
},
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;
+ py.classmethod = py.type('classmethod', null, {
+ __init__: function () {
+ var args = py.PY_parseArgs(arguments, 'function');
+ this._func = args['function'];
+ },
+ __get__: function (obj, type) {
+ return PY_instancemethod.fromJSON(this._func, type);
+ },
+ fromJSON: function (func) {
+ return py.PY_call(py.classmethod, [func]);
+ }
+ });
+ var PY_instancemethod = py.type('instancemethod', [py.PY_def], {
+ fromJSON: function (nativefunc, instance) {
+ var inst = py.PY_call(PY_instancemethod);
+ // could also use bind?
+ inst._inst = instance;
+ inst._func = nativefunc;
+ return inst;
+ }
+ });
+
+ py.len = new py.PY_def.fromJSON(function len() {
+ var args = py.PY_parseArgs(arguments, ['object']);
+ return py.float.fromJSON(py.PY_size(args.object));
+ });
+ py.isinstance = new py.PY_def.fromJSON(function isinstance() {
+ var args = py.PY_parseArgs(arguments, ['object', 'class']);
+ return py.PY_isInstance(args.object, args['class'])
+ ? py.True : py.False;
+ });
+ py.issubclass = new py.PY_def.fromJSON(function issubclass() {
+ var args = py.PY_parseArgs(arguments, ['C', 'B']);
+ return py.PY_isSubclass(args.C, args.B)
+ ? py.True : py.False;
});
@@ -700,10 +1043,11 @@ var py = {};
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;}],
+ '<>': ['ne', 'ne', function (a, b) { return a !== b; }],
+ '<': ['lt', 'gt', function (a, b) {return a.__class__.__name__ < b.__class__.__name__;}],
+ '<=': ['le', 'ge', function (a, b) {return a.__class__.__name__ <= b.__class__.__name__;}],
+ '>': ['gt', 'lt', function (a, b) {return a.__class__.__name__ > b.__class__.__name__;}],
+ '>=': ['ge', 'le', function (a, b) {return a.__class__.__name__ >= b.__class__.__name__;}],
'+': ['add', 'radd'],
'-': ['sub', 'rsub'],
@@ -746,8 +1090,8 @@ var py = {};
}
throw new Error(
"TypeError: unsupported operand type(s) for " + op + ": '"
- + o1.constructor.name + "' and '"
- + o2.constructor.name + "'");
+ + o1.__class__.__name__ + "' and '"
+ + o2.__class__.__name__ + "'");
};
var PY_builtins = {
@@ -761,10 +1105,16 @@ var py = {};
object: py.object,
bool: py.bool,
float: py.float,
+ str: py.str,
+ unicode: py.unicode,
tuple: py.tuple,
list: py.list,
dict: py.dict,
- issubclass: py.issubclass
+
+ len: py.len,
+ isinstance: py.isinstance,
+ issubclass: py.issubclass,
+ classmethod: py.classmethod,
};
py.parse = function (toks) {
@@ -774,7 +1124,6 @@ var py = {};
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;
@@ -782,7 +1131,7 @@ var py = {};
return b.__contains__(a);
case 'not in':
return b.__contains__(a) === py.True ? py.False : py.True;
- case '==': case '!=':
+ case '==': case '!=': case '<>':
case '<': case '<=':
case '>': case '>=':
return PY_op(a, b, operator);
@@ -799,9 +1148,9 @@ var py = {};
}
return PY_ensurepy(val, expr.value);
case '(string)':
- return new py.str(expr.value);
+ return py.str.fromJSON(expr.value);
case '(number)':
- return new py.float(expr.value);
+ return py.float.fromJSON(expr.value);
case '(constant)':
switch (expr.value) {
case 'None': return py.None;
@@ -820,9 +1169,7 @@ var py = {};
}
return py.True;
case 'not':
- return py.evaluate(expr.first, context).__nonzero__() === py.True
- ? py.False
- : py.True;
+ return py.PY_isTrue(py.evaluate(expr.first, context)) ? py.False : py.True;
case 'and':
var and_first = py.evaluate(expr.first, context);
if (and_first.__nonzero__() === py.True) {
@@ -850,7 +1197,7 @@ var py = {};
py.evaluate(arg.second, context);
}
}
- return callable.__call__(args, kwargs);
+ return py.PY_call(callable, args, kwargs);
}
var tuple_exprs = expr.first,
tuple_values = [];
@@ -858,8 +1205,8 @@ var py = {};
tuple_values.push(py.evaluate(
tuple_exprs[j], context));
}
- var t = new py.tuple();
- t.values = tuple_values;
+ var t = py.PY_call(py.tuple);
+ t._values = tuple_values;
return t;
case '[':
if (expr.second) {
@@ -871,11 +1218,11 @@ var py = {};
list_values.push(py.evaluate(
list_exprs[k], context));
}
- var l = new py.list();
- l.values = list_values;
+ var l = py.PY_call(py.list);
+ l._values = list_values;
return l;
case '{':
- var dict_exprs = expr.first, dict = new py.dict;
+ var dict_exprs = expr.first, dict = py.PY_call(py.dict);
for(var l=0; l<dict_exprs.length; ++l) {
dict.__setitem__(
py.evaluate(dict_exprs[l][0], context),
@@ -886,18 +1233,18 @@ var py = {};
if (expr.second.id !== '(name)') {
throw new Error('SyntaxError: ' + expr);
}
- return py.evaluate(expr.first, context)
- .__getattribute__(expr.second.value);
+ return py.PY_getAttr(py.evaluate(expr.first, context),
+ expr.second.value);
// numerical operators
case '~':
return (py.evaluate(expr.first, context)).__invert__();
case '+':
if (!expr.second) {
- return (py.evaluate(expr.first, context)).__pos__();
+ return py.PY_positive(py.evaluate(expr.first, context));
}
case '-':
if (!expr.second) {
- return (py.evaluate(expr.first, context)).__neg__();
+ return py.PY_negative(py.evaluate(expr.first, context));
}
case '*': case '/': case '//':
case '%':
diff --git a/addons/web/static/lib/py.js/test/parser.js b/addons/web/static/lib/py.js/test/parser.js
deleted file mode 100644
index bbbd5d7663d..00000000000
--- a/addons/web/static/lib/py.js/test/parser.js
+++ /dev/null
@@ -1,132 +0,0 @@
-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);
- });
-}); \ No newline at end of file
diff --git a/addons/web/static/lib/py.js/test/test.js b/addons/web/static/lib/py.js/test/test.js
deleted file mode 100644
index c1f92c44d81..00000000000
--- a/addons/web/static/lib/py.js/test/test.js
+++ /dev/null
@@ -1,388 +0,0 @@
-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');
- });
- });
- });
-});
-describe('Type converter', function () {
- it('should convert bare objects to objects', function () {
- expect(py.eval('foo.bar', {foo: {bar: 3}})).to.be(3);
- });
- it('should convert arrays to lists', function () {
- expect(py.eval('foo[3]', {foo: [9, 8, 7, 6, 5]}))
- .to.be(6);
- });
-});
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index ee81492b8ad..a71a071db3b 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -2348,7 +2348,7 @@
.openerp .oe_form .oe_form_field_image .oe_form_field_image_controls {
position: absolute;
top: 1px;
- padding: 4px;
+ padding: 4px 0;
width: 100%;
display: none;
text-align: center;
@@ -2376,6 +2376,7 @@
width: 100%;
left: 2px;
top: 7px;
+ overflow: hidden;
}
.openerp .oe_fileupload .oe_add button {
display: inline;
@@ -2402,10 +2403,11 @@
}
.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
display: inline-block;
- margin-left: -5px;
- height: 28px;
- width: 52px;
- margin-top: -26px;
+ margin-left: -85px;
+ height: 22px;
+ width: 152px;
+ margin-top: -24px;
+ cursor: pointer;
}
.openerp .oe_fileupload .oe_add .oe_attach_label {
color: #7c7bad;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 3923fcaf2ff..f634452a7ee 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -1880,7 +1880,7 @@ $sheet-padding: 16px
.oe_form_field_image_controls
position: absolute
top: 1px
- padding: 4px
+ padding: 4px 0
width: 100%
display: none
text-align: center
@@ -1900,6 +1900,7 @@ $sheet-padding: 16px
width: 100%
left: +2px
top: +7px
+ overflow: hidden
button
display: inline
height: 24px
@@ -1922,10 +1923,11 @@ $sheet-padding: 16px
left: -9px
input.oe_form_binary_file
display: inline-block
- margin-left: -5px
- height: 28px
- width: 52px
- margin-top: -26px
+ margin-left: -85px
+ height: 22px
+ width: 152px
+ margin-top: -24px
+ cursor: pointer
.oe_attach_label
color: #7C7BAD
margin-left: -3px
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 631a7f8c6f5..5d50841d78d 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -53,7 +53,7 @@
* OpenERP Web web module split
*---------------------------------------------------------*/
openerp.web = function(session) {
- var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
+ var files = ["pyeval", "corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
for(var i=0; i<files.length; i++) {
if(openerp.web[files[i]]) {
openerp.web[files[i]](session);
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 91cca8468d1..6bdda1a721e 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -1264,11 +1264,12 @@ instance.web.WebClient = instance.web.Client.extend({
var self = this;
return this.rpc("/web/action/load", { action_id: options.action_id })
.then(function (result) {
- var action = result;
if (options.needaction) {
- action.context.search_default_message_unread = true;
+ result.context = new instance.web.CompoundContext(
+ result.context,
+ {search_default_message_unread: true});
}
- return $.when(self.action_manager.do_action(action, {
+ return $.when(self.action_manager.do_action(result, {
clear_breadcrumbs: true,
action_menu_id: self.menu.current_menu,
})).fail(function() {
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index 0a6f4ed3a29..55984162a55 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -948,296 +948,6 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
this.server = this.origin; // keep chs happy
this.rpc_function = (this.origin == window_origin) ? this.rpc_json : this.rpc_jsonp;
},
- 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.getFullYear(), d.getMonth() + 1, d.getDate());
- });
- 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,
- current_date: date.today.__call__().strftime(['%Y-%m-%d'])
- };
- },
- /**
- * FIXME: Huge testing hack, especially the evaluation context, rewrite + test for real before switching
- */
- test_eval: function (source, expected) {
- var match_template = '<ul>' +
- '<li>Source: %(source)s</li>' +
- '<li>Local: %(local)s</li>' +
- '<li>Remote: %(remote)s</li>' +
- '</ul>',
- fail_template = '<ul>' +
- '<li>Error: %(error)s</li>' +
- '<li>Source: %(source)s</li>' +
- '</ul>';
- try {
- // see Session.eval_context in Python
- var ctx = this.test_eval_contexts(
- ([this.context] || []).concat(source.contexts));
- if (!_.isEqual(ctx, expected.context)) {
- instance.webclient.notification.warn('Context mismatch, report to xmo',
- _.str.sprintf(match_template, {
- source: JSON.stringify(source.contexts),
- local: JSON.stringify(ctx),
- remote: JSON.stringify(expected.context)
- }), true);
- }
- } catch (e) {
- instance.webclient.notification.warn('Context fail, report to xmo',
- _.str.sprintf(fail_template, {
- error: e.message,
- source: JSON.stringify(source.contexts)
- }), true);
- }
-
- try {
- var dom = this.test_eval_domains(source.domains, this.test_eval_get_context());
- if (!_.isEqual(dom, expected.domain)) {
- instance.webclient.notification.warn('Domains mismatch, report to xmo',
- _.str.sprintf(match_template, {
- source: JSON.stringify(source.domains),
- local: JSON.stringify(dom),
- remote: JSON.stringify(expected.domain)
- }), true);
- }
- } catch (e) {
- instance.webclient.notification.warn('Domain fail, report to xmo',
- _.str.sprintf(fail_template, {
- error: e.message,
- source: JSON.stringify(source.domains)
- }), true);
- }
-
- try {
- var groups = this.test_eval_groupby(source.group_by_seq);
- if (!_.isEqual(groups, expected.group_by)) {
- instance.webclient.notification.warn('GroupBy mismatch, report to xmo',
- _.str.sprintf(match_template, {
- source: JSON.stringify(source.group_by_seq),
- local: JSON.stringify(groups),
- remote: JSON.stringify(expected.group_by)
- }), true);
- }
- } catch (e) {
- instance.webclient.notification.warn('GroupBy fail, report to xmo',
- _.str.sprintf(fail_template, {
- error: e.message,
- source: JSON.stringify(source.group_by_seq)
- }), true);
- }
- },
- test_eval_contexts: function (contexts, evaluation_context) {
- evaluation_context = evaluation_context || {};
- var self = this;
- return _(contexts).reduce(function (result_context, ctx) {
- // __eval_context evaluations can lead to some of `contexts`'s
- // values being null, skip them as well as empty contexts
- if (_.isEmpty(ctx)) { return result_context; }
- var evaluated = ctx;
- switch(ctx.__ref) {
- case 'context':
- evaluated = py.eval(ctx.__debug, evaluation_context);
- break;
- case 'compound_context':
- var eval_context = self.test_eval_contexts([ctx.__eval_context]);
- evaluated = self.test_eval_contexts(
- ctx.__contexts, _.extend({}, evaluation_context, eval_context));
- break;
- }
- // add newly evaluated context to evaluation context for following
- // siblings
- _.extend(evaluation_context, evaluated);
- return _.extend(result_context, evaluated);
- }, _.extend({}, this.user_context));
- },
- test_eval_domains: function (domains, evaluation_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, evaluation_context));
- break;
- case 'compound_domain':
- var eval_context = self.test_eval_contexts([dom.__eval_context]);
- result_domain.push.apply(
- result_domain, self.test_eval_domains(
- dom.__domains, _.extend(
- {}, evaluation_context, 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, ctx.__eval_context).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.
*
@@ -1271,22 +981,19 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
var deferred = $.Deferred();
if (! options.shadow)
this.trigger('request', url, payload);
- var request = this.rpc_function(url, payload).done(
+
+ this.rpc_function(url, payload).then(
function (response, textStatus, jqXHR) {
if (! options.shadow)
self.trigger('response', response);
if (!response.error) {
- if (url.url === '/web/session/eval_domain_and_context') {
- self.test_eval(params, response.result);
- }
deferred.resolve(response["result"], textStatus, jqXHR);
} else if (response.error.data.type === "session_invalid") {
self.uid = false;
} else {
deferred.reject(response.error, $.Event());
}
- }
- ).fail(
+ },
function(jqXHR, textStatus, errorThrown) {
if (! options.shadow)
self.trigger('response_failed', jqXHR);
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index 524a88bdc48..bbff633c687 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -61,8 +61,10 @@ instance.web.Query = instance.web.Class.extend({
return instance.session.rpc('/web/dataset/search_read', {
model: this._model.name,
fields: this._fields || false,
- domain: this._model.domain(this._filter),
- context: this._model.context(this._context),
+ domain: instance.web.pyeval.eval('domains',
+ [this._model.domain(this._filter)]),
+ context: instance.web.pyeval.eval('contexts',
+ [this._model.context(this._context)]),
offset: this._offset,
limit: this._limit,
sort: instance.web.serialize_sort(this._order_by)
@@ -121,9 +123,8 @@ instance.web.Query = instance.web.Class.extend({
var self = this;
- // FIXME: when pyeval is merged
- var ctx = instance.session.test_eval_contexts(
- [this._model.context(this._context)]);
+ var ctx = instance.web.pyeval.eval(
+ 'context', this._model.context(this._context));
return this._model.call('read_group', {
groupby: grouping,
fields: _.uniq(grouping.concat(this._fields || [])),
@@ -289,6 +290,7 @@ instance.web.Model = instance.web.Class.extend({
kwargs = args;
args = [];
}
+ instance.web.pyeval.ensure_evaluated(args, kwargs);
var debug = instance.session.debug ? '/'+this.name+':'+method : '';
return instance.session.rpc('/web/dataset/call_kw' + debug, {
model: this.name,
@@ -301,7 +303,7 @@ instance.web.Model = instance.web.Class.extend({
* Fetches a Query instance bound to this model, for searching
*
* @param {Array<String>} [fields] fields to ultimately fetch during the search
- * @returns {openerp.web.Query}
+ * @returns {instance.web.Query}
*/
query: function (fields) {
return new instance.web.Query(this, fields);
@@ -349,9 +351,11 @@ instance.web.Model = instance.web.Class.extend({
* FIXME: remove when evaluator integrated
*/
call_button: function (method, args) {
+ instance.web.pyeval.ensure_evaluated(args, {});
return instance.session.rpc('/web/dataset/call_button', {
model: this.name,
method: method,
+ // Should not be necessary anymore. Integrate remote in this?
domain_id: null,
context_id: args.length - 1,
args: args || []
@@ -606,7 +610,8 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
return instance.session.rpc('/web/dataset/resequence', {
model: this.model,
ids: ids,
- context: this.get_context(options.context),
+ context: instance.web.pyeval.eval(
+ 'context', this.get_context(options.context)),
}).then(function (results) {
return results;
});
diff --git a/addons/web/static/src/js/pyeval.js b/addons/web/static/src/js/pyeval.js
new file mode 100644
index 00000000000..265449f9f64
--- /dev/null
+++ b/addons/web/static/src/js/pyeval.js
@@ -0,0 +1,774 @@
+/*
+ * py.js helpers and setup
+ */
+openerp.web.pyeval = function (instance) {
+ instance.web.pyeval = {};
+
+ var obj = function () {};
+ obj.prototype = py.object;
+ var asJS = function (arg) {
+ if (arg instanceof obj) {
+ return arg.toJSON();
+ }
+ return arg;
+ };
+
+ var datetime = py.PY_call(py.object);
+
+ /**
+ * computes (Math.floor(a/b), a%b and passes that to the callback.
+ *
+ * returns the callback's result
+ */
+ var divmod = function (a, b, fn) {
+ var mod = a%b;
+ // in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a
+ // round of b
+ if (mod > 0 && b < 0 || mod < 0 && b > 0) {
+ mod += b;
+ }
+ return fn(Math.floor(a/b), mod);
+ };
+ /**
+ * Passes the fractional and integer parts of x to the callback, returns
+ * the callback's result
+ */
+ var modf = function (x, fn) {
+ var mod = x%1;
+ if (mod < 0) {
+ mod += 1;
+ }
+ return fn(mod, Math.floor(x));
+ };
+ var zero = py.float.fromJSON(0);
+
+ // Port from pypy/lib_pypy/datetime.py
+ var DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+ var DAYS_BEFORE_MONTH = [null];
+ var dbm = 0;
+ for (var i=1; i<DAYS_IN_MONTH.length; ++i) {
+ DAYS_BEFORE_MONTH.push(dbm);
+ dbm += DAYS_IN_MONTH[i];
+ }
+ var is_leap = function (year) {
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
+ };
+ var days_before_year = function (year) {
+ var y = year - 1;
+ return y*365 + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400);
+ };
+ var days_in_month = function (year, month) {
+ if (month === 2 && is_leap(year)) {
+ return 29;
+ }
+ return DAYS_IN_MONTH[month];
+ };
+ var days_before_month = function (year, month) {
+ var post_leap_feb = month > 2 && is_leap(year);
+ return DAYS_BEFORE_MONTH[month]
+ + (post_leap_feb ? 1 : 0);
+ };
+ var ymd2ord = function (year, month, day) {
+ var dim = days_in_month(year, month);
+ if (!(1 <= day && day <= dim)) {
+ throw new Error("ValueError: day must be in 1.." + dim);
+ }
+ return days_before_year(year)
+ + days_before_month(year, month)
+ + day;
+ };
+ var DI400Y = days_before_year(401);
+ var DI100Y = days_before_year(101);
+ var DI4Y = days_before_year(5);
+ var assert = function (bool) {
+ if (!bool) {
+ throw new Error("AssertionError");
+ }
+ };
+ var ord2ymd = function (n) {
+ --n;
+ var n400, n100, n4, n1, n0;
+ divmod(n, DI400Y, function (_n400, n) {
+ n400 = _n400;
+ divmod(n, DI100Y, function (_n100, n) {
+ n100 = _n100;
+ divmod(n, DI4Y, function (_n4, n) {
+ n4 = _n4;
+ divmod(n, 365, function (_n1, n) {
+ n1 = _n1;
+ n0 = n;
+ })
+ });
+ });
+ });
+
+ n = n0;
+ var year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1;
+ if (n1 == 4 || n100 == 100) {
+ assert(n0 === 0);
+ return {
+ year: year - 1,
+ month: 12,
+ day: 31
+ };
+ }
+
+ var leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);
+ assert(leapyear == is_leap(year));
+ var month = (n + 50) >> 5;
+ var preceding = DAYS_BEFORE_MONTH[month] + ((month > 2 && leapyear) ? 1 : 0);
+ if (preceding > n) {
+ --month;
+ preceding -= DAYS_IN_MONTH[month] + ((month === 2 && leapyear) ? 1 : 0);
+ }
+ n -= preceding;
+ return {
+ year: year,
+ month: month,
+ day: n+1
+ };
+ };
+
+ /**
+ * Converts the stuff passed in into a valid date, applying overflows as needed
+ */
+ var tmxxx = function (year, month, day, hour, minute, second, microsecond) {
+ hour = hour || 0; minute = minute || 0; second = second || 0;
+ microsecond = microsecond || 0;
+
+ if (microsecond < 0 || microsecond > 999999) {
+ divmod(microsecond, 1000000, function (carry, ms) {
+ microsecond = ms;
+ second += carry
+ });
+ }
+ if (second < 0 || second > 59) {
+ divmod(second, 60, function (carry, s) {
+ second = s;
+ minute += carry;
+ });
+ }
+ if (minute < 0 || minute > 59) {
+ divmod(minute, 60, function (carry, m) {
+ minute = m;
+ hour += carry;
+ })
+ }
+ if (hour < 0 || hour > 23) {
+ divmod(hour, 24, function (carry, h) {
+ hour = h;
+ day += carry;
+ })
+ }
+ // That was easy. Now it gets muddy: the proper range for day
+ // can't be determined without knowing the correct month and year,
+ // but if day is, e.g., plus or minus a million, the current month
+ // and year values make no sense (and may also be out of bounds
+ // themselves).
+ // Saying 12 months == 1 year should be non-controversial.
+ if (month < 1 || month > 12) {
+ divmod(month-1, 12, function (carry, m) {
+ month = m + 1;
+ year += carry;
+ })
+ }
+ // Now only day can be out of bounds (year may also be out of bounds
+ // for a datetime object, but we don't care about that here).
+ // If day is out of bounds, what to do is arguable, but at least the
+ // method here is principled and explainable.
+ var dim = days_in_month(year, month);
+ if (day < 1 || day > dim) {
+ // Move day-1 days from the first of the month. First try to
+ // get off cheap if we're only one day out of range (adjustments
+ // for timezone alone can't be worse than that).
+ if (day === 0) {
+ --month;
+ if (month > 0) {
+ day = days_in_month(year, month);
+ } else {
+ --year; month=12; day=31;
+ }
+ } else if (day == dim + 1) {
+ ++month;
+ day = 1;
+ if (month > 12) {
+ month = 1;
+ ++year;
+ }
+ } else {
+ var r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));
+ year = r.year;
+ month = r.month;
+ day = r.day;
+ }
+ }
+ return {
+ year: year,
+ month: month,
+ day: day,
+ hour: hour,
+ minute: minute,
+ second: second,
+ microsecond: microsecond
+ };
+ };
+ datetime.timedelta = py.type('timedelta', null, {
+ __init__: function () {
+ var args = py.PY_parseArgs(arguments, [
+ ['days', zero], ['seconds', zero], ['microseconds', zero],
+ ['milliseconds', zero], ['minutes', zero], ['hours', zero],
+ ['weeks', zero]
+ ]);
+
+ var d = 0, s = 0, m = 0;
+ var days = args.days.toJSON() + args.weeks.toJSON() * 7;
+ var seconds = args.seconds.toJSON()
+ + args.minutes.toJSON() * 60
+ + args.hours.toJSON() * 3600;
+ var microseconds = args.microseconds.toJSON()
+ + args.milliseconds.toJSON() * 1000;
+
+ // Get rid of all fractions, and normalize s and us.
+ // Take a deep breath <wink>.
+ var daysecondsfrac = modf(days, function (dayfrac, days) {
+ d = days;
+ if (dayfrac) {
+ return modf(dayfrac * 24 * 3600, function (dsf, dsw) {
+ s = dsw;
+ return dsf;
+ });
+ }
+ return 0;
+ });
+
+ var secondsfrac = modf(seconds, function (sf, s) {
+ seconds = s;
+ return sf + daysecondsfrac;
+ });
+ divmod(seconds, 24*3600, function (days, seconds) {
+ d += days;
+ s += seconds
+ });
+ // seconds isn't referenced again before redefinition
+
+ microseconds += secondsfrac * 1e6;
+ divmod(microseconds, 1000000, function (seconds, microseconds) {
+ divmod(seconds, 24*3600, function (days, seconds) {
+ d += days;
+ s += seconds;
+ m += Math.round(microseconds);
+ });
+ });
+
+ // Carrying still possible here?
+
+ this.days = d;
+ this.seconds = s;
+ this.microseconds = m;
+ },
+ __str__: function () {
+ var hh, mm, ss;
+ divmod(this.seconds, 60, function (m, s) {
+ divmod(m, 60, function (h, m) {
+ hh = h;
+ mm = m;
+ ss = s;
+ });
+ });
+ var s = _.str.sprintf("%d:%02d:%02d", hh, mm, ss);
+ if (this.days) {
+ s = _.str.sprintf("%d day%s, %s",
+ this.days,
+ (this.days != 1 && this.days != -1) ? 's' : '',
+ s);
+ }
+ if (this.microseconds) {
+ s = _.str.sprintf("%s.%06d", s, this.microseconds);
+ }
+ return py.str.fromJSON(s);
+ },
+ __eq__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.False;
+ }
+ return (this.days === other.days
+ && this.seconds === other.seconds
+ && this.microseconds === other.microseconds)
+ ? py.True : py.False;
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(this.days + other.days),
+ py.float.fromJSON(this.seconds + other.seconds),
+ py.float.fromJSON(this.microseconds + other.microseconds)
+ ]);
+ },
+ __radd__: function (other) { return this.__add__(other); },
+ __sub__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(this.days - other.days),
+ py.float.fromJSON(this.seconds - other.seconds),
+ py.float.fromJSON(this.microseconds - other.microseconds)
+ ]);
+ },
+ __rsub__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ return this.__neg__().__add__(other);
+ },
+ __neg__: function () {
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(-this.days),
+ py.float.fromJSON(-this.seconds),
+ py.float.fromJSON(-this.microseconds)
+ ]);
+ },
+ __pos__: function () { return this; },
+ __mul__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ var n = other.toJSON();
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(this.days * n),
+ py.float.fromJSON(this.seconds * n),
+ py.float.fromJSON(this.microseconds * n)
+ ]);
+ },
+ __rmul__: function (other) { return this.__mul__(other); },
+ __div__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ var usec = ((this.days * 24 * 3600) + this.seconds) * 1000000
+ + this.microseconds;
+ return py.PY_call(
+ datetime.timedelta, [
+ zero, zero, py.float.fromJSON(usec / other.toJSON())]);
+ },
+ __floordiv__: function (other) { return this.__div__(other); },
+ total_seconds: function () {
+ return py.float.fromJSON(
+ this.days * 86400
+ + this.seconds
+ + this.microseconds / 1000000)
+ },
+ __nonzero__: function () {
+ return (!!this.days || !!this.seconds || !!this.microseconds)
+ ? py.True
+ : py.False;
+ }
+ });
+ datetime.datetime = py.type('datetime', null, {
+ __init__: function () {
+ var zero = py.float.fromJSON(0);
+ var args = py.PY_parseArgs(arguments, [
+ 'year', 'month', 'day',
+ ['hour', zero], ['minute', zero], ['second', zero],
+ ['microsecond', zero], ['tzinfo', py.None]
+ ]);
+ for(var key in args) {
+ if (!args.hasOwnProperty(key)) { continue; }
+ this[key] = asJS(args[key]);
+ }
+ },
+ strftime: function () {
+ var self = this;
+ var args = py.PY_parseArgs(arguments, 'format');
+ return py.str.fromJSON(args.format.toJSON()
+ .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);
+ case 'H': return _.str.sprintf('%02d', self.hour);
+ case 'M': return _.str.sprintf('%02d', self.minute);
+ case 'S': return _.str.sprintf('%02d', self.second);
+ }
+ throw new Error('ValueError: No known conversion for ' + m);
+ }));
+ },
+ now: py.classmethod.fromJSON(function () {
+ var d = new Date();
+ return py.PY_call(datetime.datetime,
+ [d.getFullYear(), d.getMonth() + 1, d.getDate(),
+ d.getHours(), d.getMinutes(), d.getSeconds(),
+ d.getMilliseconds() * 1000]);
+ }),
+ today: py.classmethod.fromJSON(function () {
+ var d = new Date();
+ return py.PY_call(datetime.datetime,
+ [d.getFullYear(), d.getMonth() + 1, d.getDate()]);
+ }),
+ combine: py.classmethod.fromJSON(function () {
+ var args = py.PY_parseArgs(arguments, 'date time');
+ return py.PY_call(datetime.datetime, [
+ py.PY_getAttr(args.date, 'year'),
+ py.PY_getAttr(args.date, 'month'),
+ py.PY_getAttr(args.date, 'day'),
+ py.PY_getAttr(args.time, 'hour'),
+ py.PY_getAttr(args.time, 'minute'),
+ py.PY_getAttr(args.time, 'second')
+ ]);
+ })
+ });
+ datetime.date = py.type('date', null, {
+ __init__: function () {
+ var args = py.PY_parseArgs(arguments, 'year month day');
+ this.year = asJS(args.year);
+ this.month = asJS(args.month);
+ this.day = asJS(args.day);
+ },
+ strftime: function () {
+ var self = this;
+ var args = py.PY_parseArgs(arguments, 'format');
+ return py.str.fromJSON(args.format.toJSON()
+ .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);
+ }));
+ },
+ today: py.classmethod.fromJSON(function () {
+ var d = new Date();
+ return py.PY_call(
+ datetime.date, [d.getFullYear(), d.getMonth() + 1, d.getDate()]);
+ }),
+ __eq__: function (other) {
+ return (this.year === other.year
+ && this.month === other.month
+ && this.day === other.day)
+ ? py.True : py.False;
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ var s = tmxxx(this.year, this.month, this.day + other.days);
+ return datetime.date.fromJSON(s.year, s.month, s.day);
+ },
+ __radd__: function (other) { return this.__add__(other); },
+ __sub__: function (other) {
+ if (py.PY_isInstance(other, datetime.timedelta)) {
+ return this.__add__(other.__neg__());
+ }
+ if (py.PY_isInstance(other, datetime.date)) {
+ // FIXME: getattr and sub API methods
+ return py.PY_call(datetime.timedelta, [
+ py.PY_subtract(
+ py.PY_call(py.PY_getAttr(this, 'toordinal')),
+ py.PY_call(py.PY_getAttr(other, 'toordinal')))
+ ]);
+ }
+ return py.NotImplemented;
+ },
+ toordinal: function () {
+ return py.float.fromJSON(ymd2ord(this.year, this.month, this.day));
+ },
+ fromJSON: function (year, month, day) {
+ return py.PY_call(datetime.date, [year, month, day])
+ }
+ });
+ datetime.time = py.type('time', null, {
+ __init__: function () {
+ var zero = py.float.fromJSON(0);
+ var args = py.PY_parseArgs(arguments, [
+ ['hour', zero], ['minute', zero], ['second', zero], ['microsecond', zero],
+ ['tzinfo', py.None]
+ ]);
+
+ for(var k in args) {
+ if (!args.hasOwnProperty(k)) { continue; }
+ this[k] = asJS(args[k]);
+ }
+ }
+ });
+ var time = py.PY_call(py.object);
+ time.strftime = py.PY_def.fromJSON(function () {
+ var args = py.PY_parseArgs(arguments, 'format');
+ var dt_class = py.PY_getAttr(datetime, 'datetime');
+ var d = py.PY_call(py.PY_getAttr(dt_class, 'now'));
+ return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
+ });
+
+ var relativedelta = py.type('relativedelta', null, {
+ __init__: function () {
+ this.ops = py.PY_parseArgs(arguments,
+ '* year month day hour minute second microsecond '
+ + 'years months weeks days hours minutes secondes microseconds '
+ + 'weekday leakdays yearday nlyearday');
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, 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?
+ // FIXME: use date.replace
+ return py.PY_call(datetime.date, [
+ py.float.fromJSON(year),
+ py.float.fromJSON(month),
+ py.float.fromJSON(day)
+ ]);
+ },
+ __radd__: function (other) {
+ return this.__add__(other);
+ },
+
+ __sub__: function (other) {
+ if (!py.PY_isInstance(other, 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 py.PY_call(datetime.date, [
+ py.float.fromJSON(year),
+ py.float.fromJSON(month),
+ py.float.fromJSON(day)
+ ]);
+ },
+ __rsub__: function (other) {
+ return this.__sub__(other);
+ }
+ });
+
+ var eval_contexts = function (contexts, evaluation_context) {
+ evaluation_context = evaluation_context || {};
+ return _(contexts).reduce(function (result_context, ctx) {
+ // __eval_context evaluations can lead to some of `contexts`'s
+ // values being null, skip them as well as empty contexts
+ if (_.isEmpty(ctx)) { return result_context; }
+ if (_.isString(ctx)) {
+ // wrap raw strings in context
+ ctx = { __ref: 'context', __debug: ctx };
+ }
+ var evaluated = ctx;
+ switch(ctx.__ref) {
+ case 'context':
+ evaluation_context.context = py.dict.fromJSON(evaluation_context);
+ evaluated = py.eval(ctx.__debug, evaluation_context);
+ break;
+ case 'compound_context':
+ var eval_context = eval_contexts([ctx.__eval_context]);
+ evaluated = eval_contexts(
+ ctx.__contexts, _.extend({}, evaluation_context, eval_context));
+ break;
+ }
+ // add newly evaluated context to evaluation context for following
+ // siblings
+ _.extend(evaluation_context, evaluated);
+ return _.extend(result_context, evaluated);
+ }, _.extend({}, instance.session.user_context));
+ };
+ var eval_domains = function (domains, evaluation_context) {
+ var result_domain = [];
+ _(domains).each(function (domain) {
+ if (_.isString(domain)) {
+ // wrap raw strings in domain
+ domain = { __ref: 'domain', __debug: domain };
+ }
+ switch(domain.__ref) {
+ case 'domain':
+ evaluation_context.context = py.dict.fromJSON(evaluation_context);
+ result_domain.push.apply(
+ result_domain, py.eval(domain.__debug, evaluation_context));
+ break;
+ case 'compound_domain':
+ var eval_context = eval_contexts([domain.__eval_context]);
+ result_domain.push.apply(
+ result_domain, eval_domains(
+ domain.__domains, _.extend(
+ {}, evaluation_context, eval_context)));
+ break;
+ default:
+ result_domain.push.apply(result_domain, domain);
+ }
+ });
+ return result_domain;
+ };
+ var eval_groupbys = function (contexts, evaluation_context) {
+ var result_group = [];
+ _(contexts).each(function (ctx) {
+ if (_.isString(ctx)) {
+ // wrap raw strings in context
+ ctx = { __ref: 'context', __debug: ctx };
+ }
+ var group;
+ var evaluated = ctx;
+ switch(ctx.__ref) {
+ case 'context':
+ evaluation_context.context = py.dict.fromJSON(evaluation_context);
+ evaluated = py.eval(ctx.__debug, evaluation_context);
+ break;
+ case 'compound_context':
+ var eval_context = eval_contexts([ctx.__eval_context]);
+ evaluated = eval_contexts(
+ ctx.__contexts, _.extend({}, evaluation_context, eval_context));
+ break;
+ }
+ group = evaluated.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) + '}}');
+ }
+ _.extend(evaluation_context, evaluated);
+ });
+ return result_group;
+ };
+
+ instance.web.pyeval.context = function () {
+ return {
+ uid: py.float.fromJSON(instance.session.uid),
+ datetime: datetime,
+ time: time,
+ relativedelta: relativedelta,
+ current_date: py.PY_call(
+ time.strftime, [py.str.fromJSON('%Y-%m-%d')]),
+ };
+ };
+
+ /**
+ * @param {String} type "domains", "contexts" or "groupbys"
+ * @param {Array} object domains or contexts to evaluate
+ * @param {Object} [context] evaluation context
+ */
+ instance.web.pyeval.eval = function (type, object, context) {
+ context = _.extend(instance.web.pyeval.context(), context || {});
+ context['context'] = py.dict.fromJSON(context);
+
+ //noinspection FallthroughInSwitchStatementJS
+ switch(type) {
+ case 'context': object = [object];
+ case 'contexts': return eval_contexts(object, context);
+ case 'domain': object = [object];
+ case 'domains': return eval_domains(object, context);
+ case 'groupbys': return eval_groupbys(object, context);
+ }
+ throw new Error("Unknow evaluation type " + type)
+ };
+
+ var eval_arg = function (arg) {
+ if (typeof arg !== 'object' || !arg.__ref) { return arg; }
+ switch(arg.__ref) {
+ case 'domain': case 'compound_domain':
+ return instance.web.pyeval.eval('domains', [arg]);
+ case 'context': case 'compound_context':
+ return instance.web.pyeval.eval('contexts', [arg]);
+ default:
+ throw new Error(instance.web._t("Unknown nonliteral type " + arg.__ref));
+ }
+ };
+ /**
+ * If args or kwargs are unevaluated contexts or domains (compound or not),
+ * evaluated them in-place.
+ *
+ * Potentially mutates both parameters.
+ *
+ * @param args
+ * @param kwargs
+ */
+ instance.web.pyeval.ensure_evaluated = function (args, kwargs) {
+ for (var i=0; i<args.length; ++i) {
+ args[i] = eval_arg(args[i]);
+ }
+ for (var k in kwargs) {
+ if (!kwargs.hasOwnProperty(k)) { continue; }
+ kwargs[k] = eval_arg(kwargs[k]);
+ }
+ };
+ instance.web.pyeval.eval_domains_and_contexts = function (source) {
+ return new $.Deferred(function (d) {setTimeout(function () {
+ try {
+ var contexts = ([instance.session.user_context] || []).concat(source.contexts);
+ // see Session.eval_context in Python
+ d.resolve({
+ context: instance.web.pyeval.eval('contexts', contexts),
+ domain: instance.web.pyeval.eval('domains', source.domains),
+ group_by: instance.web.pyeval.eval('groupbys', source.group_by_seq || [])
+ });
+ } catch (e) {
+ d.resolve({ error: {
+ code: 400,
+ message: instance.web._t("Evaluation Error"),
+ data: {
+ type: 'local_exception',
+ debug: _.str.sprintf(
+ instance.web._t("Local evaluation failure\n%s\n\n%s"),
+ e.message, JSON.stringify(source))
+ }
+ }});
+ }
+ }, 0); });
+ }
+};
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index 88d45793173..5b59c7d6912 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -341,17 +341,19 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
if (this.headless) {
this.ready.resolve();
} else {
- var load_view = this.rpc("/web/searchview/load", {
+ var load_view = this.rpc("/web/view/load", {
model: this.model,
view_id: this.view_id,
- context: this.dataset.get_context() });
-
- $.when(load_view)
- .then(function(r) {
- self.search_view_loaded(r)
- }, function () {
- self.ready.reject.apply(null, arguments);
- });
+ view_type: 'search',
+ context: instance.web.pyeval.eval(
+ 'context', this.dataset.get_context())
+ });
+
+ $.when(load_view).then(function (r) {
+ return self.search_view_loaded(r)
+ }).fail(function () {
+ self.ready.reject.apply(null, arguments);
+ });
}
instance.web.bus.on('click', this, function(ev) {
@@ -615,16 +617,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
search_view_loaded: function(data) {
var self = this;
- this.fields_view = data.fields_view;
- if (data.fields_view.type !== 'search' ||
- data.fields_view.arch.tag !== 'search') {
+ this.fields_view = data;
+ if (data.type !== 'search' ||
+ data.arch.tag !== 'search') {
throw new Error(_.str.sprintf(
"Got non-search view after asking for a search view: type %s, arch root %s",
- data.fields_view.type, data.fields_view.arch.tag));
+ data.type, data.arch.tag));
}
this.make_widgets(
- data.fields_view['arch'].children,
- data.fields_view.fields);
+ data['arch'].children,
+ data.fields);
this.add_common_inputs();
@@ -632,6 +634,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
var drawer_started = $.when.apply(
null, _(this.select_for_drawer()).invoke(
'appendTo', this.$('.oe_searchview_drawer')));
+
// load defaults
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
@@ -1506,16 +1509,10 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
this.$el.on('click', 'h4', function () {
self.$el.toggleClass('oe_opened');
});
- // FIXME: local eval of domain and context to get rid of special endpoint
- return this.rpc('/web/searchview/get_filters', {
- model: this.view.model
- })
+ return this.model.call('get_filters', [this.view.model])
.then(this.proxy('set_filters'))
- .then(function () {
- self.is_ready.resolve(null);
- }, function () {
- self.is_ready.reject();
- });
+ .done(function () { self.is_ready.resolve(); })
+ .fail(function () { self.is_ready.reject.apply(self.is_ready, arguments); });
},
/**
* Special implementation delaying defaults until CustomFilters is loaded
@@ -1614,7 +1611,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
var search = this.view.build_search_data();
- this.rpc('/web/session/eval_domain_and_context', {
+ instance.web.pyeval.eval_domains_and_contexts({
domains: search.domains,
contexts: search.contexts,
group_by_seq: search.groupbys || []
@@ -1708,10 +1705,10 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
});
return $.when(
this._super(),
- this.rpc("/web/searchview/fields_get", {model: this.view.model}).done(function(data) {
+ new instance.web.Model(this.view.model).call('fields_get').done(function(data) {
self.fields = _.extend({
id: { string: 'ID', type: 'id' }
- }, data.fields);
+ }, data);
})).done(function () {
self.append_proposition();
});
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
index bf39f543734..5aeeeb4de78 100644
--- a/addons/web/static/src/js/testing.js
+++ b/addons/web/static/src/js/testing.js
@@ -2,7 +2,8 @@
openerp.testing = {};
(function (testing) {
var dependencies = {
- corelib: [],
+ pyeval: [],
+ corelib: ['pyeval'],
coresetup: ['corelib'],
data: ['corelib', 'coresetup'],
dates: [],
@@ -270,6 +271,9 @@ openerp.testing = {};
openerp.web[module](instance);
});
}
+ if (instance.session) {
+ instance.session.uid = 42;
+ }
if (_.isNumber(opts.asserts)) {
expect(opts.asserts);
}
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 06c1a3d989b..b4f59499b9b 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -436,26 +436,26 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var method = call[1];
if (!_.str.trim(call[2])) {
- return {method: method, args: [], context_index: null}
+ return {method: method, args: []}
}
var argument_replacement = {
'False': function () {return false;},
'True': function () {return true;},
'None': function () {return null;},
- 'context': function (i) {
- context_index = i;
- var ctx = new instance.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {});
- return ctx;
+ 'context': function () {
+ return new instance.web.CompoundContext(
+ self.dataset.get_context(),
+ widget.build_context() ? widget.build_context() : {});
}
};
- var parent_fields = null, context_index = null;
+ var parent_fields = null;
var args = _.map(call[2].split(','), function (a, i) {
var field = _.str.trim(a);
// literal constant or context
if (field in argument_replacement) {
- return argument_replacement[field](i);
+ return argument_replacement[field]();
}
// literal number
if (/^-?\d+(\.\d+)?$/.test(field)) {
@@ -490,8 +490,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return {
method: method,
- args: args,
- context_index: context_index
+ args: args
};
},
do_onchange: function(widget, processed) {
@@ -510,18 +509,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var on_change = widget.node.attrs.on_change;
if (on_change) {
var change_spec = self.parse_on_change(on_change, widget);
- def = self.rpc('/web/dataset/onchange', {
- model: self.dataset.model,
- method: change_spec.method,
- args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(change_spec.args),
- context_id: change_spec.context_index == undefined ? null : change_spec.context_index + 1
- });
+ var id = [self.datarecord.id == null ? [] : [self.datarecord.id]];
+ def = new instance.web.Model(self.dataset.model).call(
+ change_spec.method, id.concat(change_spec.args));
} else {
def = $.when({});
}
return def.then(function(response) {
if (widget.field['change_default']) {
- var fieldname = widget.name
+ var fieldname = widget.name;
var value_;
if (response.value && (fieldname in response.value)) {
// Use value from onchange if onchange executed
@@ -533,11 +529,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var condition = fieldname + '=' + value_;
if (value_) {
- return self.rpc('/web/dataset/call', {
- model: 'ir.values',
- method: 'get_defaults',
- args: [self.model, condition]
- }).then(function (results) {
+ return new instance.web.Model('ir.values').call(
+ 'get_defaults', [self.model, condition]
+ ).then(function (results) {
if (!results.length) {
return response;
}
@@ -4591,7 +4585,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
var self = this;
this.init_dataset();
if (this.options.initial_view == "search") {
- self.rpc('/web/session/eval_domain_and_context', {
+ instance.web.pyeval.eval_domains_and_contexts({
domains: [],
contexts: [this.context]
}).done(function (results) {
@@ -4664,7 +4658,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
},
do_search: function(domains, contexts, groupbys) {
var self = this;
- this.rpc('/web/session/eval_domain_and_context', {
+ instance.web.pyeval.eval_domains_and_contexts({
domains: domains || [],
contexts: contexts || [],
group_by_seq: groupbys || []
diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js
index 8265be77ba1..4d08da385fd 100644
--- a/addons/web/static/src/js/view_tree.js
+++ b/addons/web/static/src/js/view_tree.js
@@ -44,7 +44,8 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
view_id: this.view_id,
view_type: "tree",
toolbar: this.view_manager ? !!this.view_manager.sidebar : false,
- context: this.dataset.get_context()
+ context: instance.web.pyeval.eval(
+ 'context', this.dataset.get_context())
}).done(this.on_loaded);
},
/**
@@ -227,8 +228,9 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
return this.rpc('/web/treeview/action', {
id: id,
model: this.dataset.model,
- context: new instance.web.CompoundContext(
- this.dataset.get_context(), local_context)
+ context: instance.web.pyeval.eval(
+ 'context', new instance.web.CompoundContext(
+ this.dataset.get_context(), local_context))
}).then(function (actions) {
if (!actions.length) { return; }
var action = actions[0][2];
@@ -236,7 +238,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
if (action.context) {
c.add(action.context);
}
- return self.rpc('/web/session/eval_domain_and_context', {
+ return instance.web.pyeval.eval_domains_and_contexts({
contexts: [c], domains: []
}).then(function (res) {
action.context = res.context;
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index 600a0b065c1..d7dcd590218 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -256,7 +256,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
on_close: function() {},
action_menu_id: null,
});
- if (_.isString(action) && instance.web.client_actions.contains(action)) {
+ if (action === false) {
+ action = { type: 'ir.actions.act_window_close' };
+ } else if (_.isString(action) && instance.web.client_actions.contains(action)) {
var action_client = { type: "ir.actions.client", tag: action, params: {} };
return this.do_action(action_client, options);
} else if (_.isNumber(action) || _.isString(action)) {
@@ -265,6 +267,17 @@ instance.web.ActionManager = instance.web.Widget.extend({
return self.do_action(result, options);
});
}
+
+ // Ensure context & domain are evaluated and can be manipulated/used
+ if (action.context) {
+ action.context = instance.web.pyeval.eval(
+ 'context', action.context);
+ }
+ if (action.domain) {
+ action.domain = instance.web.pyeval.eval(
+ 'domain', action.domain);
+ }
+
if (!action.type) {
console.error("No type for action", action);
return $.Deferred().reject();
@@ -382,6 +395,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
options.on_close();
}
this.dialog_stop();
+ return $.when();
},
ir_actions_server: function (action, options) {
var self = this;
@@ -395,29 +409,36 @@ instance.web.ActionManager = instance.web.Widget.extend({
ir_actions_report_xml: function(action, options) {
var self = this;
instance.web.blockUI();
- self.rpc("/web/session/eval_domain_and_context", {
+ return instance.web.pyeval.eval_domains_and_contexts({
contexts: [action.context],
domains: []
- }).done(function(res) {
+ }).then(function(res) {
action = _.clone(action);
action.context = res.context;
var c = instance.webclient.crashmanager;
- self.session.get_file({
- url: '/web/report',
- data: {action: JSON.stringify(action)},
- complete: instance.web.unblockUI,
- success: function(){
- if (!self.dialog) {
- options.on_close();
+ return $.Deferred(function (d) {
+ self.session.get_file({
+ url: '/web/report',
+ data: {action: JSON.stringify(action)},
+ complete: instance.web.unblockUI,
+ success: function(){
+ if (!self.dialog) {
+ options.on_close();
+ }
+ self.dialog_stop();
+ d.resolve();
+ },
+ error: function () {
+ c.rpc_error.apply(c, arguments);
+ d.reject();
}
- self.dialog_stop();
- },
- error: c.rpc_error.bind(c)
- })
+ })
+ });
});
},
ir_actions_act_url: function (action) {
window.open(action.url, action.target === 'self' ? '_self' : '_blank');
+ return $.when();
},
});
@@ -666,7 +687,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
var self = this,
controller = this.views[this.active_view].controller,
action_context = this.action.context || {};
- this.rpc('/web/session/eval_domain_and_context', {
+ instance.web.pyeval.eval_domains_and_contexts({
domains: [this.action.domain || []].concat(domains || []),
contexts: [action_context].concat(contexts || []),
group_by_seq: groupbys || []
@@ -880,7 +901,11 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
nested: true,
}
};
- this.session.get_file({ url: '/web/report', data: {action: JSON.stringify(action)}, complete: instance.web.unblockUI });
+ this.session.get_file({
+ url: '/web/report',
+ data: {action: JSON.stringify(action)},
+ complete: instance.web.unblockUI
+ });
}
break;
default:
@@ -1069,14 +1094,17 @@ instance.web.Sidebar = instance.web.Widget.extend({
active_id: ids[0],
active_ids: ids,
active_model: self.getParent().dataset.model
- };
+ };
+ var c = instance.web.pyeval.eval('context',
+ new instance.web.CompoundContext(
+ sidebar_eval_context, active_ids_context));
self.rpc("/web/action/load", {
action_id: item.action.id,
- context: active_ids_context,
- eval_context: new instance.web.CompoundContext(sidebar_eval_context, active_ids_context),
+ context: c
}).done(function(result) {
- console.log(result.context);
- result.context = new instance.web.CompoundContext(result.context || {}, active_ids_context);
+ result.context = new instance.web.CompoundContext(
+ result.context || {}, active_ids_context)
+ .set_eval_context(c);
result.flags = result.flags || {};
result.flags.new_window = true;
self.do_action(result, {
@@ -1137,7 +1165,6 @@ instance.web.Sidebar = instance.web.Widget.extend({
}
},
on_attachment_delete: function(e) {
- var self = this;
e.preventDefault();
e.stopPropagation();
var self = this;
@@ -1182,7 +1209,8 @@ instance.web.View = instance.web.Widget.extend({
"view_id": this.view_id,
"view_type": this.view_type,
toolbar: !!this.options.$sidebar,
- context: this.dataset.get_context(context)
+ context: instance.web.pyeval.eval(
+ 'context', this.dataset.get_context(context))
});
}
return view_loaded.then(function(r) {
@@ -1226,8 +1254,7 @@ instance.web.View = instance.web.Widget.extend({
};
var context = new instance.web.CompoundContext(dataset.get_context(), action_data.context || {});
- var handler = function (r) {
- var action = r;
+ var handler = function (action) {
if (action && action.constructor == Object) {
var ncontext = new instance.web.CompoundContext(context);
if (record_id) {
@@ -1238,18 +1265,10 @@ instance.web.View = instance.web.Widget.extend({
});
}
ncontext.add(action.context || {});
- return self.rpc('/web/session/eval_domain_and_context', {
- contexts: [ncontext],
- domains: []
- }).then(function (results) {
- action.context = results.context;
- /* niv: previously we were overriding once more with action_data.context,
- * I assumed this was not a correct behavior and removed it
- */
- return self.do_action(action, {
- on_close: result_handler,
- });
- }, null);
+ action.context = ncontext;
+ return self.do_action(action, {
+ on_close: result_handler,
+ });
} else {
self.do_action({"type":"ir.actions.act_window_close"});
return result_handler();
@@ -1271,11 +1290,15 @@ instance.web.View = instance.web.Widget.extend({
}
}
args.push(context);
- return dataset.call_button(action_data.name, args).done(handler);
+ return dataset.call_button(action_data.name, args).then(handler);
} else if (action_data.type=="action") {
- return this.rpc('/web/action/load', { action_id: action_data.name, context: context, do_not_eval: true}).done(handler);
+ return this.rpc('/web/action/load', {
+ action_id: action_data.name,
+ context: instance.web.pyeval.eval('context', context),
+ do_not_eval: true
+ }).then(handler);
} else {
- return dataset.exec_workflow(record_id, action_data.name).done(handler);
+ return dataset.exec_workflow(record_id, action_data.name).then(handler);
}
},
/**
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 9aa36595cba..62d8a872f25 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -929,11 +929,11 @@
</li>
<li t-if="widget.node.attrs.context" data-item="context">
<span class="oe_tooltip_technical_title">Context:</span>
- <t t-esc="widget.node.attrs.context_string"/>
+ <t t-esc="widget.node.attrs.context"/>
</li>
<li t-if="widget.node.attrs.domain" data-item="domain">
<span class="oe_tooltip_technical_title">Domain:</span>
- <t t-esc="widget.node.attrs.domain_string"/>
+ <t t-esc="widget.node.attrs.domain"/>
</li>
<li t-if="widget.node.attrs.modifiers and widget.node.attrs.modifiers != '{}'" data-item="modifiers">
<span class="oe_tooltip_technical_title">Modifiers:</span>
diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js
index 6eec615b199..8e836dade7e 100644
--- a/addons/web/static/test/evals.js
+++ b/addons/web/static/test/evals.js
@@ -1,11 +1,566 @@
+openerp.testing.section('eval.types', {
+ dependencies: ['web.coresetup'],
+ setup: function (instance) {
+ instance.session.uid = 42;
+ }
+}, function (test) {
+ test('strftime', function (instance) {
+ var d = new Date();
+ var context = instance.web.pyeval.context();
+ strictEqual(
+ py.eval("time.strftime('%Y')", context),
+ String(d.getFullYear()));
+ strictEqual(
+ py.eval("time.strftime('%Y')+'-01-30'", context),
+ String(d.getFullYear()) + '-01-30');
+ strictEqual(
+ py.eval("time.strftime('%Y-%m-%d %H:%M:%S')", context),
+ _.str.sprintf('%04d-%02d-%02d %02d:%02d:%02d',
+ d.getFullYear(), d.getMonth() + 1, d.getDate(),
+ d.getHours(), d.getMinutes(), d.getSeconds()));
+ });
+ // Port from pypy/lib_pypy/test_datetime.py
+ var makeEq = function (instance, c2) {
+ var ctx = instance.web.pyeval.context();
+ var c = _.extend({ td: ctx.datetime.timedelta }, c2 || {});
+ return function (a, b, message) {
+ ok(py.eval(a + ' == ' + b, c), message);
+ };
+ };
+ test('timedelta.test_constructor', function (instance) {
+ var eq = makeEq(instance);
+
+ // keyword args to constructor
+ eq('td()', 'td(weeks=0, days=0, hours=0, minutes=0, seconds=0, ' +
+ 'milliseconds=0, microseconds=0)');
+ eq('td(1)', 'td(days=1)');
+ eq('td(0, 1)', 'td(seconds=1)');
+ eq('td(0, 0, 1)', 'td(microseconds=1)');
+ eq('td(weeks=1)', 'td(days=7)');
+ eq('td(days=1)', 'td(hours=24)');
+ eq('td(hours=1)', 'td(minutes=60)');
+ eq('td(minutes=1)', 'td(seconds=60)');
+ eq('td(seconds=1)', 'td(milliseconds=1000)');
+ eq('td(milliseconds=1)', 'td(microseconds=1000)');
+
+ // Check float args to constructor
+ eq('td(weeks=1.0/7)', 'td(days=1)');
+ eq('td(days=1.0/24)', 'td(hours=1)');
+ eq('td(hours=1.0/60)', 'td(minutes=1)');
+ eq('td(minutes=1.0/60)', 'td(seconds=1)');
+ eq('td(seconds=0.001)', 'td(milliseconds=1)');
+ eq('td(milliseconds=0.001)', 'td(microseconds=1)');
+ });
+ test('timedelta.test_computations', function (instance) {
+ var c = instance.web.pyeval.context();
+ var zero = py.float.fromJSON(0);
+ var eq = makeEq(instance, {
+ // one week
+ a: py.PY_call(c.datetime.timedelta, [
+ py.float.fromJSON(7)]),
+ // one minute
+ b: py.PY_call(c.datetime.timedelta, [
+ zero, py.float.fromJSON(60)]),
+ // one millisecond
+ c: py.PY_call(c.datetime.timedelta, [
+ zero, zero, py.float.fromJSON(1000)]),
+ });
+
+ eq('a+b+c', 'td(7, 60, 1000)');
+ eq('a-b', 'td(6, 24*3600 - 60)');
+ eq('-a', 'td(-7)');
+ eq('+a', 'td(7)');
+ eq('-b', 'td(-1, 24*3600 - 60)');
+ eq('-c', 'td(-1, 24*3600 - 1, 999000)');
+// eq('abs(a)', 'a');
+// eq('abs(-a)', 'a');
+ eq('td(6, 24*3600)', 'a');
+ eq('td(0, 0, 60*1000000)', 'b');
+ eq('a*10', 'td(70)');
+ eq('a*10', '10*a');
+ // eq('a*10L', '10*a');
+ eq('b*10', 'td(0, 600)');
+ eq('10*b', 'td(0, 600)');
+ // eq('b*10L', 'td(0, 600)');
+ eq('c*10', 'td(0, 0, 10000)');
+ eq('10*c', 'td(0, 0, 10000)');
+ // eq('c*10L', 'td(0, 0, 10000)');
+ eq('a*-1', '-a');
+ eq('b*-2', '-b-b');
+ eq('c*-2', '-c+-c');
+ eq('b*(60*24)', '(b*60)*24');
+ eq('b*(60*24)', '(60*b)*24');
+ eq('c*1000', 'td(0, 1)');
+ eq('1000*c', 'td(0, 1)');
+ eq('a//7', 'td(1)');
+ eq('b//10', 'td(0, 6)');
+ eq('c//1000', 'td(0, 0, 1)');
+ eq('a//10', 'td(0, 7*24*360)');
+ eq('a//3600000', 'td(0, 0, 7*24*1000)');
+
+ // Issue #11576
+ eq('td(999999999, 86399, 999999) - td(999999999, 86399, 999998)', 'td(0, 0, 1)');
+ eq('td(999999999, 1, 1) - td(999999999, 1, 0)',
+ 'td(0, 0, 1)')
+ });
+ test('timedelta.test_basic_attributes', function (instance) {
+ var ctx = instance.web.pyeval.context();
+ strictEqual(py.eval('datetime.timedelta(1, 7, 31).days', ctx), 1);
+ strictEqual(py.eval('datetime.timedelta(1, 7, 31).seconds', ctx), 7);
+ strictEqual(py.eval('datetime.timedelta(1, 7, 31).microseconds', ctx), 31);
+ });
+ test('timedelta.test_total_seconds', function (instance) {
+ var c = { timedelta: instance.web.pyeval.context().datetime.timedelta };
+ strictEqual(py.eval('timedelta(365).total_seconds()', c), 31536000);
+ strictEqual(
+ py.eval('timedelta(seconds=123456.789012).total_seconds()', c),
+ 123456.789012);
+ strictEqual(
+ py.eval('timedelta(seconds=-123456.789012).total_seconds()', c),
+ -123456.789012);
+ strictEqual(
+ py.eval('timedelta(seconds=0.123456).total_seconds()', c), 0.123456);
+ strictEqual(py.eval('timedelta().total_seconds()', c), 0);
+ strictEqual(
+ py.eval('timedelta(seconds=1000000).total_seconds()', c), 1e6);
+ });
+ test('timedelta.test_str', function (instance) {
+ var c = { td: instance.web.pyeval.context().datetime.timedelta };
+
+ strictEqual(py.eval('str(td(1))', c), "1 day, 0:00:00");
+ strictEqual(py.eval('str(td(-1))', c), "-1 day, 0:00:00");
+ strictEqual(py.eval('str(td(2))', c), "2 days, 0:00:00");
+ strictEqual(py.eval('str(td(-2))', c), "-2 days, 0:00:00");
+
+ strictEqual(py.eval('str(td(hours=12, minutes=58, seconds=59))', c),
+ "12:58:59");
+ strictEqual(py.eval('str(td(hours=2, minutes=3, seconds=4))', c),
+ "2:03:04");
+ strictEqual(
+ py.eval('str(td(weeks=-30, hours=23, minutes=12, seconds=34))', c),
+ "-210 days, 23:12:34");
+
+ strictEqual(py.eval('str(td(milliseconds=1))', c), "0:00:00.001000");
+ strictEqual(py.eval('str(td(microseconds=3))', c), "0:00:00.000003");
+
+ strictEqual(
+ py.eval('str(td(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999))', c),
+ "999999999 days, 23:59:59.999999");
+ });
+ test('timedelta.test_massive_normalization', function (instance) {
+ var td = py.PY_call(
+ instance.web.pyeval.context().datetime.timedelta,
+ {microseconds: py.float.fromJSON(-1)});
+ strictEqual(td.days, -1);
+ strictEqual(td.seconds, 24 * 3600 - 1);
+ strictEqual(td.microseconds, 999999);
+ });
+ test('timedelta.test_bool', function (instance) {
+ var c = { td: instance.web.pyeval.context().datetime.timedelta };
+ ok(py.eval('bool(td(1))', c));
+ ok(py.eval('bool(td(0, 1))', c));
+ ok(py.eval('bool(td(0, 0, 1))', c));
+ ok(py.eval('bool(td(microseconds=1))', c));
+ ok(py.eval('bool(not td(0))', c));
+ });
+
+ test('date.test_computations', function (instance) {
+ var d = instance.web.pyeval.context().datetime;
+
+ var a = d.date.fromJSON(2002, 1, 31);
+ var b = d.date.fromJSON(1956, 1, 31);
+ strictEqual(
+ py.eval('(a - b).days', {a: a, b: b}),
+ 46 * 365 + 12);
+ strictEqual(py.eval('(a - b).seconds', {a: a, b: b}), 0);
+ strictEqual(py.eval('(a - b).microseconds', {a: a, b: b}), 0);
+
+ var day = py.PY_call(d.timedelta, [py.float.fromJSON(1)]);
+ var week = py.PY_call(d.timedelta, [py.float.fromJSON(7)]);
+ a = d.date.fromJSON(2002, 3, 2);
+ var ctx = {
+ a: a,
+ day: day,
+ week: week,
+ date: d.date
+ };
+ ok(py.eval('a + day == date(2002, 3, 3)', ctx));
+ ok(py.eval('day + a == date(2002, 3, 3)', ctx)); // 5
+ ok(py.eval('a - day == date(2002, 3, 1)', ctx));
+ ok(py.eval('-day + a == date(2002, 3, 1)', ctx));
+ ok(py.eval('a + week == date(2002, 3, 9)', ctx));
+ ok(py.eval('a - week == date(2002, 2, 23)', ctx));
+ ok(py.eval('a + 52*week == date(2003, 3, 1)', ctx)); // 10
+ ok(py.eval('a - 52*week == date(2001, 3, 3)', ctx));
+ ok(py.eval('(a + week) - a == week', ctx));
+ ok(py.eval('(a + day) - a == day', ctx));
+ ok(py.eval('(a - week) - a == -week', ctx));
+ ok(py.eval('(a - day) - a == -day', ctx)); // 15
+ ok(py.eval('a - (a + week) == -week', ctx));
+ ok(py.eval('a - (a + day) == -day', ctx));
+ ok(py.eval('a - (a - week) == week', ctx));
+ ok(py.eval('a - (a - day) == day', ctx));
+
+ raises(function () {
+ py.eval('a + 1', ctx);
+ }, /^Error: TypeError:/); // 20
+ raises(function () {
+ py.eval('a - 1', ctx);
+ }, /^Error: TypeError:/);
+ raises(function () {
+ py.eval('1 + a', ctx);
+ }, /^Error: TypeError:/);
+ raises(function () {
+ py.eval('1 - a', ctx);
+ }, /^Error: TypeError:/);
+
+ // delta - date is senseless.
+ raises(function () {
+ py.eval('day - a', ctx);
+ }, /^Error: TypeError:/);
+ // mixing date and (delta or date) via * or // is senseless
+ raises(function () {
+ py.eval('day * a', ctx);
+ }, /^Error: TypeError:/); // 25
+ raises(function () {
+ py.eval('a * day', ctx);
+ }, /^Error: TypeError:/);
+ raises(function () {
+ py.eval('day // a', ctx);
+ }, /^Error: TypeError:/);
+ raises(function () {
+ py.eval('a // day', ctx);
+ }, /^Error: TypeError:/);
+ raises(function () {
+ py.eval('a * a', ctx);
+ }, /^Error: TypeError:/);
+ raises(function () {
+ py.eval('a // a', ctx);
+ }, /^Error: TypeError:/); // 30
+ // date + date is senseless
+ raises(function () {
+ py.eval('a + a', ctx);
+ }, /^Error: TypeError:/);
+ });
+});
+openerp.testing.section('eval.edc', {
+ dependencies: ['web.data'],
+ rpc: 'mock',
+ setup: function (instance, $fix, mock) {
+ var user = { login: 'admin', id: 1, lang: 'en_US', tz: false };
+ instance.edc = function (domains, contexts) {
+ return instance.web.pyeval.eval_domains_and_contexts({
+ contexts: contexts || [],
+ domains: domains || []
+ });
+ };
+ mock('res.lang:load_lang', function () { return true; });
+ mock('res.users:write', function (args) {
+ _.extend(user, args[1]);
+ return true;
+ });
+ mock('/web/session/get_session_info', function () {
+ return {
+ session_id: 'foobar',
+ db: '3',
+ login: user.login,
+ uid: user.id,
+ context: {
+ uid: user.id,
+ lang: user.lang,
+ tz: user.tz
+ }
+ };
+ });
+ return instance.session.session_reload();
+ }
+}, function (test) {
+ test('empty, basic', {asserts: 3}, function (instance) {
+ return instance.edc().then(function (result) {
+ // default values for new db
+ deepEqual(result.context, {
+ lang: 'en_US',
+ tz: false,
+ uid: 1
+ });
+ deepEqual(result.domain, []);
+ deepEqual(result.group_by, []);
+ });
+ });
+ test('empty, context altered', {
+ asserts: 3,
+ setup: function (instance) {
+ var lang = new instance.web.Model('res.lang');
+ var users = new instance.web.Model('res.users');
+ return lang.call('load_lang', ['ru_RU']).then(function () {
+ return users.call('write', [instance.session.uid, {
+ lang: 'ru_RU',
+ tz: 'America/Santarem'
+ }]);
+ }).then(instance.session.session_reload.bind(instance.session));
+ }
+ }, function (instance) {
+ return instance.edc().then(function (result) {
+ // default values for new db
+ deepEqual(result.context, {
+ lang: 'ru_RU',
+ tz: 'America/Santarem',
+ uid: 1
+ });
+ deepEqual(result.domain, []);
+ deepEqual(result.group_by, []);
+ });
+ });
+ test('context_merge_00', {asserts: 1}, function (instance) {
+ var ctx = [
+ {
+ "__contexts": [
+ { "lang": "en_US", "tz": false, "uid": 1 },
+ {
+ "active_id": 8,
+ "active_ids": [ 8 ],
+ "active_model": "sale.order",
+ "bin_raw": true,
+ "default_composition_mode": "comment",
+ "default_model": "sale.order",
+ "default_res_id": 8,
+ "default_template_id": 18,
+ "default_use_template": true,
+ "edi_web_url_view": "faaaake",
+ "lang": "en_US",
+ "mark_so_as_sent": null,
+ "show_address": null,
+ "tz": false,
+ "uid": null
+ },
+ {}
+ ],
+ "__eval_context": null,
+ "__ref": "compound_context"
+ },
+ { "active_id": 9, "active_ids": [ 9 ], "active_model": "mail.compose.message" }
+ ];
+ return instance.edc([], ctx).then(function (result) {
+ deepEqual(result.context, {
+ active_id: 9,
+ active_ids: [9],
+ active_model: 'mail.compose.message',
+ bin_raw: true,
+ default_composition_mode: 'comment',
+ default_model: 'sale.order',
+ default_res_id: 8,
+ default_template_id: 18,
+ default_use_template: true,
+ edi_web_url_view: "faaaake",
+ lang: 'en_US',
+ mark_so_as_sent: null,
+ show_address: null,
+ tz: false,
+ uid: null
+ });
+ });
+ });
+ test('context_merge_01', {asserts: 1}, function (instance) {
+ var ctx = [{
+ "__contexts": [
+ {
+ "lang": "en_US",
+ "tz": false,
+ "uid": 1
+ },
+ {
+ "default_attachment_ids": [],
+ "default_body": "",
+ "default_content_subtype": "html",
+ "default_model": "res.users",
+ "default_parent_id": false,
+ "default_res_id": 1
+ },
+ {}
+ ],
+ "__eval_context": null,
+ "__ref": "compound_context"
+ }];
+ return instance.edc([], ctx).then(function (result) {
+ deepEqual(result.context, {
+ "default_attachment_ids": [],
+ "default_body": "",
+ "default_content_subtype": "html",
+ "default_model": "res.users",
+ "default_parent_id": false,
+ "default_res_id": 1,
+ "lang": "en_US",
+ "tz": false,
+ "uid": 1
+ });
+ });
+ });
+});
+openerp.testing.section('eval.edc.nonliterals', {
+ dependencies: ['web.data'],
+ setup: function (instance) {
+ instance.session.user_context = {
+ lang: 'en_US',
+ tz: false,
+ uid: 1
+ };
+ _.extend(instance, {
+ edc: function (domains, contexts) {
+ return instance.web.pyeval.eval_domains_and_contexts({
+ contexts: contexts || [],
+ domains: domains || []
+ });
+ }
+ });
+ }
+}, function (test) {
+ test('domain with time', {asserts: 1}, function (instance) {
+ return instance.edc([
+ [['type', '=', 'contract']],
+ { "__domains": [["|"], [["state", "in", ["open", "draft"]]], [["state", "=", "pending"]]],
+ "__eval_context": null,
+ "__ref": "compound_domain"
+ },
+ "['|', '&', ('date', '!=', False), ('date', '<=', time.strftime('%Y-%m-%d')), ('is_overdue_quantity', '=', True)]",
+ [['user_id', '=', 1]]
+ ]).then(function (result) {
+ var d = new Date();
+ var today = _.str.sprintf("%04d-%02d-%02d",
+ d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
+ deepEqual(result.domain, [
+ ["type", "=", "contract"],
+ "|", ["state", "in", ["open", "draft"]],
+ ["state", "=", "pending"],
+ "|",
+ "&", ["date", "!=", false],
+ ["date", "<=", today],
+ ["is_overdue_quantity", "=", true],
+ ["user_id", "=", 1]
+ ]);
+ });
+ });
+ test('conditional context', {asserts: 2}, function (instance) {
+ var d = {
+ __ref: 'domain',
+ __debug: "[('company_id', '=', context.get('company_id',False))]"
+ };
+ var e1 = instance.edc([d]).then(function (result) {
+ deepEqual(result.domain, [
+ ['company_id', '=', false]
+ ]);
+ });
+ var cd = new instance.web.CompoundDomain(d);
+ cd.set_eval_context({company_id: 42});
+ var e2 = instance.edc([cd]).then(function (result) {
+ deepEqual(result.domain, [
+ ['company_id', '=', 42]
+ ]);
+ });
+
+ return $.when(e1, e2);
+ });
+ test('substitution in context', {asserts: 1}, function (instance) {
+ var c = "{'default_opportunity_id': active_id, 'default_duration': 1.0, 'lng': lang}";
+ var cc = new instance.web.CompoundContext(c);
+ cc.set_eval_context({active_id: 42});
+ return instance.edc([], [cc]).then(function (result) {
+ deepEqual(result.context, {
+ lang: "en_US",
+ tz: false,
+ uid: 1,
+ default_opportunity_id: 42,
+ default_duration: 1.0,
+ lng: "en_US"
+ });
+ });
+ });
+ test('date', {asserts: 1}, function (instance) {
+ var d = "[('state','!=','cancel'),('opening_date','>',datetime.date.today().strftime('%Y-%m-%d'))]";
+ return instance.edc([d]).then(function (result) {
+ var d = new Date();
+ var today = _.str.sprintf("%04d-%02d-%02d",
+ d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
+ deepEqual(result.domain, [
+ ['state', '!=', 'cancel'],
+ ['opening_date', '>', today]
+ ]);
+ });
+ });
+ test('delta', {asserts: 1}, function (instance) {
+ var d = "[('type','=','in'),('day','<=', time.strftime('%Y-%m-%d')),('day','>',(datetime.date.today()-datetime.timedelta(days=15)).strftime('%Y-%m-%d'))]";
+ return instance.edc([d]).then(function (result) {
+ var d = new Date();
+ var today = _.str.sprintf("%04d-%02d-%02d",
+ d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
+ d.setDate(d.getDate() - 15);
+ var ago_15_d = _.str.sprintf("%04d-%02d-%02d",
+ d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
+ deepEqual(result.domain, [
+ ['type', '=', 'in'],
+ ['day', '<=', today],
+ ['day', '>', ago_15_d]
+ ]);
+ });
+ });
+ test('horror from the deep', {asserts: 1}, function (instance) {
+ var cs = [
+ {"__ref": "compound_context",
+ "__contexts": [
+ {"__ref": "context", "__debug": "{'k': 'foo,' + str(context.get('test_key', False))}"},
+ {"__ref": "compound_context",
+ "__contexts": [
+ {"lang": "en_US", "tz": false, "uid": 1},
+ {"lang": "en_US", "tz": false, "uid": 1,
+ "active_model": "sale.order", "default_type": "out",
+ "show_address": 1, "contact_display": "partner_address",
+ "active_ids": [9], "active_id": 9},
+ {}
+ ], "__eval_context": null },
+ {"active_id": 8, "active_ids": [8],
+ "active_model": "stock.picking.out"},
+ {"__ref": "context", "__debug": "{'default_ref': 'stock.picking.out,'+str(context.get('active_id', False))}", "__id": "54d6ad1d6c45"}
+ ], "__eval_context": null}
+ ];
+ return instance.edc([], cs).then(function (result) {
+ deepEqual(result.context, {
+ k: 'foo,False',
+ lang: 'en_US',
+ tz: false,
+ uid: 1,
+ active_model: 'stock.picking.out',
+ active_id: 8,
+ active_ids: [8],
+ default_type: 'out',
+ show_address: 1,
+ contact_display: 'partner_address',
+ default_ref: 'stock.picking.out,8'
+ });
+ });
+ });
+});
openerp.testing.section('eval.contexts', {
dependencies: ['web.coresetup']
}, function (test) {
+ test('context_recursive', function (instance) {
+ var context_to_eval = [{
+ __ref: 'context',
+ __debug: '{"foo": context.get("bar", "qux")}'
+ }];
+ deepEqual(
+ instance.web.pyeval.eval('contexts', context_to_eval, {bar: "ok"}),
+ {foo: 'ok'});
+ deepEqual(
+ instance.web.pyeval.eval('contexts', context_to_eval, {bar: false}),
+ {foo: false});
+ deepEqual(
+ instance.web.pyeval.eval('contexts', context_to_eval),
+ {foo: 'qux'});
+ });
test('context_sequences', function (instance) {
// Context n should have base evaluation context + all of contexts
// 0..n-1 in its own evaluation context
var active_id = 4;
- var result = instance.session.test_eval_contexts([
+ var result = instance.web.pyeval.eval('contexts', [
{
"__contexts": [
{
@@ -49,7 +604,7 @@ openerp.testing.section('eval.contexts', {
});
});
test('non-literal_eval_contexts', function (instance) {
- var result = instance.session.test_eval_contexts([{
+ var result = instance.web.pyeval.eval('contexts', [{
"__ref": "compound_context",
"__contexts": [
{"__ref": "context", "__debug": "{'type':parent.type}",
@@ -127,17 +682,101 @@ openerp.testing.section('eval.contexts', {
deepEqual(result, {type: 'out_invoice'});
});
});
-openerp.testing.section('eval.contexts', {
+openerp.testing.section('eval.domains', {
dependencies: ['web.coresetup', 'web.dates']
}, function (test) {
test('current_date', function (instance) {
var current_date = instance.web.date_to_str(new Date());
- var result = instance.session.test_eval_domains(
+ var result = instance.web.pyeval.eval('domains',
[[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
- instance.session.test_eval_get_context());
+ instance.web.pyeval.context());
deepEqual(result, [
['name', '>=', current_date],
['name', '<=', current_date]
]);
- })
+ });
+ test('context_freevar', function (instance) {
+ var domains_to_eval = [{
+ __ref: 'domain',
+ __debug: '[("foo", "=", context.get("bar", "qux"))]'
+ }, [['bar', '>=', 42]]];
+ deepEqual(
+ instance.web.pyeval.eval('domains', domains_to_eval, {bar: "ok"}),
+ [['foo', '=', 'ok'], ['bar', '>=', 42]]);
+ deepEqual(
+ instance.web.pyeval.eval('domains', domains_to_eval, {bar: false}),
+ [['foo', '=', false], ['bar', '>=', 42]]);
+ deepEqual(
+ instance.web.pyeval.eval('domains', domains_to_eval),
+ [['foo', '=', 'qux'], ['bar', '>=', 42]]);
+ });
+});
+openerp.testing.section('eval.groupbys', {
+ dependencies: ['web.coresetup']
+}, function (test) {
+ test('groupbys_00', function (instance) {
+ var result = instance.web.pyeval.eval('groupbys', [
+ {group_by: 'foo'},
+ {group_by: ['bar', 'qux']},
+ {group_by: null},
+ {group_by: 'grault'}
+ ]);
+ deepEqual(result, ['foo', 'bar', 'qux', 'grault']);
+ });
+ test('groupbys_01', function (instance) {
+ var result = instance.web.pyeval.eval('groupbys', [
+ {group_by: 'foo'},
+ { __ref: 'context', __debug: '{"group_by": "bar"}' },
+ {group_by: 'grault'}
+ ]);
+ deepEqual(result, ['foo', 'bar', 'grault']);
+ });
+ test('groupbys_02', function (instance) {
+ var result = instance.web.pyeval.eval('groupbys', [
+ {group_by: 'foo'},
+ {
+ __ref: 'compound_context',
+ __contexts: [ {group_by: 'bar'} ],
+ __eval_context: null
+ },
+ {group_by: 'grault'}
+ ]);
+ deepEqual(result, ['foo', 'bar', 'grault']);
+ });
+ test('groupbys_03', function (instance) {
+ var result = instance.web.pyeval.eval('groupbys', [
+ {group_by: 'foo'},
+ {
+ __ref: 'compound_context',
+ __contexts: [
+ { __ref: 'context', __debug: '{"group_by": value}' }
+ ],
+ __eval_context: { value: 'bar' }
+ },
+ {group_by: 'grault'}
+ ]);
+ deepEqual(result, ['foo', 'bar', 'grault']);
+ });
+ test('groupbys_04', function (instance) {
+ var result = instance.web.pyeval.eval('groupbys', [
+ {group_by: 'foo'},
+ {
+ __ref: 'compound_context',
+ __contexts: [
+ { __ref: 'context', __debug: '{"group_by": value}' }
+ ],
+ __eval_context: { value: 'bar' }
+ },
+ {group_by: 'grault'}
+ ], { value: 'bar' });
+ deepEqual(result, ['foo', 'bar', 'grault']);
+ });
+ test('groupbys_05', function (instance) {
+ var result = instance.web.pyeval.eval('groupbys', [
+ {group_by: 'foo'},
+ { __ref: 'context', __debug: '{"group_by": value}' },
+ {group_by: 'grault'}
+ ], { value: 'bar' });
+ deepEqual(result, ['foo', 'bar', 'grault']);
+ });
});
diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js
index 20534eff76a..45f6d2c715b 100644
--- a/addons/web/static/test/search.js
+++ b/addons/web/static/test/search.js
@@ -152,9 +152,9 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
instance.dummy = {};
instance.dummy.DummyWidget = instance.web.search.Field.extend(
dummy_widget_attributes || {});
- if (!('/web/searchview/load' in instance.session.responses)) {
- instance.session.responses['/web/searchview/load'] = function () {
- return {fields_view: {
+ if (!('/web/view/load' in instance.session.responses)) {
+ instance.session.responses['/web/view/load'] = function () {
+ return {
type: 'search',
fields: {
dummy: {type: 'char', string: "Dummy"}
@@ -171,16 +171,16 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
children: []
}]
}
- }};
+ };
};
}
- instance.session.responses['/web/searchview/get_filters'] = function () {
+ instance.session.responses['ir.filters:get_filters'] = function () {
return [];
};
- instance.session.responses['/web/searchview/fields_get'] = function () {
- return {fields: {
+ instance.session.responses['dummy.model:fields_get'] = function () {
+ return {
dummy: {type: 'char', string: 'Dummy'}
- }};
+ };
};
var dataset = {model: 'dummy.model', get_context: function () { return {}; }};
@@ -890,9 +890,9 @@ openerp.testing.section('filters', {
rpc: 'mock',
templates: true,
setup: function (instance, $s, mock) {
- mock('/web/searchview/load', function () {
+ mock('/web/view/load', function () {
// view with a single group of filters
- return {fields_view: {
+ return {
type: 'search',
fields: {},
arch: {
@@ -915,7 +915,7 @@ openerp.testing.section('filters', {
children: []
}]
}
- }};
+ };
});
}
}, function (test) {
@@ -992,7 +992,7 @@ openerp.testing.section('saved_filters', {
}, function (test) {
test('checkboxing', {asserts: 6}, function (instance, $fix, mock) {
var view = makeSearchView(instance);
- mock('/web/searchview/get_filters', function () {
+ mock('ir.filters:get_filters', function () {
return [{ name: "filter name", user_id: 42 }];
});
@@ -1015,7 +1015,7 @@ openerp.testing.section('saved_filters', {
});
test('removal', {asserts: 1}, function (instance, $fix, mock) {
var view = makeSearchView(instance);
- mock('/web/searchview/get_filters', function () {
+ mock('ir.filters:get_filters', function () {
return [{ name: "filter name", user_id: 42 }];
});
diff --git a/addons/web/static/test/test.html.THIS b/addons/web/static/test/test.html.THIS
new file mode 100644
index 00000000000..f5f29f88d7c
--- /dev/null
+++ b/addons/web/static/test/test.html.THIS
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>OpenERP Web Test Suite</title>
+ <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
+
+ <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css">
+ <script src="/web/static/lib/qunit/qunit.js" type="text/javascript"></script>
+
+ <script src="/web/static/lib/underscore/underscore.js" type="text/javascript"></script>
+ <script src="/web/static/lib/underscore/underscore.string.js" type="text/javascript"></script>
+ <script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script>
+
+ <!-- jquery -->
+ <script src="/web/static/lib/jquery/jquery-1.7.2.js"></script>
+ <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js"></script>
+ <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
+
+ <script src="/web/static/lib/datejs/globalization/en-US.js"></script>
+ <script src="/web/static/lib/datejs/core.js"></script>
+ <script src="/web/static/lib/datejs/parser.js"></script>
+ <script src="/web/static/lib/datejs/sugarpak.js"></script>
+ <script src="/web/static/lib/datejs/extras.js"></script>
+
+ <script src="/web/static/lib/qweb/qweb2.js"></script>
+
+ <script src="/web/static/lib/py.js/lib/py.js"></script>
+
+ <script src="/web/static/src/js/boot.js"></script>
+ <script src="/web/static/src/js/pyeval.js"></script>
+ <script src="/web/static/src/js/corelib.js"></script>
+ <script src="/web/static/src/js/coresetup.js"></script>
+ <script src="/web/static/src/js/dates.js"></script>
+ <script src="/web/static/src/js/formats.js"></script>
+ <script src="/web/static/src/js/chrome.js"></script>
+ <script src="/web/static/src/js/data.js"></script>
+ <script src="/web/static/src/js/views.js"></script>
+ <script src="/web/static/src/js/search.js"></script>
+ <script src="/web/static/src/js/view_form.js"></script>
+ <script src="/web/static/src/js/view_list.js"></script>
+ <script src="/web/static/src/js/view_list_editable.js"></script>
+
+ <script src="/web/static/test/testing.js"></script>
+ <script type="text/javascript">
+ QUnit.config.testTimeout = 2000;
+ </script>
+</head>
+ <body id="oe" class="openerp">
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+ </body>
+ <script type="text/javascript" src="/web/static/test/class.js"></script>
+ <script type="text/javascript" src="/web/static/test/registry.js"></script>
+ <script type="text/javascript" src="/web/static/test/form.js"></script>
+ <script type="text/javascript" src="/web/static/test/list-utils.js"></script>
+ <script type="text/javascript" src="/web/static/test/formats.js"></script>
+ <script type="text/javascript" src="/web/static/test/rpc.js"></script>
+ <script type="text/javascript" src="/web/static/test/evals.js"></script>
+ <script type="text/javascript" src="/web/static/test/search.js"></script>
+ <script type="text/javascript" src="/web/static/test/Widget.js"></script>
+ <script type="text/javascript" src="/web/static/test/list-editable.js"></script>
+</html>
diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py
index 8cb2118e0b6..d7abb8e5a8c 100644
--- a/addons/web/tests/__init__.py
+++ b/addons/web/tests/__init__.py
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
-from . import test_dataset, test_menu, test_serving_base, test_view, test_js
+from . import test_dataset, test_menu, test_serving_base, test_js
fast_suite = []
checks = [
test_dataset,
test_menu,
test_serving_base,
- test_view,
]
diff --git a/addons/web/tests/test_dataset.py b/addons/web/tests/test_dataset.py
index 1169ff5f0b9..a6faf9e6df1 100644
--- a/addons/web/tests/test_dataset.py
+++ b/addons/web/tests/test_dataset.py
@@ -18,14 +18,14 @@ class TestDataSetController(unittest2.TestCase):
self.dataset.do_search_read(self.request, 'fake.model'),
{'records': [], 'length': 0})
self.read.assert_called_once_with(
- [], False, self.request.session.eval_context())
+ [], False, self.request.context)
def test_regular_find(self):
self.search.return_value = [1, 2, 3]
self.dataset.do_search_read(self.request, 'fake.model')
self.read.assert_called_once_with(
- [1, 2, 3], False,self.request.session.eval_context())
+ [1, 2, 3], False,self.request.context)
def test_ids_shortcut(self):
self.search.return_value = [1, 2, 3]
diff --git a/addons/web/tests/test_menu.py b/addons/web/tests/test_menu.py
index c5e1b948b67..75d1d009408 100644
--- a/addons/web/tests/test_menu.py
+++ b/addons/web/tests/test_menu.py
@@ -4,7 +4,6 @@ import mock
import unittest2
from ..controllers import main
-from ..session import OpenERPSession
class Placeholder(object):
def __init__(self, **kwargs):
@@ -40,11 +39,11 @@ class LoadTest(unittest2.TestCase):
root = self.menu.do_load(self.request)
self.MockMenus.search.assert_called_with(
- [], 0, False, False, self.request.session.eval_context())
+ [], 0, False, False, self.request.context)
self.MockMenus.read.assert_called_with(
[], ['name', 'sequence', 'parent_id',
'action', 'needaction_enabled', 'needaction_counter'],
- self.request.session.eval_context())
+ self.request.context)
self.assertListEqual(
root['children'],
@@ -63,7 +62,7 @@ class LoadTest(unittest2.TestCase):
self.MockMenus.read.assert_called_with(
[1, 2, 3], ['name', 'sequence', 'parent_id',
'action', 'needaction_enabled', 'needaction_counter'],
- self.request.session.eval_context())
+ self.request.context)
self.assertEqual(
root['children'],
diff --git a/addons/web/tests/test_view.py b/addons/web/tests/test_view.py
deleted file mode 100644
index 51377099558..00000000000
--- a/addons/web/tests/test_view.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import copy
-import xml.etree.ElementTree
-import mock
-
-import unittest2
-import simplejson
-
-import openerp.addons.web.controllers.main
-from .. import nonliterals, session as s
-
-def field_attrs(fields_view_get, fieldname):
- (field,) = filter(lambda f: f['attrs'].get('name') == fieldname,
- fields_view_get['arch']['children'])
- return field['attrs']
-
-#noinspection PyCompatibility
-class DomainsAndContextsTest(unittest2.TestCase):
- def setUp(self):
- self.view = openerp.addons.web.controllers.main.View()
-
- def test_convert_literal_domain(self):
- e = xml.etree.ElementTree.Element(
- 'field', domain=" [('somefield', '=', 3)] ")
- self.view.parse_domains_and_contexts(e, None)
-
- self.assertEqual(
- e.get('domain'),
- [('somefield', '=', 3)])
-
- def test_convert_complex_domain(self):
- e = xml.etree.ElementTree.Element(
- 'field',
- domain="[('account_id.type','in',['receivable','payable']),"
- "('reconcile_id','=',False),"
- "('reconcile_partial_id','=',False),"
- "('state', '=', 'valid')]"
- )
- self.view.parse_domains_and_contexts(e, None)
-
- self.assertEqual(
- e.get('domain'),
- [('account_id.type', 'in', ['receivable', 'payable']),
- ('reconcile_id', '=', False),
- ('reconcile_partial_id', '=', False),
- ('state', '=', 'valid')]
- )
-
- def test_retrieve_nonliteral_domain(self):
- session = mock.Mock(spec=s.OpenERPSession)
- session.domains_store = {}
- domain_string = ("[('month','=',(datetime.date.today() - "
- "datetime.timedelta(365/12)).strftime('%%m'))]")
- e = xml.etree.ElementTree.Element(
- 'field', domain=domain_string)
-
- self.view.parse_domains_and_contexts(e, session)
-
- self.assertIsInstance(e.get('domain'), nonliterals.Domain)
- self.assertEqual(
- nonliterals.Domain(
- session, key=e.get('domain').key).get_domain_string(),
- domain_string)
-
- def test_convert_literal_context(self):
- e = xml.etree.ElementTree.Element(
- 'field', context=" {'some_prop': 3} ")
- self.view.parse_domains_and_contexts(e, None)
-
- self.assertEqual(
- e.get('context'),
- {'some_prop': 3})
-
- def test_convert_complex_context(self):
- e = xml.etree.ElementTree.Element(
- 'field',
- context="{'account_id.type': ['receivable','payable'],"
- "'reconcile_id': False,"
- "'reconcile_partial_id': False,"
- "'state': 'valid'}"
- )
- self.view.parse_domains_and_contexts(e, None)
-
- self.assertEqual(
- e.get('context'),
- {'account_id.type': ['receivable', 'payable'],
- 'reconcile_id': False,
- 'reconcile_partial_id': False,
- 'state': 'valid'}
- )
-
- def test_retrieve_nonliteral_context(self):
- session = mock.Mock(spec=s.OpenERPSession)
- session.contexts_store = {}
- context_string = ("{'month': (datetime.date.today() - "
- "datetime.timedelta(365/12)).strftime('%%m')}")
- e = xml.etree.ElementTree.Element(
- 'field', context=context_string)
-
- self.view.parse_domains_and_contexts(e, session)
-
- self.assertIsInstance(e.get('context'), nonliterals.Context)
- self.assertEqual(
- nonliterals.Context(
- session, key=e.get('context').key).get_context_string(),
- context_string)
-
-class AttrsNormalizationTest(unittest2.TestCase):
- def setUp(self):
- self.view = openerp.addons.web.controllers.main.View()
-
- def test_identity(self):
- web_view = """
- <form string="Title">
- <group>
- <field name="some_field"/>
- <field name="some_other_field"/>
- </group>
- <field name="stuff"/>
- </form>
- """
-
- pristine = xml.etree.ElementTree.fromstring(web_view)
- transformed = self.view.transform_view(web_view, None)
-
- self.assertEqual(
- xml.etree.ElementTree.tostring(transformed),
- xml.etree.ElementTree.tostring(pristine)
- )
diff --git a/addons/web_calendar/i18n/es_EC.po b/addons/web_calendar/i18n/es_EC.po
index a5a1fe9638f..312e325e79c 100644
--- a/addons/web_calendar/i18n/es_EC.po
+++ b/addons/web_calendar/i18n/es_EC.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_calendar
#. openerp-web
diff --git a/addons/web_calendar/i18n/es_MX.po b/addons/web_calendar/i18n/es_MX.po
new file mode 100644
index 00000000000..6663a170261
--- /dev/null
+++ b/addons/web_calendar/i18n/es_MX.po
@@ -0,0 +1,203 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 15:51+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:151
+#, python-format
+msgid "New event"
+msgstr "Nuevo evento"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:154
+#, python-format
+msgid "Details"
+msgstr "Más"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:152
+#, python-format
+msgid "Save"
+msgstr "Guardar"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:147
+#, python-format
+msgid "Today"
+msgstr "Hoy"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:149
+#, python-format
+msgid "Week"
+msgstr "Semana"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:161
+#, python-format
+msgid "Full day"
+msgstr "Día completo"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:159
+#: code:addons/web_calendar/static/src/js/calendar.js:172
+#, python-format
+msgid "Description"
+msgstr "Descripción"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:158
+#, python-format
+msgid "Event will be deleted permanently, are you sure?"
+msgstr "¿El evento sera eliminado permanentemente, esta seguro?"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/xml/web_calendar.xml:8
+#: code:addons/web_calendar/static/src/xml/web_calendar.xml:9
+#, python-format
+msgid "&nbsp;"
+msgstr "&nbsp;"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:171
+#, python-format
+msgid "Date"
+msgstr "Fecha"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:468
+#, python-format
+msgid "Edit: "
+msgstr "Editar: "
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:148
+#, python-format
+msgid "Day"
+msgstr "Día"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:155
+#, python-format
+msgid "Edit"
+msgstr "Editar"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:167
+#, python-format
+msgid "Enabled"
+msgstr "Habilitado"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:164
+#, python-format
+msgid "Do you want to edit the whole set of repeated events?"
+msgstr "¿Quiere editar todos los eventos repetidos?"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:80
+#, python-format
+msgid "Filter"
+msgstr "Filtro"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:165
+#, python-format
+msgid "Repeat event"
+msgstr "Repetir evento"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:170
+#: code:addons/web_calendar/static/src/js/calendar.js:178
+#, python-format
+msgid "Agenda"
+msgstr "Agenda"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:160
+#, python-format
+msgid "Time period"
+msgstr "Periodo de tiempo"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:156
+#, python-format
+msgid "Delete"
+msgstr "Borrar"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:150
+#, python-format
+msgid "Month"
+msgstr "Mes"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:166
+#, python-format
+msgid "Disabled"
+msgstr "Deshabilitado/a"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:433
+#, python-format
+msgid "Create: "
+msgstr "Crear: "
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:175
+#, python-format
+msgid "Year"
+msgstr "Año"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:153
+#, python-format
+msgid "Cancel"
+msgstr "Cancelar"
+
+#. module: web_calendar
+#. openerp-web
+#: code:addons/web_calendar/static/src/js/calendar.js:28
+#, python-format
+msgid "Calendar"
+msgstr "Calendario"
diff --git a/addons/web_calendar/i18n/fa.po b/addons/web_calendar/i18n/fa.po
index d0ec75941c4..1249c9a5238 100644
--- a/addons/web_calendar/i18n/fa.po
+++ b/addons/web_calendar/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_calendar
#. openerp-web
diff --git a/addons/web_diagram/i18n/es_MX.po b/addons/web_diagram/i18n/es_MX.po
new file mode 100644
index 00000000000..94797d3d364
--- /dev/null
+++ b/addons/web_diagram/i18n/es_MX.po
@@ -0,0 +1,104 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 15:54+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:254
+#: code:addons/web_diagram/static/src/js/diagram.js:319
+#, python-format
+msgid "Open: "
+msgstr "Abrir: "
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:217
+#, python-format
+msgid ""
+"Deleting this node cannot be undone.\n"
+"It will also delete all connected transitions.\n"
+"\n"
+"Are you sure ?"
+msgstr ""
+"Eliminar este nodo no puede ser revertido.\n"
+"También eliminara todas las transisiones conectadas."
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/xml/base_diagram.xml:13
+#, python-format
+msgid "New Node"
+msgstr "Nuevo Nodo"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:312
+#: code:addons/web_diagram/static/src/js/diagram.js:331
+#, python-format
+msgid "Transition"
+msgstr "Transición"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:11
+#, python-format
+msgid "Diagram"
+msgstr "Diagrama"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:246
+#: code:addons/web_diagram/static/src/js/diagram.js:280
+#, python-format
+msgid "Activity"
+msgstr "Actividad"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:422
+#, python-format
+msgid "%d / %d"
+msgstr "%d / %d"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:285
+#: code:addons/web_diagram/static/src/js/diagram.js:337
+#, python-format
+msgid "Create:"
+msgstr "Crear:"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:187
+#, python-format
+msgid "Are you sure?"
+msgstr "¿Está Seguro?"
+
+#. module: web_diagram
+#. openerp-web
+#: code:addons/web_diagram/static/src/js/diagram.js:235
+#, python-format
+msgid ""
+"Deleting this transition cannot be undone.\n"
+"\n"
+"Are you sure ?"
+msgstr ""
+"Eliminar esta transision no puede ser revertido.\n"
+"\n"
+"¿Está seguro?"
diff --git a/addons/web_diagram/i18n/fa.po b/addons/web_diagram/i18n/fa.po
index 30e2ab40dc3..6fc9cf7d9e2 100644
--- a/addons/web_diagram/i18n/fa.po
+++ b/addons/web_diagram/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_diagram
#. openerp-web
diff --git a/addons/web_gantt/i18n/es_MX.po b/addons/web_gantt/i18n/es_MX.po
new file mode 100644
index 00000000000..a54ad0629df
--- /dev/null
+++ b/addons/web_gantt/i18n/es_MX.po
@@ -0,0 +1,32 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 15:54+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_gantt
+#. openerp-web
+#: code:addons/web_gantt/static/src/xml/web_gantt.xml:10
+#, python-format
+msgid "Create"
+msgstr "Crear"
+
+#. module: web_gantt
+#. openerp-web
+#: code:addons/web_gantt/static/src/js/gantt.js:11
+#, python-format
+msgid "Gantt"
+msgstr "Gantt"
diff --git a/addons/web_gantt/i18n/fa.po b/addons/web_gantt/i18n/fa.po
index 47163d22562..a1928179ead 100644
--- a/addons/web_gantt/i18n/fa.po
+++ b/addons/web_gantt/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_gantt
#. openerp-web
diff --git a/addons/web_gantt/static/src/js/gantt.js b/addons/web_gantt/static/src/js/gantt.js
index 627942d0dbc..ebbf04926ed 100644
--- a/addons/web_gantt/static/src/js/gantt.js
+++ b/addons/web_gantt/static/src/js/gantt.js
@@ -22,10 +22,11 @@ instance.web_gantt.GanttView = instance.web.View.extend({
var self = this;
this.fields_view = fields_view_get;
this.$el.addClass(this.fields_view.arch.attrs['class']);
- return this.rpc("/web/searchview/fields_get", {"model": this.dataset.model}).then(function(fields_get) {
- self.fields = fields_get.fields;
- self.has_been_loaded.resolve();
- });
+ return new instance.web.Model(this.dataset.model)
+ .call('fields_get').then(function (fields) {
+ self.fields = fields;
+ self.has_been_loaded.resolve();
+ });
},
do_search: function (domains, contexts, group_bys) {
var self = this;
diff --git a/addons/web_graph/i18n/es_EC.po b/addons/web_graph/i18n/es_EC.po
index 09d0ae48cd2..e7d8aa1cf38 100644
--- a/addons/web_graph/i18n/es_EC.po
+++ b/addons/web_graph/i18n/es_EC.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_graph
#. openerp-web
diff --git a/addons/web_graph/i18n/es_MX.po b/addons/web_graph/i18n/es_MX.po
new file mode 100644
index 00000000000..d264632a762
--- /dev/null
+++ b/addons/web_graph/i18n/es_MX.po
@@ -0,0 +1,137 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 15:57+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:12
+#, python-format
+msgid "Bars"
+msgstr "Barras"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:33
+#, python-format
+msgid "Show Data"
+msgstr "Mostrar Datos"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/js/graph.js:22
+#, python-format
+msgid "Graph"
+msgstr "Gráfico"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:25
+#, python-format
+msgid "Inside"
+msgstr "Dentro de"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:3
+#, python-format
+msgid "&iacute;"
+msgstr "&iacute;"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:11
+#, python-format
+msgid "Pie"
+msgstr "Pastel"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:28
+#, python-format
+msgid "Actions"
+msgstr "Acciones"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:7
+#, python-format
+msgid "Graph Mode"
+msgstr "Modo de gráfico"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:18
+#, python-format
+msgid "Radar"
+msgstr "Radar"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:34
+#, python-format
+msgid "Download as PNG"
+msgstr "Descargando como PNG"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:26
+#, python-format
+msgid "Top"
+msgstr "Arriba"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:24
+#, python-format
+msgid "Hidden"
+msgstr "Oculto"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:3
+#, python-format
+msgid "Graph Options"
+msgstr "Opciones del gráfico"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:14
+#, python-format
+msgid "Lines"
+msgstr "Líneas"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:20
+#, python-format
+msgid "Legend"
+msgstr "Leyenda:"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:32
+#, python-format
+msgid "Switch Axis"
+msgstr "Cambiar Eje"
+
+#. module: web_graph
+#. openerp-web
+#: code:addons/web_graph/static/src/xml/web_graph.xml:15
+#, python-format
+msgid "Areas"
+msgstr "Areas"
diff --git a/addons/web_graph/i18n/fa.po b/addons/web_graph/i18n/fa.po
index cadb3252155..b3a7d0fc236 100644
--- a/addons/web_graph/i18n/fa.po
+++ b/addons/web_graph/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_graph
#. openerp-web
diff --git a/addons/web_kanban/i18n/es_MX.po b/addons/web_kanban/i18n/es_MX.po
new file mode 100644
index 00000000000..ae2dbe088dd
--- /dev/null
+++ b/addons/web_kanban/i18n/es_MX.po
@@ -0,0 +1,146 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 15:59+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:697
+#, python-format
+msgid "Edit column"
+msgstr "Editar columna"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:70
+#, python-format
+msgid "Show more... ("
+msgstr "Mostrar más... ("
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:10
+#, python-format
+msgid "Kanban"
+msgstr "Kanban"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:551
+#, python-format
+msgid "Undefined"
+msgstr "Sin definir"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:716
+#, python-format
+msgid "Are you sure to remove this column ?"
+msgstr "¿Está seguro de querer eliminar esta columna?"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:42
+#, python-format
+msgid "Edit"
+msgstr "Editar"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:188
+#, python-format
+msgid "Add column"
+msgstr "Añadir columna"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:1090
+#, python-format
+msgid "Create: "
+msgstr "Crear: "
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:24
+#, python-format
+msgid "Add a new column"
+msgstr "Añadir una nueva columna"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:687
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:40
+#, python-format
+msgid "Fold"
+msgstr "Doblar"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:88
+#, python-format
+msgid "Add"
+msgstr "Agregar"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:35
+#, python-format
+msgid "Quick create"
+msgstr "Creación rapida"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:687
+#, python-format
+msgid "Unfold"
+msgstr "Desplegar"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:89
+#, python-format
+msgid "Cancel"
+msgstr "Cancelar"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:70
+#, python-format
+msgid "remaining)"
+msgstr "restante)"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:22
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:88
+#, python-format
+msgid "or"
+msgstr "ó"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/js/kanban.js:928
+#, python-format
+msgid "Are you sure you want to delete this record ?"
+msgstr "¿Está seguro que quiere eliminar este registro?"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:43
+#, python-format
+msgid "Delete"
+msgstr "Borrar"
diff --git a/addons/web_kanban/i18n/fa.po b/addons/web_kanban/i18n/fa.po
index e2dffe99e8c..3968d0f1a0e 100644
--- a/addons/web_kanban/i18n/fa.po
+++ b/addons/web_kanban/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_kanban
#. openerp-web
diff --git a/addons/web_kanban/i18n/zh_CN.po b/addons/web_kanban/i18n/zh_CN.po
index 8d0448e0720..3544cddc944 100644
--- a/addons/web_kanban/i18n/zh_CN.po
+++ b/addons/web_kanban/i18n/zh_CN.po
@@ -8,21 +8,29 @@ msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-11-24 01:23+0000\n"
-"PO-Revision-Date: 2012-02-10 08:25+0000\n"
-"Last-Translator: 开阖软件 Jeff Wang <jeff@osbzr.com>\n"
+"PO-Revision-Date: 2012-11-28 07:30+0000\n"
+"Last-Translator: youring <youring@gmail.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-11-25 06:42+0000\n"
-"X-Generator: Launchpad (build 16293)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_kanban
+#. openerp-web
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:22
+#: code:addons/web_kanban/static/src/xml/web_kanban.xml:88
+#, python-format
+msgid "or"
+msgstr "or"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/js/kanban.js:697
#, python-format
msgid "Edit column"
-msgstr ""
+msgstr "编辑列"
#. module: web_kanban
#. openerp-web
@@ -50,35 +58,35 @@ msgstr "未定义"
#: code:addons/web_kanban/static/src/js/kanban.js:716
#, python-format
msgid "Are you sure to remove this column ?"
-msgstr ""
+msgstr "您确认要移除此列?"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:42
#, python-format
msgid "Edit"
-msgstr ""
+msgstr "编辑"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/js/kanban.js:188
#, python-format
msgid "Add column"
-msgstr ""
+msgstr "增加列"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/js/kanban.js:1090
#, python-format
msgid "Create: "
-msgstr ""
+msgstr "创建: "
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:24
#, python-format
msgid "Add a new column"
-msgstr ""
+msgstr "添加新列"
#. module: web_kanban
#. openerp-web
@@ -86,35 +94,35 @@ msgstr ""
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:40
#, python-format
msgid "Fold"
-msgstr ""
+msgstr "收拢"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:88
#, python-format
msgid "Add"
-msgstr ""
+msgstr "添加"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:35
#, python-format
msgid "Quick create"
-msgstr ""
+msgstr "快速创建"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/js/kanban.js:687
#, python-format
msgid "Unfold"
-msgstr ""
+msgstr "展开"
#. module: web_kanban
#. openerp-web
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:89
#, python-format
msgid "Cancel"
-msgstr ""
+msgstr "取消"
#. module: web_kanban
#. openerp-web
@@ -125,14 +133,6 @@ msgstr "剩下的)"
#. module: web_kanban
#. openerp-web
-#: code:addons/web_kanban/static/src/xml/web_kanban.xml:22
-#: code:addons/web_kanban/static/src/xml/web_kanban.xml:88
-#, python-format
-msgid "or"
-msgstr ""
-
-#. module: web_kanban
-#. openerp-web
#: code:addons/web_kanban/static/src/js/kanban.js:928
#, python-format
msgid "Are you sure you want to delete this record ?"
@@ -143,7 +143,7 @@ msgstr "您确定要删除此记录吗?"
#: code:addons/web_kanban/static/src/xml/web_kanban.xml:43
#, python-format
msgid "Delete"
-msgstr ""
+msgstr "删除"
#~ msgid "</tr><tr>"
#~ msgstr "</tr><tr>"
diff --git a/addons/web_view_editor/i18n/es_MX.po b/addons/web_view_editor/i18n/es_MX.po
new file mode 100644
index 00000000000..8ff0b2a35db
--- /dev/null
+++ b/addons/web_view_editor/i18n/es_MX.po
@@ -0,0 +1,149 @@
+# Spanish (Mexico) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 16:01+0000\n"
+"Last-Translator: OscarAlca <oscarolar@hotmail.com>\n"
+"Language-Team: Spanish (Mexico) <es_MX@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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:396
+#, python-format
+msgid "Preview"
+msgstr "Vista Preliminar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:90
+#, python-format
+msgid "Save"
+msgstr "Guardar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:64
+#, python-format
+msgid "Edit"
+msgstr "Editar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:37
+#, python-format
+msgid "Manage Views (%s)"
+msgstr "Administrar vistas (%s)"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:14
+#, python-format
+msgid "Could not find current view declaration"
+msgstr "No se ha encontrado la declaración de la vista actual"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:382
+#, python-format
+msgid "Inherited View"
+msgstr "Vista Heredada"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:828
+#: code:addons/web_view_editor/static/src/js/view_editor.js:954
+#, python-format
+msgid "Update"
+msgstr "Actualizar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:65
+#, python-format
+msgid "Remove"
+msgstr "Eliminar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:825
+#: code:addons/web_view_editor/static/src/js/view_editor.js:951
+#, python-format
+msgid "Properties"
+msgstr "Propiedades"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:379
+#, python-format
+msgid "View Editor %d - %s"
+msgstr "Ver Editor %d - %s"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:386
+#, python-format
+msgid "Do you really wants to create an inherited view here?"
+msgstr "¿De verdad desea crear una vista heredada aquí?"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:13
+#, python-format
+msgid "Manage Views"
+msgstr "Administrar Vistas"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:112
+#: code:addons/web_view_editor/static/src/js/view_editor.js:846
+#: code:addons/web_view_editor/static/src/js/view_editor.js:974
+#, python-format
+msgid "Cancel"
+msgstr "Cancelar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:66
+#: code:addons/web_view_editor/static/src/js/view_editor.js:413
+#, python-format
+msgid "Close"
+msgstr "Cerrar"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:516
+#, python-format
+msgid "Do you really want to remove this node?"
+msgstr "¿De verdad desea eliminar este nodo?"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:183
+#, python-format
+msgid "Do you really want to remove this view?"
+msgstr "¿De verdad desea eliminar esta vista?"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:63
+#, python-format
+msgid "Create"
+msgstr "Crear"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:88
+#, python-format
+msgid "Create a view (%s)"
+msgstr "Crear vista (%s)"
diff --git a/addons/web_view_editor/i18n/fa.po b/addons/web_view_editor/i18n/fa.po
index d9f992a76b8..e3a8a5804c0 100644
--- a/addons/web_view_editor/i18n/fa.po
+++ b/addons/web_view_editor/i18n/fa.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-11-28 04:57+0000\n"
-"X-Generator: Launchpad (build 16309)\n"
+"X-Launchpad-Export-Date: 2012-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
#. module: web_view_editor
#. openerp-web
diff --git a/addons/web_view_editor/i18n/zh_CN.po b/addons/web_view_editor/i18n/zh_CN.po
new file mode 100644
index 00000000000..2c3020c8bc2
--- /dev/null
+++ b/addons/web_view_editor/i18n/zh_CN.po
@@ -0,0 +1,149 @@
+# Chinese (Simplified) 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-11-24 01:23+0000\n"
+"PO-Revision-Date: 2012-11-28 06:44+0000\n"
+"Last-Translator: youring <youring@gmail.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-11-29 05:21+0000\n"
+"X-Generator: Launchpad (build 16319)\n"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:396
+#, python-format
+msgid "Preview"
+msgstr "预览"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:90
+#, python-format
+msgid "Save"
+msgstr "保存"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:64
+#, python-format
+msgid "Edit"
+msgstr "编辑"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:37
+#, python-format
+msgid "Manage Views (%s)"
+msgstr "管理视图 (%s)"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:14
+#, python-format
+msgid "Could not find current view declaration"
+msgstr ""
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:382
+#, python-format
+msgid "Inherited View"
+msgstr "继承视图"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:828
+#: code:addons/web_view_editor/static/src/js/view_editor.js:954
+#, python-format
+msgid "Update"
+msgstr "更新"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:65
+#, python-format
+msgid "Remove"
+msgstr "移除"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:825
+#: code:addons/web_view_editor/static/src/js/view_editor.js:951
+#, python-format
+msgid "Properties"
+msgstr "属性"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:379
+#, python-format
+msgid "View Editor %d - %s"
+msgstr "视图编辑器 %d - %s"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:386
+#, python-format
+msgid "Do you really wants to create an inherited view here?"
+msgstr "您确定要在此处创建继承视图吗?"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:13
+#, python-format
+msgid "Manage Views"
+msgstr "管理视图"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:112
+#: code:addons/web_view_editor/static/src/js/view_editor.js:846
+#: code:addons/web_view_editor/static/src/js/view_editor.js:974
+#, python-format
+msgid "Cancel"
+msgstr "取消"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:66
+#: code:addons/web_view_editor/static/src/js/view_editor.js:413
+#, python-format
+msgid "Close"
+msgstr "关闭"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:516
+#, python-format
+msgid "Do you really want to remove this node?"
+msgstr "您确认要删除这个节点吗?"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:183
+#, python-format
+msgid "Do you really want to remove this view?"
+msgstr "您确认要移除此视图吗?"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:63
+#, python-format
+msgid "Create"
+msgstr "创建"
+
+#. module: web_view_editor
+#. openerp-web
+#: code:addons/web_view_editor/static/src/js/view_editor.js:88
+#, python-format
+msgid "Create a view (%s)"
+msgstr "创建视图 (%s)"