[IMP] use non-literal domains to and from client, add a bunch of tests

Converted OpenERPSession's evaluations to be based on literal (dict) or nonliteral (Domain) objects. OpenERPSession will *not accept* to eval strings

bzr revid: xmo@openerp.com-20110328141920-hlp6sb173o2j6ldw
This commit is contained in:
Xavier Morel 2011-03-28 16:19:20 +02:00
parent fd9da7558a
commit e7860c7e63
5 changed files with 123 additions and 22 deletions

View File

@ -21,7 +21,8 @@ class Xml2Json:
# URL: http://code.google.com/p/xml2json-direct/
@staticmethod
def convert_to_json(s):
return simplejson.dumps(Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
return simplejson.dumps(
Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
@staticmethod
def convert_to_structure(s):
@ -174,10 +175,15 @@ class Menu(openerpweb.Controller):
actions = Values.get('action', 'tree_but_open', [('ir.ui.menu', menu_id)], False, {})
for _, _, action in actions:
# values come from the server, we can just eval them
action['context'] = req.session.eval_context(
action['context']) or {}
action['domain'] = req.session.eval_domain(
action['domain'], action['context']) or []
if isinstance(action['domain'], basestring):
action['domain'] = eval(
action['domain'],
req.session.evaluation_context(
action['context'])) or []
return {"action": actions}

View File

@ -6,6 +6,8 @@ can't be sent there themselves).
"""
import binascii
import hashlib
import simplejson.decoder
import simplejson.encoder
__all__ = ['Domain']
@ -13,6 +15,21 @@ __all__ = ['Domain']
#: with a million hashes, according to hg@67081329d49a
SHORT_HASH_BYTES_SIZE = 6
class NonLiteralEncoder(simplejson.encoder.JSONEncoder):
def default(self, object):
if isinstance(object, Domain):
return {
'__ref': 'domain',
'__id': object.key
}
return super(NonLiteralEncoder, self).default(object)
def non_literal_decoder(dct):
if '__ref' in dct:
if dct['__ref'] == 'domain':
return Domain(None, key=dct['__id'])
return dct
class Domain(object):
def __init__(self, session, domain_string=None, key=None):
""" Uses session information to store the domain string and map it to a

View File

@ -15,6 +15,7 @@ import cherrypy
import cherrypy.lib.static
import simplejson
import nonliterals
import xmlrpctimeout
#----------------------------------------------------------
@ -197,31 +198,37 @@ class OpenERPSession(object):
current_context.update(final_context)
return final_context
def eval_domain(self, domain_string, context=None, use_base=True):
def eval_domain(self, domain, context=None):
""" Evaluates the provided domain_string using the provided context
(merged with the session's evaluation context)
:param str domain_string: an OpenERP domain as a string, to evaluate.
:param domain: an OpenERP domain as a dict or as a
:class:`openerpweb.nonliterals.Domain` instance
If not a string, is returned as-is
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.
:param bool use_base: whether the base eval context (combination
of the default context and the session
context) should be used
:returns: the evaluated domain
:rtype: list
:raises: ``TypeError`` if ``domain`` is neither a dict nor a Domain
"""
if not isinstance(domain_string, basestring):
return domain_string
if not isinstance(domain, (list, nonliterals.Domain)):
raise TypeError("Domain %r is not a dict or a nonliteral Domain",
domain)
if isinstance(domain, list):
return domain
ctx = {}
if use_base:
ctx.update(self.base_eval_context)
if context:
ctx.update(context)
ctx['context'] = ctx
return eval(domain_string, ctx)
# if the domain was unpacked from JSON, it needs the current
# OpenERPSession for its data retrieval
domain.session = self
return domain.evaluate(ctx)
def eval_domains(self, domains, context=None):
""" Evaluates and concatenates the provided domains using the
@ -236,14 +243,10 @@ class OpenERPSession(object):
:returns: the final combination of all domains in the sequence
:rtype: list
"""
ctx = dict(
self.base_eval_context,
**(context or {}))
final_domain = []
for domain in domains:
final_domain.extend(
self.eval_domain(domain, ctx))
self.eval_domain(domain, context))
return final_domain
#----------------------------------------------------------
@ -290,9 +293,11 @@ class JsonRequest(object):
:rtype: bytes
'''
if requestf:
request = simplejson.load(requestf)
request = simplejson.load(
requestf, object_hook=nonliterals.non_literal_decoder)
else:
request = simplejson.loads(request)
request = simplejson.loads(
request, object_hook=nonliterals.non_literal_decoder)
try:
print "--> %s.%s %s" % (controller.__class__.__name__, method.__name__, request)
error = None
@ -335,7 +340,8 @@ class JsonRequest(object):
print "<--", response
print
content = simplejson.dumps(response)
content = simplejson.dumps(
response, cls=nonliterals.NonLiteralEncoder)
cherrypy.response.headers['Content-Type'] = 'application/json'
cherrypy.response.headers['Content-Length'] = len(content)
return content

View File

@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
import mock
import simplejson
import unittest2
from openerpweb.nonliterals import Domain
import openerpweb.nonliterals
import openerpweb.openerpweb
class NonLiteralDomainTest(unittest2.TestCase):
@ -41,3 +43,14 @@ class NonLiteralDomainTest(unittest2.TestCase):
result = Domain(self.session, "[('a', '=', foo)]").evaluate({'foo': 3})
self.assertEqual(
result, [('a', '=', 3)])
class NonLiteralJSON(unittest2.TestCase):
def setUp(self):
self.session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
self.session.domains_store = {}
def test_encode(self):
d = Domain(self.session, "some arbitrary string")
self.assertEqual(
simplejson.dumps(d, cls=openerpweb.nonliterals.NonLiteralEncoder),
simplejson.dumps({'__ref': 'domain', '__id': d.key}))

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import unittest2
from openerpweb.nonliterals import Domain
import openerpweb.openerpweb
class TestOpenERPSession(unittest2.TestCase):
@ -49,3 +50,61 @@ class TestOpenERPSession(unittest2.TestCase):
self.assertEqual(
eval('foo + 3', self.session.evaluation_context({'foo': 4})),
7)
def test_eval_domain_typeerror(self):
self.assertRaises(
TypeError, self.session.eval_domain, "foo")
def test_eval_domain_list(self):
self.assertEqual(
self.session.eval_domain([]),
[])
def test_eval_nonliteral_domain(self):
d = Domain(self.session, "[('foo', 'is', 3)]")
self.assertEqual(
self.session.eval_domain(d),
[('foo', 'is', 3)])
def test_eval_nonliteral_domain_bykey(self):
key = Domain(
self.session, "[('foo', 'is', 3)]").key
d = Domain(None, key=key)
self.assertEqual(
self.session.eval_domain(d),
[('foo', 'is', 3)])
def test_eval_empty_domains(self):
self.assertEqual(
self.session.eval_domains([]),
[])
def test_eval_literal_domains(self):
domains = [
[('a', 'is', 3)],
[('b', 'ilike', 'foo')],
['|',
('c', '=', False),
('c', 'in', ['a', 'b', 'c'])]
]
self.assertEqual(
self.session.eval_domains(domains),
[
('a', 'is', 3),
('b', 'ilike', 'foo'),
'|',
('c', '=', False),
('c', 'in', ['a', 'b', 'c'])
])
def test_eval_nonliteral_domains(self):
domains = [
Domain(self.session, "[('uid', '=', uid)]"),
Domain(self.session,
"['|', ('date', '<', current_date),"
" ('date', '>', current_date)]")]
self.assertEqual(
self.session.eval_domains(domains),
[('uid', '=', -1),
'|', ('date', '<', '1945-08-05'), ('date', '>', '1945-08-05')]
)