[ADD] non-literal contexts
bzr revid: xmo@openerp.com-20110328151410-6ep57ay6sdc2k39r
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user