[REM] EVALPOCALYPSE PART 2: no more python-side eval
trigger an error if a nonliteral context is pushed to the server (through a new object_hook) bzr revid: xmo@openerp.com-20121126140525-ni2x5m56upss610b
This commit is contained in:
parent
eb9a1c7d55
commit
4a5cb1ebc4
|
@ -30,7 +30,6 @@ except ImportError:
|
||||||
import openerp
|
import openerp
|
||||||
|
|
||||||
from .. import http
|
from .. import http
|
||||||
from .. import nonliterals
|
|
||||||
openerpweb = http
|
openerpweb = http
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
@ -442,39 +441,6 @@ def fix_view_modes(action):
|
||||||
|
|
||||||
return 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):
|
def _local_web_translations(trans_file):
|
||||||
messages = []
|
messages = []
|
||||||
try:
|
try:
|
||||||
|
@ -970,8 +936,8 @@ class Menu(openerpweb.Controller):
|
||||||
|
|
||||||
menu_domain = [('parent_id', '=', False)]
|
menu_domain = [('parent_id', '=', False)]
|
||||||
if user_menu_id:
|
if user_menu_id:
|
||||||
domain_string = s.model('ir.actions.act_window').read([user_menu_id[0]], ['domain'],
|
domain_string = s.model('ir.actions.act_window').read(
|
||||||
req.context)[0]['domain']
|
[user_menu_id[0]], ['domain'],req.context)[0]['domain']
|
||||||
if domain_string:
|
if domain_string:
|
||||||
menu_domain = ast.literal_eval(domain_string)
|
menu_domain = ast.literal_eval(domain_string)
|
||||||
|
|
||||||
|
@ -1176,8 +1142,6 @@ class View(openerpweb.Controller):
|
||||||
fvg = Model.fields_view_get(view_id, view_type, req.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
|
# todo fme?: check that we should pass the evaluated context here
|
||||||
self.process_view(req.session, fvg, req.context, transform, (view_type == 'kanban'))
|
self.process_view(req.session, fvg, req.context, transform, (view_type == 'kanban'))
|
||||||
if toolbar and transform:
|
|
||||||
self.process_toolbar(req, fvg['toolbar'])
|
|
||||||
return fvg
|
return fvg
|
||||||
|
|
||||||
def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
|
def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
|
||||||
|
@ -1194,12 +1158,8 @@ class View(openerpweb.Controller):
|
||||||
arch = fvg['arch']
|
arch = fvg['arch']
|
||||||
fvg['arch_string'] = arch
|
fvg['arch_string'] = arch
|
||||||
|
|
||||||
if transform:
|
fvg['arch'] = xml2json_from_elementtree(
|
||||||
evaluation_context = session.evaluation_context(context or {})
|
ElementTree.fromstring(arch), preserve_whitespaces)
|
||||||
xml = self.transform_view(arch, session, evaluation_context)
|
|
||||||
else:
|
|
||||||
xml = ElementTree.fromstring(arch)
|
|
||||||
fvg['arch'] = xml2json_from_elementtree(xml, preserve_whitespaces)
|
|
||||||
|
|
||||||
if 'id' in fvg['fields']:
|
if 'id' in fvg['fields']:
|
||||||
# Special case for id's
|
# Special case for id's
|
||||||
|
@ -1208,29 +1168,8 @@ class View(openerpweb.Controller):
|
||||||
id_field['type'] = 'id'
|
id_field['type'] = 'id'
|
||||||
|
|
||||||
for field in fvg['fields'].itervalues():
|
for field in fvg['fields'].itervalues():
|
||||||
if field.get('views'):
|
for view in field.get("views", {}).itervalues():
|
||||||
for view in field["views"].itervalues():
|
self.process_view(session, view, None, transform)
|
||||||
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)
|
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
def add_custom(self, req, view_id, arch):
|
def add_custom(self, req, view_id, arch):
|
||||||
|
@ -1255,40 +1194,6 @@ class View(openerpweb.Controller):
|
||||||
return {'result': True}
|
return {'result': True}
|
||||||
return {'result': False}
|
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
|
@openerpweb.jsonrequest
|
||||||
def load(self, req, model, view_id, view_type, toolbar=False):
|
def load(self, req, model, view_id, view_type, toolbar=False):
|
||||||
return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
|
return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
|
||||||
|
@ -1488,7 +1393,7 @@ class Action(openerpweb.Controller):
|
||||||
ctx.update(req.context)
|
ctx.update(req.context)
|
||||||
action = req.session.model(action_type).read([action_id], False, ctx)
|
action = req.session.model(action_type).read([action_id], False, ctx)
|
||||||
if action:
|
if action:
|
||||||
value = clean_action(req, action[0], do_not_eval)
|
value = clean_action(req, action[0])
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
|
|
|
@ -11,7 +11,7 @@ from mako.template import Template
|
||||||
from openerp.modules import module
|
from openerp.modules import module
|
||||||
|
|
||||||
from .main import module_topological_sort
|
from .main import module_topological_sort
|
||||||
from .. import http, nonliterals
|
from .. import http
|
||||||
|
|
||||||
NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
|
NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
|
@ -17,7 +17,6 @@ import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import urllib
|
|
||||||
import urlparse
|
import urlparse
|
||||||
import uuid
|
import uuid
|
||||||
import xmlrpclib
|
import xmlrpclib
|
||||||
|
@ -33,7 +32,6 @@ import werkzeug.wsgi
|
||||||
|
|
||||||
import openerp
|
import openerp
|
||||||
|
|
||||||
import nonliterals
|
|
||||||
import session
|
import session
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
@ -95,7 +93,7 @@ class WebRequest(object):
|
||||||
if not self.session:
|
if not self.session:
|
||||||
self.session = session.OpenERPSession()
|
self.session = session.OpenERPSession()
|
||||||
self.httpsession[self.session_id] = self.session
|
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
|
self.debug = self.params.pop('debug', False) is not False
|
||||||
# Determine self.lang
|
# Determine self.lang
|
||||||
lang = self.params.get('lang', None)
|
lang = self.params.get('lang', None)
|
||||||
|
@ -112,6 +110,11 @@ class WebRequest(object):
|
||||||
# we use _ as seprator where RFC2616 uses '-'
|
# we use _ as seprator where RFC2616 uses '-'
|
||||||
self.lang = lang.replace('-', '_')
|
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):
|
class JsonRequest(WebRequest):
|
||||||
""" JSON-RPC2 over HTTP.
|
""" JSON-RPC2 over HTTP.
|
||||||
|
@ -182,9 +185,9 @@ class JsonRequest(WebRequest):
|
||||||
try:
|
try:
|
||||||
# Read POST content or POST Form Data named "request"
|
# Read POST content or POST Form Data named "request"
|
||||||
if requestf:
|
if requestf:
|
||||||
self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
|
self.jsonrequest = simplejson.load(requestf, object_hook=reject_nonliteral)
|
||||||
else:
|
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", {}))
|
self.init(self.jsonrequest.get("params", {}))
|
||||||
if _logger.isEnabledFor(logging.DEBUG):
|
if _logger.isEnabledFor(logging.DEBUG):
|
||||||
_logger.debug("--> %s.%s\n%s", method.im_class.__name__, method.__name__, pprint.pformat(self.jsonrequest))
|
_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.
|
# We need then to manage http sessions manually.
|
||||||
response['httpsessionid'] = self.httpsession.sid
|
response['httpsessionid'] = self.httpsession.sid
|
||||||
mime = 'application/javascript'
|
mime = 'application/javascript'
|
||||||
body = "%s(%s);" % (jsonp, simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),)
|
body = "%s(%s);" % (jsonp, simplejson.dumps(response),)
|
||||||
else:
|
else:
|
||||||
mime = 'application/json'
|
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))])
|
r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
|
||||||
return r
|
return r
|
||||||
|
|
|
@ -1,111 +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 simplejson.encoder
|
|
||||||
|
|
||||||
__all__ = ['Domain', 'Context', 'NonLiteralEncoder', 'non_literal_decoder', 'CompoundDomain', 'CompoundContext']
|
|
||||||
|
|
||||||
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',
|
|
||||||
'__debug': object.domain_string
|
|
||||||
}
|
|
||||||
elif isinstance(object, Context):
|
|
||||||
return {
|
|
||||||
'__ref': 'context',
|
|
||||||
'__debug': object.context_string
|
|
||||||
}
|
|
||||||
raise TypeError('Could not encode unknown non-literal %s' % object)
|
|
||||||
|
|
||||||
def non_literal_decoder(dct):
|
|
||||||
if '__ref' in dct:
|
|
||||||
raise ValueError(
|
|
||||||
"Non literal contexts can not be sent to the server anymore (%r)" % (dct,))
|
|
||||||
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):
|
|
||||||
""" 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
|
|
||||||
"""
|
|
||||||
self.session = session
|
|
||||||
self.domain_string = domain_string
|
|
||||||
|
|
||||||
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.domain_string, SuperDict(ctx))
|
|
||||||
except NameError as e:
|
|
||||||
raise ValueError('Error during evaluation of this domain: "%s", message: "%s"' % (self.domain_string, e.message))
|
|
||||||
|
|
||||||
class Context(BaseContext):
|
|
||||||
def __init__(self, session, context_string):
|
|
||||||
""" 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
|
|
||||||
"""
|
|
||||||
self.session = session
|
|
||||||
self.context_string = context_string
|
|
||||||
|
|
||||||
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.context_string, SuperDict(ctx))
|
|
||||||
except NameError as e:
|
|
||||||
raise ValueError('Error during evaluation of this context: "%s", message: "%s"' % (self.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
|
|
|
@ -10,8 +10,6 @@ import xmlrpclib
|
||||||
|
|
||||||
import openerp
|
import openerp
|
||||||
|
|
||||||
import nonliterals
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
|
|
@ -1191,11 +1191,12 @@ instance.web.WebClient = instance.web.Client.extend({
|
||||||
var self = this;
|
var self = this;
|
||||||
return this.rpc("/web/action/load", { action_id: options.action_id })
|
return this.rpc("/web/action/load", { action_id: options.action_id })
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
var action = result;
|
|
||||||
if (options.needaction) {
|
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,
|
clear_breadcrumbs: true,
|
||||||
action_menu_id: self.menu.current_menu,
|
action_menu_id: self.menu.current_menu,
|
||||||
})).fail(function() {
|
})).fail(function() {
|
||||||
|
|
|
@ -44,7 +44,8 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
|
||||||
view_id: this.view_id,
|
view_id: this.view_id,
|
||||||
view_type: "tree",
|
view_type: "tree",
|
||||||
toolbar: this.view_manager ? !!this.view_manager.sidebar : false,
|
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);
|
}).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', {
|
return this.rpc('/web/treeview/action', {
|
||||||
id: id,
|
id: id,
|
||||||
model: this.dataset.model,
|
model: this.dataset.model,
|
||||||
context: new instance.web.CompoundContext(
|
context: instance.web.pyeval.eval(
|
||||||
this.dataset.get_context(), local_context)
|
'context', new instance.web.CompoundContext(
|
||||||
|
this.dataset.get_context(), local_context))
|
||||||
}).then(function (actions) {
|
}).then(function (actions) {
|
||||||
if (!actions.length) { return; }
|
if (!actions.length) { return; }
|
||||||
var action = actions[0][2];
|
var action = actions[0][2];
|
||||||
|
|
|
@ -265,6 +265,17 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||||
return self.do_action(result, options);
|
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) {
|
if (!action.type) {
|
||||||
console.error("No type for action", action);
|
console.error("No type for action", action);
|
||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
|
@ -1081,7 +1092,8 @@ instance.web.Sidebar = instance.web.Widget.extend({
|
||||||
context: instance.web.pyeval.eval(
|
context: instance.web.pyeval.eval(
|
||||||
'context', additional_context)
|
'context', additional_context)
|
||||||
}).done(function(result) {
|
}).done(function(result) {
|
||||||
result.context = _.extend(result.context || {},
|
result.context = new instance.web.CompoundContext(
|
||||||
|
result.context,
|
||||||
additional_context);
|
additional_context);
|
||||||
result.flags = result.flags || {};
|
result.flags = result.flags || {};
|
||||||
result.flags.new_window = true;
|
result.flags.new_window = true;
|
||||||
|
|
|
@ -905,11 +905,11 @@
|
||||||
</li>
|
</li>
|
||||||
<li t-if="widget.node.attrs.context" data-item="context">
|
<li t-if="widget.node.attrs.context" data-item="context">
|
||||||
<span class="oe_tooltip_technical_title">Context:</span>
|
<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>
|
||||||
<li t-if="widget.node.attrs.domain" data-item="domain">
|
<li t-if="widget.node.attrs.domain" data-item="domain">
|
||||||
<span class="oe_tooltip_technical_title">Domain:</span>
|
<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>
|
||||||
<li t-if="widget.node.attrs.modifiers and widget.node.attrs.modifiers != '{}'" data-item="modifiers">
|
<li t-if="widget.node.attrs.modifiers and widget.node.attrs.modifiers != '{}'" data-item="modifiers">
|
||||||
<span class="oe_tooltip_technical_title">Modifiers:</span>
|
<span class="oe_tooltip_technical_title">Modifiers:</span>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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 = []
|
fast_suite = []
|
||||||
checks = [
|
checks = [
|
||||||
test_dataset,
|
test_dataset,
|
||||||
test_menu,
|
test_menu,
|
||||||
test_serving_base,
|
test_serving_base,
|
||||||
test_view,
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
import unittest2
|
|
||||||
|
|
||||||
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):
|
|
||||||
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, None)
|
|
||||||
|
|
||||||
self.assertIsInstance(e.get('domain'), nonliterals.Domain)
|
|
||||||
|
|
||||||
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):
|
|
||||||
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, None)
|
|
||||||
|
|
||||||
self.assertIsInstance(e.get('context'), nonliterals.Context)
|
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
Loading…
Reference in New Issue