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 e36b46ed51a..3f35b3f44ea 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -28,9 +28,9 @@ except ImportError: xlwt = None import openerp +from openerp.tools.translate import _ from .. import http -from .. import nonliterals openerpweb = http #---------------------------------------------------------- @@ -361,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) @@ -473,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: @@ -821,7 +763,7 @@ class Database(openerpweb.Controller): except xmlrpclib.Fault, e: if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied': return {'error': e.faultCode, 'title': 'Drop Database'} - return {'error': 'Could not drop database !', 'title': 'Drop Database'} + return {'error': _('Could not drop database !'), 'title': _('Drop Database')} @openerpweb.httprequest def backup(self, req, backup_db, backup_pwd, token): @@ -839,7 +781,7 @@ class Database(openerpweb.Controller): {'fileToken': int(token)} ) except xmlrpclib.Fault, e: - return simplejson.dumps([[],[{'error': e.faultCode, 'title': 'backup Database'}]]) + return simplejson.dumps([[],[{'error': e.faultCode, 'title': _('Backup Database')}]]) @openerpweb.httprequest def restore(self, req, db_file, restore_pwd, new_db): @@ -860,8 +802,8 @@ class Database(openerpweb.Controller): return req.session.proxy("db").change_admin_password(old_password, new_password) except xmlrpclib.Fault, e: if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied': - return {'error': e.faultCode, 'title': 'Change Password'} - return {'error': 'Error, password not changed !', 'title': 'Change Password'} + return {'error': e.faultCode, 'title': _('Change Password')} + return {'error': _('Error, password not changed !'), 'title': _('Change Password')} class Session(openerpweb.Controller): _cp_path = "/web/session" @@ -897,87 +839,34 @@ class Session(openerpweb.Controller): old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')( dict(map(operator.itemgetter('name', 'value'), fields))) if not (old_password.strip() and new_password.strip() and confirm_password.strip()): - return {'error':'You cannot leave any password empty.','title': 'Change Password'} + return {'error':_('You cannot leave any password empty.'),'title': _('Change Password')} if new_password != confirm_password: - return {'error': 'The new password and its confirmation must be identical.','title': 'Change Password'} + return {'error': _('The new password and its confirmation must be identical.'),'title': _('Change Password')} try: if req.session.model('res.users').change_password( old_password, new_password): return {'new_password':new_password} except Exception: - return {'error': 'The old password you provided is incorrect, your password was not changed.', 'title': 'Change Password'} - return {'error': 'Error, password not changed !', 'title': 'Change Password'} + return {'error': _('The old password you provided is incorrect, your password was not changed.'), 'title': _('Change Password')} + return {'error': _('Error, password not changed !'), 'title': _('Change Password')} @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): try: return req.session.proxy("db").list_lang() or [] except Exception, e: - return {"error": e, "title": "Languages"} + return {"error": e, "title": _("Languages")} @openerpweb.jsonrequest def modules(self, req): # return all installed modules. Web client is smart enough to not load a module twice 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): """ @@ -1047,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). @@ -1068,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] @@ -1134,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']: @@ -1149,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, @@ -1160,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]: @@ -1203,39 +1076,9 @@ 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): @@ -1243,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 @@ -1282,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): @@ -1304,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 @@ -1318,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): @@ -1349,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) @@ -1413,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" @@ -1464,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() @@ -1475,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]) @@ -1532,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() @@ -1558,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: @@ -1571,7 +1303,7 @@ class Binary(openerpweb.Controller): res = Model.default_get(fields, context) filecontent = base64.b64decode(res.get(field, '')) if not filecontent: - raise ValueError("No content found for field '%s' on '%s:%s'" % + raise ValueError(_("No content found for field '%s' on '%s:%s'") % (field, model, id)) else: filename = '%s_%s' % (model.replace('.', '_'), id) @@ -1599,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 = """