[ADD] non-literal contexts

bzr revid: xmo@openerp.com-20110328151410-6ep57ay6sdc2k39r
This commit is contained in:
Xavier Morel
2011-03-28 17:14:10 +02:00
parent e7860c7e63
commit 6f3862e101
5 changed files with 210 additions and 27 deletions
+13 -3
View File
@@ -176,8 +176,10 @@ class Menu(openerpweb.Controller):
for _, _, action in actions:
# values come from the server, we can just eval them
action['context'] = req.session.eval_context(
action['context']) or {}
if isinstance(action['context'], basestring):
action['context'] = eval(
action['context'],
req.session.evaluation_context()) or {}
if isinstance(action['domain'], basestring):
action['domain'] = eval(
@@ -265,7 +267,6 @@ class DataRecord(openerpweb.Controller):
value = r[0]
return {'value': value}
class View(openerpweb.Controller):
def fields_view_get(self, session, model, view_id, view_type, transform=True):
Model = session.model(model)
@@ -346,6 +347,15 @@ class View(openerpweb.Controller):
# not a literal
elem.set('domain',
openerpweb.nonliterals.Domain(session, domain))
context_string = elem.get('context')
if context_string:
try:
elem.set('context',
openerpweb.ast.literal_eval(context_string))
except ValueError:
elem.set('context',
openerpweb.nonliterals.Context(
session, context_string))
class FormView(View):
_cp_path = "/base/formview"
+43
View File
@@ -72,3 +72,46 @@ class ViewTest(unittest2.TestCase):
openerpweb.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_domain(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_domain(self):
session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
session.domains_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'), openerpweb.nonliterals.Context)
self.assertEqual(
openerpweb.nonliterals.Context(
session, key=e.get('context').key).get_context_string(),
context_string)
+54 -1
View File
@@ -9,7 +9,7 @@ import hashlib
import simplejson.decoder
import simplejson.encoder
__all__ = ['Domain']
__all__ = ['Domain', 'Context', 'NonLiteralEncoder, non_literal_decoder']
#: 48 bits should be sufficient to have almost no chance of collision
#: with a million hashes, according to hg@67081329d49a
@@ -22,12 +22,19 @@ class NonLiteralEncoder(simplejson.encoder.JSONEncoder):
'__ref': 'domain',
'__id': object.key
}
elif isinstance(object, Context):
return {
'__ref': 'context',
'__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'])
elif dct['__ref'] == 'context':
return Context(None, key=dct['__id'])
return dct
class Domain(object):
@@ -74,3 +81,49 @@ class Domain(object):
"""
return eval(self.get_domain_string(),
self.session.evaluation_context(context))
class Context(object):
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: openerpweb.openerpweb.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.domains_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.
:param session: the OpenERP Session used to store the context string in
the first place.
:type session: openerpweb.openerpweb.OpenERPSession
"""
return self.session.domains_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.
"""
return eval(self.get_context_string(),
self.session.evaluation_context(context))
+26 -21
View File
@@ -146,36 +146,41 @@ class OpenERPSession(object):
d.update(context)
return d
def eval_context(self, context_string, context=None, use_base=True):
""" Evaluates the provided context_string in the context (haha) of
def eval_context(self, context_to_eval, context=None):
""" Evaluates the provided context_to_eval in the context (haha) of
the context.
:param str context_string: a context to evaluate, if not a string,
will be returned as-is
: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 merged to the provided
context (or used alone)
: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
"""
if not isinstance(context_string, basestring):
return context_string
if not isinstance(context_to_eval, (dict, nonliterals.Domain)):
raise TypeError("Context %r is not a dict or a nonliteral Context",
context_to_eval)
if isinstance(context_to_eval, dict):
return context_to_eval
ctx = {}
if use_base:
ctx.update(self.base_eval_context)
if context:
ctx.update(context)
ctx['context'] = ctx
return eval(context_string, ctx)
# if the domain was unpacked from JSON, it needs the current
# OpenERPSession for its data retrieval
context_to_eval.session = self
return context_to_eval.evaluate(ctx)
def eval_contexts(self, contexts, context=None):
""" Evaluates a sequence of contexts to build a single final result
:param list contexts: a list of string or dict contexts
:param list contexts: a list of Context or dict contexts
:param dict context: a base context, if needed
:returns: the final combination of all provided contexts
:rtype: dict
@@ -192,17 +197,17 @@ class OpenERPSession(object):
# the result
final_context.update(
self.eval_context(
ctx, current_context, use_base=False))
ctx, current_context))
# update the current evaluation context so that future
# evaluations can use the results we just gathered
current_context.update(final_context)
return final_context
def eval_domain(self, domain, context=None):
""" Evaluates the provided domain_string using the provided context
""" Evaluates the provided domain using the provided context
(merged with the session's evaluation context)
:param domain: an OpenERP domain as a dict or as a
: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.
@@ -211,10 +216,10 @@ class OpenERPSession(object):
:returns: the evaluated domain
:rtype: list
:raises: ``TypeError`` if ``domain`` is neither a dict nor a Domain
:raises: ``TypeError`` if ``domain`` is neither a list nor a Domain
"""
if not isinstance(domain, (list, nonliterals.Domain)):
raise TypeError("Domain %r is not a dict or a nonliteral Domain",
raise TypeError("Domain %r is not a list or a nonliteral Domain",
domain)
if isinstance(domain, list):
@@ -236,7 +241,7 @@ class OpenERPSession(object):
Returns the final, concatenated result.
:param list domains: a list of string or list domains
:param list domains: a list of Domain or list domains
:param dict context: the context in which the domains
should be evaluated (if evaluations need
to happen)
+74 -2
View File
@@ -3,7 +3,7 @@ import mock
import simplejson
import unittest2
from openerpweb.nonliterals import Domain
from openerpweb.nonliterals import Domain, Context
import openerpweb.nonliterals
import openerpweb.openerpweb
@@ -44,13 +44,85 @@ class NonLiteralDomainTest(unittest2.TestCase):
self.assertEqual(
result, [('a', '=', 3)])
class NonLiteralContextTest(unittest2.TestCase):
def setUp(self):
self.session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
self.session.domains_store = {}
def test_store_domain(self):
c = Context(self.session, "some arbitrary string")
self.assertEqual(
self.session.domains_store[c.key],
"some arbitrary string")
def test_get_domain_back(self):
c = Context(self.session, "some arbitrary string")
self.assertEqual(
c.get_context_string(),
"some arbitrary string")
def test_retrieve_second_domain(self):
""" A different domain should be able to retrieve the nonliteral set
previously
"""
key = Context(self.session, "some arbitrary string").key
self.assertEqual(
Domain(self.session, key=key).get_domain_string(),
"some arbitrary string")
def test_key_and_string(self):
self.assertRaises(
ValueError, Context, None, context_string="a", key="b")
def test_eval(self):
self.session.evaluation_context.return_value = {'foo': 3}
result = Context(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):
def test_encode_domain(self):
d = Domain(self.session, "some arbitrary string")
self.assertEqual(
simplejson.dumps(d, cls=openerpweb.nonliterals.NonLiteralEncoder),
simplejson.dumps({'__ref': 'domain', '__id': d.key}))
def test_decode_domain(self):
encoded = simplejson.dumps(
Domain(self.session, "some arbitrary string"),
cls=openerpweb.nonliterals.NonLiteralEncoder)
domain = simplejson.loads(
encoded, object_hook=openerpweb.nonliterals.non_literal_decoder)
domain.session = self.session
self.assertEqual(
domain.get_domain_string(),
"some arbitrary string"
)
def test_encode_context(self):
c = Context(self.session, "some arbitrary string")
self.assertEqual(
simplejson.dumps(c, cls=openerpweb.nonliterals.NonLiteralEncoder),
simplejson.dumps({'__ref': 'context', '__id': c.key}))
def test_decode_context(self):
encoded = simplejson.dumps(
Context(self.session, "some arbitrary string"),
cls=openerpweb.nonliterals.NonLiteralEncoder)
context = simplejson.loads(
encoded, object_hook=openerpweb.nonliterals.non_literal_decoder)
context.session = self.session
self.assertEqual(
context.get_context_string(),
"some arbitrary string"
)