[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:
Xavier Morel 2013-10-04 13:41:55 +02:00
parent 6507bdd82c
commit 81a82ca37f
4 changed files with 67 additions and 49 deletions

View File

@ -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:

View File

@ -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):

View File

@ -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 ?

View File

@ -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')