[IMP] make qweb into an openerp object
also attempt to improve/simplify a few implementation details bzr revid: xmo@openerp.com-20131004114155-j2639jx6dwvkz3hh
This commit is contained in:
parent
6507bdd82c
commit
81a82ca37f
|
@ -39,6 +39,7 @@ import ir_config_parameter
|
||||||
import osv_memory_autovacuum
|
import osv_memory_autovacuum
|
||||||
import ir_mail_server
|
import ir_mail_server
|
||||||
import ir_fields
|
import ir_fields
|
||||||
|
import ir_qweb
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import werkzeug.utils
|
import werkzeug.utils
|
||||||
#from openerp.tools.safe_eval import safe_eval as eval
|
|
||||||
|
|
||||||
import xml # FIXME use lxml
|
import xml # FIXME use lxml
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -32,7 +32,8 @@ BUILTINS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class QWebContext(dict):
|
class QWebContext(dict):
|
||||||
def __init__(self, data, undefined_handler=None):
|
def __init__(self, data, undefined_handler=None, loader=None):
|
||||||
|
self.loader = loader
|
||||||
self.undefined_handler = undefined_handler
|
self.undefined_handler = undefined_handler
|
||||||
dic = BUILTINS.copy()
|
dic = BUILTINS.copy()
|
||||||
dic.update(data)
|
dic.update(data)
|
||||||
|
@ -47,7 +48,15 @@ class QWebContext(dict):
|
||||||
else:
|
else:
|
||||||
return self.get(key, self.undefined_handler(key, self))
|
return self.get(key, self.undefined_handler(key, self))
|
||||||
|
|
||||||
class QWebXml(object):
|
def copy(self):
|
||||||
|
return QWebContext(dict.copy(self),
|
||||||
|
undefined_handler=self.undefined_handler,
|
||||||
|
loader=self.loader)
|
||||||
|
|
||||||
|
def __copy__(self):
|
||||||
|
return self.copy()
|
||||||
|
|
||||||
|
class QWeb(orm.AbstractModel):
|
||||||
"""QWeb Xml templating engine
|
"""QWeb Xml templating engine
|
||||||
|
|
||||||
The templating engine use a very simple syntax, "magic" xml attributes, to
|
The templating engine use a very simple syntax, "magic" xml attributes, to
|
||||||
|
@ -63,48 +72,61 @@ class QWebXml(object):
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, loader=None, undefined_handler=None):
|
|
||||||
self.loader = loader
|
|
||||||
self.undefined_handler = undefined_handler
|
|
||||||
self.node = xml.dom.Node
|
|
||||||
self._t = {}
|
|
||||||
self._render_tag = {}
|
|
||||||
self._format_regex = re.compile('(#\{(.*?)\})|(\{\{(.*?)\}\})')
|
|
||||||
self._void_elements = set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
|
|
||||||
'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'])
|
|
||||||
prefix = 'render_tag_'
|
|
||||||
for i in [j for j in dir(self) if j.startswith(prefix)]:
|
|
||||||
name = i[len(prefix):].replace('_', '-')
|
|
||||||
self._render_tag[name] = getattr(self.__class__, i)
|
|
||||||
|
|
||||||
self._render_att = {}
|
_name = 'ir.templating.qweb'
|
||||||
prefix = 'render_att_'
|
|
||||||
for i in [j for j in dir(self) if j.startswith(prefix)]:
|
node = xml.dom.Node
|
||||||
name = i[len(prefix):].replace('_', '-')
|
_void_elements = frozenset([
|
||||||
self._render_att[name] = getattr(self.__class__, i)
|
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
|
||||||
|
'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'])
|
||||||
|
_format_regex = re.compile('(#\{(.*?)\})|(\{\{(.*?)\}\})')
|
||||||
|
|
||||||
|
def __init__(self, pool, cr):
|
||||||
|
super(QWeb, self).__init__(pool, cr)
|
||||||
|
self._t = {}
|
||||||
|
|
||||||
|
self._render_tag = self.prefixed_methods('render_tag_')
|
||||||
|
self._render_att = self.prefixed_methods('render_att_')
|
||||||
|
|
||||||
|
def prefixed_methods(self, prefix):
|
||||||
|
""" Extracts all methods prefixed by ``prefix``, and returns a mapping
|
||||||
|
of (t-name, method) where the t-name is the method name with prefix
|
||||||
|
removed and underscore converted to dashes
|
||||||
|
|
||||||
|
:param str prefix:
|
||||||
|
:return: dict
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
(name[len(prefix):].replace('_', '-'), getattr(type(self), name))
|
||||||
|
for name in dir(self)
|
||||||
|
if name.startswith(prefix))
|
||||||
|
|
||||||
def register_tag(self, tag, func):
|
def register_tag(self, tag, func):
|
||||||
self._render_tag[tag] = func
|
self._render_tag[tag] = func
|
||||||
|
|
||||||
def add_template(self, x):
|
def load_document(self, x):
|
||||||
|
"""
|
||||||
|
Loads an XML document and installs any contained template in the engine
|
||||||
|
"""
|
||||||
if hasattr(x, 'documentElement'):
|
if hasattr(x, 'documentElement'):
|
||||||
dom = x
|
dom = x
|
||||||
elif x.startswith("<?xml"):
|
elif x.startswith("<?xml"):
|
||||||
dom = xml.dom.minidom.parseString(x)
|
dom = xml.dom.minidom.parseString(x)
|
||||||
else:
|
else:
|
||||||
dom = xml.dom.minidom.parse(x)
|
dom = xml.dom.minidom.parse(x)
|
||||||
|
|
||||||
for n in dom.documentElement.childNodes:
|
for n in dom.documentElement.childNodes:
|
||||||
if n.nodeType == 1 and n.getAttribute('t-name'):
|
if n.nodeType == self.node.ELEMENT_NODE and n.getAttribute('t-name'):
|
||||||
self._t[str(n.getAttribute("t-name"))] = n
|
self._t[str(n.getAttribute("t-name"))] = n
|
||||||
|
|
||||||
def get_template(self, name):
|
def get_template(self, name, context):
|
||||||
|
if context.loader and name not in self._t:
|
||||||
|
xml_doc = context.loader(name)
|
||||||
|
self.load_document(xml_doc)
|
||||||
|
|
||||||
if name in self._t:
|
if name in self._t:
|
||||||
return self._t[name]
|
return self._t[name]
|
||||||
elif self.loader:
|
|
||||||
xml = self.loader(name)
|
|
||||||
self.add_template(xml)
|
|
||||||
if name in self._t:
|
|
||||||
return self._t[name]
|
|
||||||
raise KeyError('qweb: template "%s" not found' % name)
|
raise KeyError('qweb: template "%s" not found' % name)
|
||||||
|
|
||||||
def eval(self, expr, v):
|
def eval(self, expr, v):
|
||||||
|
@ -147,17 +169,18 @@ class QWebXml(object):
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def render(self, tname, v=None, out=None):
|
def render(self, tname, v=None, out=None, loader=None, undefined_handler=None):
|
||||||
if v is None:
|
if v is None:
|
||||||
v = {}
|
v = {}
|
||||||
|
if not isinstance(v, QWebContext):
|
||||||
|
v = QWebContext(v, undefined_handler=undefined_handler, loader=loader)
|
||||||
v['__template__'] = tname
|
v['__template__'] = tname
|
||||||
stack = v.get('__stack__', [])
|
stack = v.get('__stack__', [])
|
||||||
if stack:
|
if stack:
|
||||||
v['__caller__'] = stack[-1]
|
v['__caller__'] = stack[-1]
|
||||||
stack.append(tname)
|
stack.append(tname)
|
||||||
v['__stack__'] = stack
|
v['__stack__'] = stack
|
||||||
v = QWebContext(v, self.undefined_handler)
|
return self.render_node(self.get_template(tname, v), v)
|
||||||
return self.render_node(self.get_template(tname), v)
|
|
||||||
|
|
||||||
def render_node(self, e, v):
|
def render_node(self, e, v):
|
||||||
r = ""
|
r = ""
|
||||||
|
@ -189,8 +212,7 @@ class QWebXml(object):
|
||||||
debugger = t_att.get('debug', 'pdb')
|
debugger = t_att.get('debug', 'pdb')
|
||||||
__import__(debugger).set_trace() # pdb, ipdb, pudb, ...
|
__import__(debugger).set_trace() # pdb, ipdb, pudb, ...
|
||||||
if t_render:
|
if t_render:
|
||||||
if t_render in self._render_tag:
|
r = self._render_tag[t_render](self, e, t_att, g_att, v)
|
||||||
r = self._render_tag[t_render](self, e, t_att, g_att, v)
|
|
||||||
else:
|
else:
|
||||||
r = self.render_element(e, t_att, g_att, v)
|
r = self.render_element(e, t_att, g_att, v)
|
||||||
if isinstance(r, unicode):
|
if isinstance(r, unicode):
|
||||||
|
@ -275,7 +297,7 @@ class QWebXml(object):
|
||||||
enum = self.eval_object(expr, v)
|
enum = self.eval_object(expr, v)
|
||||||
if enum is not None:
|
if enum is not None:
|
||||||
var = t_att.get('as', expr).replace('.', '_')
|
var = t_att.get('as', expr).replace('.', '_')
|
||||||
d = QWebContext(v.copy(), self.undefined_handler)
|
d = v.copy()
|
||||||
size = -1
|
size = -1
|
||||||
if isinstance(enum, (list, tuple)):
|
if isinstance(enum, (list, tuple)):
|
||||||
size = len(enum)
|
size = len(enum)
|
||||||
|
@ -313,11 +335,9 @@ class QWebXml(object):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def render_tag_call(self, e, t_att, g_att, v):
|
def render_tag_call(self, e, t_att, g_att, v):
|
||||||
if "import" in t_att:
|
d = v if 'import' in t_att else v.copy()
|
||||||
d = v
|
|
||||||
else:
|
|
||||||
d = QWebContext(v.copy(), self.undefined_handler)
|
|
||||||
d[0] = self.render_element(e, t_att, g_att, d)
|
d[0] = self.render_element(e, t_att, g_att, d)
|
||||||
|
|
||||||
return self.render(self.eval_format(t_att["call"], d), d)
|
return self.render(self.eval_format(t_att["call"], d), d)
|
||||||
|
|
||||||
def render_tag_set(self, e, t_att, g_att, v):
|
def render_tag_set(self, e, t_att, g_att, v):
|
|
@ -22,20 +22,17 @@ import collections
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import HTMLParser
|
import HTMLParser
|
||||||
from lxml import etree, html
|
from lxml import etree
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from openerp import tools
|
from openerp import tools
|
||||||
from openerp.osv import fields, osv, orm
|
from openerp.osv import fields, osv, orm
|
||||||
from openerp.tools import graph, SKIPPED_ELEMENT_TYPES
|
from openerp.tools import graph, SKIPPED_ELEMENT_TYPES
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from openerp.tools.safe_eval import safe_eval as eval
|
||||||
from openerp.tools.view_validation import valid_view
|
from openerp.tools.view_validation import valid_view
|
||||||
from openerp.tools import misc, qweb
|
from openerp.tools import misc
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -764,8 +761,10 @@ class view(osv.osv):
|
||||||
def loader(name):
|
def loader(name):
|
||||||
return self.read_template(cr, uid, name, context=context)
|
return self.read_template(cr, uid, name, context=context)
|
||||||
|
|
||||||
engine = qweb.QWebXml(loader=loader, undefined_handler=lambda key, v: None)
|
engine = self.pool['ir.templating.qweb']
|
||||||
return engine.render(id_or_xml_id, values)
|
return engine.render(
|
||||||
|
id_or_xml_id, values,
|
||||||
|
loader=loader, undefined_handler=lambda key, v: None)
|
||||||
|
|
||||||
# maybe used to print the workflow ?
|
# maybe used to print the workflow ?
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ from xml.dom import minidom as dom
|
||||||
|
|
||||||
import common
|
import common
|
||||||
|
|
||||||
from ..tools import qweb
|
|
||||||
|
|
||||||
impl = dom.getDOMImplementation()
|
impl = dom.getDOMImplementation()
|
||||||
document = impl.createDocument(None, None, None)
|
document = impl.createDocument(None, None, None)
|
||||||
|
|
||||||
|
@ -20,7 +18,7 @@ class RegistryProxy(object):
|
||||||
class TestQWebTField(common.TransactionCase):
|
class TestQWebTField(common.TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestQWebTField, self).setUp()
|
super(TestQWebTField, self).setUp()
|
||||||
self.engine = qweb.QWebXml()
|
self.engine = self.registry('ir.templating.qweb')
|
||||||
|
|
||||||
def test_trivial(self):
|
def test_trivial(self):
|
||||||
field = document.createElement('span')
|
field = document.createElement('span')
|
||||||
|
|
Loading…
Reference in New Issue