summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2012-11-26 15:05:25 +0100
committerXavier Morel <xmo@openerp.com>2012-11-26 15:05:25 +0100
commit4a5cb1ebc478cb650a2bf33081f101e4f74bc6fe (patch)
treed439fb62effd89c2e31f9bf238c024e6b36625b2 /addons/web
parenteb9a1c7d55bf4b07b5e516698a3b96a3f9f43d5b (diff)
[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
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/controllers/main.py109
-rw-r--r--addons/web/controllers/testing.py2
-rw-r--r--addons/web/http.py17
-rw-r--r--addons/web/nonliterals.py111
-rw-r--r--addons/web/session.py2
-rw-r--r--addons/web/static/src/js/chrome.js7
-rw-r--r--addons/web/static/src/js/view_tree.js8
-rw-r--r--addons/web/static/src/js/views.js14
-rw-r--r--addons/web/static/src/xml/base.xml4
-rw-r--r--addons/web/tests/__init__.py3
-rw-r--r--addons/web/tests/test_view.py113
11 files changed, 43 insertions, 347 deletions
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 9f43b4c75e7..e420dcda142 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -30,7 +30,6 @@ except ImportError:
import openerp
from .. import http
-from .. import nonliterals
openerpweb = http
#----------------------------------------------------------
@@ -442,39 +441,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:
@@ -970,8 +936,8 @@ class Menu(openerpweb.Controller):
menu_domain = [('parent_id', '=', False)]
if user_menu_id:
- domain_string = s.model('ir.actions.act_window').read([user_menu_id[0]], ['domain'],
- req.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)
@@ -1176,8 +1142,6 @@ class View(openerpweb.Controller):
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, req.context, transform, (view_type == 'kanban'))
- if toolbar and transform:
- self.process_toolbar(req, fvg['toolbar'])
return fvg
def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
@@ -1194,12 +1158,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
@@ -1208,29 +1168,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):
@@ -1255,40 +1194,6 @@ class View(openerpweb.Controller):
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)
@@ -1488,7 +1393,7 @@ class Action(openerpweb.Controller):
ctx.update(req.context)
action = req.session.model(action_type).read([action_id], False, ctx)
if action:
- value = clean_action(req, action[0], do_not_eval)
+ value = clean_action(req, action[0])
return value
@openerpweb.jsonrequest
diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py
index 5c7fdf46e1d..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 .. import http, nonliterals
+from .. import http
NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
<html>
diff --git a/addons/web/http.py b/addons/web/http.py
index 3fc8f63231f..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,7 +93,7 @@ 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)
@@ -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
diff --git a/addons/web/nonliterals.py b/addons/web/nonliterals.py
deleted file mode 100644
index 807e940e1de..00000000000
--- a/addons/web/nonliterals.py
+++ /dev/null
@@ -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
diff --git a/addons/web/session.py b/addons/web/session.py
index 15ff1cf8d88..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__)
#----------------------------------------------------------
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 2cca57f0b56..77d23d8fa53 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -1191,11 +1191,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/view_tree.js b/addons/web/static/src/js/view_tree.js
index 8fd3b25ade5..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];
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index efd5f890544..9fad16a58b0 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -265,6 +265,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();
@@ -1081,7 +1092,8 @@ instance.web.Sidebar = instance.web.Widget.extend({
context: instance.web.pyeval.eval(
'context', additional_context)
}).done(function(result) {
- result.context = _.extend(result.context || {},
+ result.context = new instance.web.CompoundContext(
+ result.context,
additional_context);
result.flags = result.flags || {};
result.flags.new_window = true;
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 1b7a5cea53a..723f6707cce 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -905,11 +905,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/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_view.py b/addons/web/tests/test_view.py
deleted file mode 100644
index c9a7d854d75..00000000000
--- a/addons/web/tests/test_view.py
+++ /dev/null
@@ -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)
- )