[ADD] start working on adding the concept of literal and non-lit domains, non-lit domains will *not* be pushed to the client but will be stored locally and only a ref will go through
bzr revid: xmo@openerp.com-20110328122724-gnxn8cnta4xyotab
This commit is contained in:
parent
ec7510b839
commit
fd9da7558a
|
@ -6,6 +6,8 @@ from cStringIO import StringIO
|
|||
import simplejson
|
||||
|
||||
import openerpweb
|
||||
import openerpweb.ast
|
||||
import openerpweb.nonliterals
|
||||
|
||||
__all__ = ['Session', 'Menu', 'DataSet', 'DataRecord',
|
||||
'View', 'FormView', 'ListView', 'SearchView',
|
||||
|
@ -264,7 +266,7 @@ class View(openerpweb.Controller):
|
|||
r = Model.fields_view_get(view_id, view_type)
|
||||
if transform:
|
||||
context = {} # TODO: dict(ctx_sesssion, **ctx_action)
|
||||
xml = self.transform_view(r['arch'], context)
|
||||
xml = self.transform_view(r['arch'], session, context)
|
||||
else:
|
||||
xml = ElementTree.fromstring(r['arch'])
|
||||
r['arch'] = Xml2Json.convert_element(xml)
|
||||
|
@ -303,7 +305,7 @@ class View(openerpweb.Controller):
|
|||
if a == 'invisible' and 'attrs' in elem.attrib:
|
||||
del elem.attrib['attrs']
|
||||
|
||||
def transform_view(self, view_string, context=None):
|
||||
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",))
|
||||
|
@ -313,8 +315,32 @@ class View(openerpweb.Controller):
|
|||
if root is None:
|
||||
root = elem
|
||||
self.normalize_attrs(elem, context)
|
||||
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
|
||||
"""
|
||||
domain = elem.get('domain')
|
||||
if domain:
|
||||
try:
|
||||
elem.set(
|
||||
'domain',
|
||||
openerpweb.ast.literal_eval(
|
||||
domain))
|
||||
except ValueError:
|
||||
# not a literal
|
||||
elem.set('domain',
|
||||
openerpweb.nonliterals.Domain(session, domain))
|
||||
|
||||
class FormView(View):
|
||||
_cp_path = "/base/formview"
|
||||
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import xml.etree.ElementTree
|
||||
import mock
|
||||
|
||||
import unittest2
|
||||
|
||||
import base.controllers.main
|
||||
import openerpweb.nonliterals
|
||||
import openerpweb.openerpweb
|
||||
|
||||
#noinspection PyCompatibility
|
||||
class ViewTest(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
self.view = base.controllers.main.View()
|
||||
def test_identity(self):
|
||||
view = base.controllers.main.View()
|
||||
base_view = """
|
||||
<form string="Title">
|
||||
<group>
|
||||
|
@ -18,10 +23,52 @@ class ViewTest(unittest2.TestCase):
|
|||
"""
|
||||
|
||||
pristine = xml.etree.ElementTree.fromstring(base_view)
|
||||
transformed = view.transform_view(base_view)
|
||||
transformed = self.view.transform_view(base_view, None)
|
||||
|
||||
self.assertEqual(
|
||||
xml.etree.ElementTree.tostring(transformed),
|
||||
xml.etree.ElementTree.tostring(pristine)
|
||||
)
|
||||
|
||||
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):
|
||||
session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
|
||||
session.domains_store = {}
|
||||
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, session)
|
||||
|
||||
self.assertIsInstance(e.get('domain'), openerpweb.nonliterals.Domain)
|
||||
self.assertEqual(
|
||||
openerpweb.nonliterals.Domain(
|
||||
session, key=e.get('domain').key).get_domain_string(),
|
||||
domain_string)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" Backport of Python 2.6's ast.py for Python 2.5
|
||||
"""
|
||||
__all__ = ['literal_eval']
|
||||
try:
|
||||
from ast import literal_eval
|
||||
except ImportError:
|
||||
from _ast import *
|
||||
from _ast import __version__
|
||||
|
||||
|
||||
def parse(expr, filename='<unknown>', mode='exec'):
|
||||
"""
|
||||
Parse an expression into an AST node.
|
||||
Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
|
||||
"""
|
||||
return compile(expr, filename, mode, PyCF_ONLY_AST)
|
||||
|
||||
|
||||
def literal_eval(node_or_string):
|
||||
"""
|
||||
Safely evaluate an expression node or a string containing a Python
|
||||
expression. The string or node provided may only consist of the
|
||||
following Python literal structures: strings, numbers, tuples, lists,
|
||||
dicts, booleans, and None.
|
||||
"""
|
||||
_safe_names = {'None': None, 'True': True, 'False': False}
|
||||
if isinstance(node_or_string, basestring):
|
||||
node_or_string = parse(node_or_string, mode='eval')
|
||||
if isinstance(node_or_string, Expression):
|
||||
node_or_string = node_or_string.body
|
||||
def _convert(node):
|
||||
if isinstance(node, Str):
|
||||
return node.s
|
||||
elif isinstance(node, Num):
|
||||
return node.n
|
||||
elif isinstance(node, Tuple):
|
||||
return tuple(map(_convert, node.elts))
|
||||
elif isinstance(node, List):
|
||||
return list(map(_convert, node.elts))
|
||||
elif isinstance(node, Dict):
|
||||
return dict((_convert(k), _convert(v)) for k, v
|
||||
in zip(node.keys, node.values))
|
||||
elif isinstance(node, Name):
|
||||
if node.id in _safe_names:
|
||||
return _safe_names[node.id]
|
||||
raise ValueError('malformed string')
|
||||
return _convert(node_or_string)
|
|
@ -0,0 +1,59 @@
|
|||
# -*- 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 binascii
|
||||
import hashlib
|
||||
|
||||
__all__ = ['Domain']
|
||||
|
||||
#: 48 bits should be sufficient to have almost no chance of collision
|
||||
#: with a million hashes, according to hg@67081329d49a
|
||||
SHORT_HASH_BYTES_SIZE = 6
|
||||
|
||||
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
|
||||
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: openerpweb.openerpweb.OpenERPSession
|
||||
:param str domain_string: a non-literal domain in string form
|
||||
:param str key: key used to retrieve the domain string
|
||||
"""
|
||||
if domain_string and key:
|
||||
raise ValueError("A nonliteral domain can not take both a key "
|
||||
"and a domain string")
|
||||
|
||||
self.session = session
|
||||
if domain_string:
|
||||
self.key = binascii.hexlify(
|
||||
hashlib.sha256(domain_string).digest()[:SHORT_HASH_BYTES_SIZE])
|
||||
self.session.domains_store[self.key] = domain_string
|
||||
elif key:
|
||||
self.key = key
|
||||
|
||||
def get_domain_string(self):
|
||||
""" Retrieves the domain string linked to this non-literal domain in
|
||||
the provided session.
|
||||
|
||||
:param session: the OpenERP Session used to store the domain 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 domain, using the provided
|
||||
context (as well as the session's base context), and returns the
|
||||
evaluated result.
|
||||
"""
|
||||
return eval(self.get_domain_string(),
|
||||
self.session.evaluation_context(context))
|
|
@ -50,6 +50,13 @@ class OpenERPSession(object):
|
|||
|
||||
The session context, a ``dict``. Can be reloaded by calling
|
||||
:meth:`openerpweb.openerpweb.OpenERPSession.get_context`
|
||||
|
||||
.. attribute:: domains_store
|
||||
|
||||
A ``dict`` matching domain keys to evaluable (but non-literal) domains.
|
||||
|
||||
Used to store references to non-literal domains which need to be
|
||||
round-tripped to the client browser.
|
||||
"""
|
||||
def __init__(self, server='127.0.0.1', port=8069,
|
||||
model_factory=OpenERPModel):
|
||||
|
@ -62,6 +69,7 @@ class OpenERPSession(object):
|
|||
self.model_factory = model_factory
|
||||
|
||||
self.context = {}
|
||||
self.domains_store = {}
|
||||
|
||||
def proxy(self, service):
|
||||
s = xmlrpctimeout.TimeoutServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service), timeout=5)
|
||||
|
@ -113,14 +121,29 @@ class OpenERPSession(object):
|
|||
|
||||
Used to evaluate contexts and domains.
|
||||
"""
|
||||
return dict(
|
||||
base = dict(
|
||||
uid=self._uid,
|
||||
current_date=datetime.date.today().strftime('%Y-%m-%d'),
|
||||
time=time,
|
||||
datetime=datetime,
|
||||
relativedelta=dateutil.relativedelta.relativedelta,
|
||||
**self.context
|
||||
relativedelta=dateutil.relativedelta.relativedelta
|
||||
)
|
||||
base.update(self.context)
|
||||
return base
|
||||
|
||||
def evaluation_context(self, context=None):
|
||||
""" Returns the session's evaluation context, augmented with the
|
||||
provided context if any.
|
||||
|
||||
:param dict context: to add merge in the session's base eval context
|
||||
:returns: the augmented context
|
||||
:rtype: dict
|
||||
"""
|
||||
d = {}
|
||||
d.update(self.base_eval_context)
|
||||
if context:
|
||||
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
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import mock
|
||||
import unittest2
|
||||
|
||||
from openerpweb.nonliterals import Domain
|
||||
import openerpweb.openerpweb
|
||||
|
||||
class NonLiteralDomainTest(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
self.session = mock.Mock(spec=openerpweb.openerpweb.OpenERPSession)
|
||||
self.session.domains_store = {}
|
||||
def test_store_domain(self):
|
||||
d = Domain(self.session, "some arbitrary string")
|
||||
|
||||
self.assertEqual(
|
||||
self.session.domains_store[d.key],
|
||||
"some arbitrary string")
|
||||
|
||||
def test_get_domain_back(self):
|
||||
d = Domain(self.session, "some arbitrary string")
|
||||
|
||||
self.assertEqual(
|
||||
d.get_domain_string(),
|
||||
"some arbitrary string")
|
||||
def test_retrieve_second_domain(self):
|
||||
""" A different domain should be able to retrieve the nonliteral set
|
||||
previously
|
||||
"""
|
||||
key = Domain(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, Domain, None, domain_string="a", key="b")
|
||||
|
||||
def test_eval(self):
|
||||
self.session.evaluation_context.return_value = {'foo': 3}
|
||||
result = Domain(self.session, "[('a', '=', foo)]").evaluate({'foo': 3})
|
||||
self.assertEqual(
|
||||
result, [('a', '=', 3)])
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unittest2
|
||||
import openerpweb.openerpweb
|
||||
|
||||
class TestOpenERPSession(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
self.module = object()
|
||||
self.session = openerpweb.openerpweb.OpenERPSession()
|
||||
self.session._uid = -1
|
||||
self.session.context = {
|
||||
'current_date': '1945-08-05',
|
||||
'date': self.module,
|
||||
'time': self.module,
|
||||
'datetime': self.module,
|
||||
'relativedelta': self.module
|
||||
}
|
||||
def test_base_eval_context(self):
|
||||
self.assertEqual(type(self.session.base_eval_context), dict)
|
||||
self.assertEqual(
|
||||
self.session.base_eval_context,
|
||||
{'uid': -1, 'current_date': '1945-08-05',
|
||||
'date': self.module, 'datetime': self.module, 'time': self.module,
|
||||
'relativedelta': self.module}
|
||||
)
|
||||
|
||||
def test_evaluation_context_nocontext(self):
|
||||
self.assertEqual(
|
||||
type(self.session.evaluation_context()),
|
||||
dict
|
||||
)
|
||||
self.assertEqual(
|
||||
self.session.evaluation_context(),
|
||||
self.session.base_eval_context
|
||||
)
|
||||
|
||||
def test_evaluation_context(self):
|
||||
ctx = self.session.evaluation_context({'foo': 3})
|
||||
self.assertEqual(
|
||||
type(ctx),
|
||||
dict
|
||||
)
|
||||
self.assertIn('foo', ctx)
|
||||
|
||||
def test_eval_with_context(self):
|
||||
self.assertEqual(
|
||||
eval('current_date', self.session.evaluation_context()),
|
||||
'1945-08-05')
|
||||
|
||||
self.assertEqual(
|
||||
eval('foo + 3', self.session.evaluation_context({'foo': 4})),
|
||||
7)
|
Loading…
Reference in New Issue