[IMP] tools.safe_eval_qweb: methods intended to provide more restricted alternatives...
[odoo/odoo.git] / openerp / tools / qweb.py
index c076b3e..abe60fb 100644 (file)
@@ -1,51 +1,15 @@
-import cgi
 import logging
 import re
-import types
 
-#from openerp.tools.safe_eval import safe_eval as eval
+import werkzeug.utils
+from openerp.tools.safe_eval_qweb import safe_eval_qweb as eval, UndefinedError, SecurityError
 
 import xml   # FIXME use lxml
-import xml.dom.minidom
 import traceback
+from openerp.osv import osv, orm
 
 _logger = logging.getLogger(__name__)
 
-class QWebContext(dict):
-    def __init__(self, data, undefined_handler=None):
-        self.undefined_handler = undefined_handler
-        d = {
-            'True': True,
-            'False': False,
-            'None': None,
-            'str': str,
-            'globals': locals,
-            'locals': locals,
-            'bool': bool,
-            'dict': dict,
-            'list': list,
-            'tuple': tuple,
-            'map': map,
-            'abs': abs,
-            'min': min,
-            'max': max,
-            'reduce': reduce,
-            'filter': filter,
-            'round': round,
-            'len': len,
-            'set': set
-        }
-        d.update(data)
-        dict.__init__(self, d)
-        self['defined'] = lambda key: key in self
-
-    def __getitem__(self, key):
-        if key in self:
-            return self.get(key)
-        elif not self.undefined_handler:
-            raise NameError("QWeb: name %r is not defined while rendering template %r" % (key, self.get('__template__')))
-        else:
-            return self.get(key, self.undefined_handler(key, self))
 
 class QWebXml(object):
     """QWeb Xml templating engine
@@ -69,7 +33,7 @@ class QWebXml(object):
         self.node = xml.dom.Node
         self._t = {}
         self._render_tag = {}
-        self._format_regex = re.compile('#\{(.*?)\}')
+        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_'
@@ -94,7 +58,7 @@ class QWebXml(object):
         else:
             dom = xml.dom.minidom.parse(x)
         for n in dom.documentElement.childNodes:
-            if n.getAttribute('t-name'):
+            if n.nodeType == 1 and n.getAttribute('t-name'):
                 self._t[str(n.getAttribute("t-name"))] = n
 
     def get_template(self, name):
@@ -110,6 +74,13 @@ class QWebXml(object):
     def eval(self, expr, v):
         try:
             return eval(expr, None, v)
+        except (osv.except_osv, orm.except_orm), err:
+            raise orm.except_orm("QWeb Error", "Invalid expression %r while rendering template '%s'.\n\n%s" % (expr, v.get('__template__'), err[1]))
+        except (UndefinedError, SecurityError), err:
+            if self.undefined_handler:
+                return self.undefined_handler(expr, v)
+            else:
+                raise SyntaxError(err.message)
         except Exception:
             raise SyntaxError("QWeb: invalid expression %r while rendering template '%s'.\n\n%s" % (expr, v.get('__template__'), traceback.format_exc()))
 
@@ -120,6 +91,7 @@ class QWebXml(object):
         if expr == "0":
             return v.get(0, '')
         val = self.eval(expr, v)
+
         if isinstance(val, unicode):
             return val.encode("utf8")
         return str(val)
@@ -128,7 +100,8 @@ class QWebXml(object):
         use_native = True
         for m in self._format_regex.finditer(expr):
             use_native = False
-            expr = expr.replace(m.group(), self.eval_str(m.groups()[0], v))
+            expr = expr.replace(m.group(), self.eval_str(m.groups()[1] or m.groups()[3], v))
+
         if not use_native:
             return expr
         else:
@@ -153,7 +126,6 @@ class QWebXml(object):
             v['__caller__'] = stack[-1]
         stack.append(tname)
         v['__stack__'] = stack
-        v = QWebContext(v, self.undefined_handler)
         return self.render_node(self.get_template(tname), v)
 
     def render_node(self, e, v):
@@ -166,7 +138,7 @@ class QWebXml(object):
             t_att = {}
             for (an, av) in e.attributes.items():
                 an = str(an)
-                if isinstance(av, types.UnicodeType):
+                if isinstance(av, unicode):
                     av = av.encode("utf8")
                 else:
                     av = av.nodeValue.encode("utf8")
@@ -180,12 +152,18 @@ class QWebXml(object):
                             t_render = an[2:]
                         t_att[an[2:]] = av
                 else:
-                    g_att += ' %s="%s"' % (an, cgi.escape(av, 1))
+                    g_att += ' %s="%s"' % (an, werkzeug.utils.escape(av))
+
+            if 'debug' in t_att:
+                debugger = t_att.get('debug', 'pdb')
+                __import__(debugger).set_trace() # pdb, ipdb, pudb, ...
             if t_render:
                 if t_render in self._render_tag:
                     r = self._render_tag[t_render](self, e, t_att, g_att, v)
             else:
                 r = self.render_element(e, t_att, g_att, v)
+        if isinstance(r, unicode):
+            return r.encode('utf-8')
         return r
 
     def render_element(self, e, t_att, g_att, v, inner=None):
@@ -214,7 +192,9 @@ class QWebXml(object):
         if name == "t":
             return inner
         elif len(inner) or name not in self._void_elements:
-            return "<%s%s>%s</%s>" % (name, g_att, inner, name)
+            return "<%s%s>%s</%s>" % tuple(
+                v if isinstance(v, str) else v.encode('utf-8')
+                for v in (name, g_att, inner, name))
         else:
             return "<%s%s/>" % (name, g_att)
 
@@ -223,10 +203,24 @@ class QWebXml(object):
         if an.startswith("t-attf-"):
             att, val = an[7:], self.eval_format(av, v)
         elif an.startswith("t-att-"):
-            att, val = an[6:], self.eval_str(av, v)
+            att, val = an[6:], self.eval(av, v)
+            if isinstance(val, unicode):
+                val = val.encode("utf8")
         else:
             att, val = self.eval_object(av, v)
-        return ' %s="%s"' % (att, cgi.escape(val, 1))
+        return val and ' %s="%s"' % (att, werkzeug.utils.escape(val)) or " "
+
+    def render_att_href(self, e, an, av, v):
+        return self.url_for(e, an, av, v)
+    def render_att_src(self, e, an, av, v):
+        return self.url_for(e, an, av, v)
+    def render_att_action(self, e, an, av, v):
+        return self.url_for(e, an, av, v)
+    def url_for(self, e, an, av, v):
+        if 'url_for' not in v:
+            raise KeyError("qweb: no 'url_for' found in context")
+        path = str(v['url_for'](self.eval_format(av, v)))
+        return ' %s="%s"' % (an[2:], werkzeug.utils.escape(path))
 
     # Tags
     def render_tag_raw(self, e, t_att, g_att, v):
@@ -238,11 +232,11 @@ class QWebXml(object):
         return self.render_element(e, t_att, g_att, v, inner)
 
     def render_tag_esc(self, e, t_att, g_att, v):
-        inner = cgi.escape(self.eval_str(t_att["esc"], v))
+        inner = werkzeug.utils.escape(self.eval_str(t_att["esc"], v))
         return self.render_element(e, t_att, g_att, v, inner)
 
     def render_tag_escf(self, e, t_att, g_att, v):
-        inner = cgi.escape(self.eval_format(t_att["escf"], v))
+        inner = werkzeug.utils.escape(self.eval_format(t_att["escf"], v))
         return self.render_element(e, t_att, g_att, v, inner)
 
     def render_tag_foreach(self, e, t_att, g_att, v):
@@ -250,11 +244,9 @@ class QWebXml(object):
         enum = self.eval_object(expr, v)
         if enum is not None:
             var = t_att.get('as', expr).replace('.', '_')
-            d = QWebContext(v.copy(), self.undefined_handler)
+            d = v.copy()
             size = -1
-            if isinstance(enum, types.ListType):
-                size = len(enum)
-            elif isinstance(enum, types.TupleType):
+            if isinstance(enum, (list, tuple)):
                 size = len(enum)
             elif hasattr(enum, 'count'):
                 size = enum.count()
@@ -273,15 +265,15 @@ class QWebXml(object):
                     d["%s_parity" % var] = 'odd'
                 else:
                     d["%s_parity" % var] = 'even'
-                if isinstance(i, types.DictType):
-                    d.update(i)
-                else:
+                if 'as' in t_att:
                     d[var] = i
+                elif isinstance(i, dict):
+                    d.update(i)
                 ru.append(self.render_element(e, t_att, g_att, d))
                 index += 1
             return "".join(ru)
         else:
-            return "qweb: t-foreach %s not found." % expr
+            raise NameError("QWeb: foreach enumerator %r is not defined while rendering template %r" % (expr, v.get('__template__')))
 
     def render_tag_if(self, e, t_att, g_att, v):
         if self.eval_bool(t_att["if"], v):
@@ -293,9 +285,9 @@ class QWebXml(object):
         if "import" in t_att:
             d = v
         else:
-            d = QWebContext(v.copy(), self.undefined_handler)
+            d = v.copy()
         d[0] = self.render_element(e, t_att, g_att, d)
-        return self.render(t_att["call"], d)
+        return self.render(self.eval_format(t_att["call"], d), d)
 
     def render_tag_set(self, e, t_att, g_att, v):
         if "value" in t_att:
@@ -308,35 +300,44 @@ class QWebXml(object):
 
     def render_tag_field(self, e, t_att, g_att, v):
         """ eg: <span t-record="browse_record(res.partner, 1)" t-field="phone">+1 555 555 8069</span>"""
+        node_name = e.nodeName
+        assert node_name not in ("table", "tbody", "thead", "tfoot", "tr", "td",
+                                 "ol", "ul", "ol", "dl", "dt", "dd"),\
+            "RTE widgets do not work correctly on %r elements" % node_name
+        assert node_name != 't',\
+            "t-field can not be used on a t element, provide an actual HTML node"
 
         record, field = t_att["field"].rsplit('.', 1)
         record = self.eval_object(record, v)
 
-        inner = ""
-        field_type = record._model._all_columns.get(field).column._type
+        column = record._model._all_columns[field].column
+        field_type = column._type
+
+        req = v['request']
+        converter = req.registry['ir.fields.converter'].from_field(
+            req.cr, req.uid, record._model, column, totype='html')
+
+        content = None
         try:
-            if field_type == 'many2one':
-                field_data = record.read([field])[0].get(field)
-                inner = field_data and field_data[1] or ""
-            else:
-                inner = getattr(record, field) or ""
-            if isinstance(inner, types.UnicodeType):
-                inner = inner.encode("utf8")
-            if field_type != 'html':
-                cgi.escape(str(inner))
-            if e.tagName != 't':
-                g_att += ''.join(
-                    ' %s="%s"' % (name, cgi.escape(str(value), True))
-                    for name, value in [
-                        ('data-oe-model', record._model._name),
-                        ('data-oe-id', str(record.id)),
-                        ('data-oe-field', field),
-                        ('data-oe-type', field_type),
-                    ]
-                )
-        except AttributeError:
+            value = record[field]
+            if value:
+                content, warnings = converter(value)
+                assert not warnings
+        except KeyError:
             _logger.warning("t-field no field %s for model %s", field, record._model._name)
 
-        return self.render_element(e, t_att, g_att, v, str(inner))
+        g_att += ''.join(
+            ' %s="%s"' % (name, werkzeug.utils.escape(value))
+            for name, value in [
+                ('data-oe-model', record._model._name),
+                ('data-oe-id', record.id),
+                ('data-oe-field', field),
+                ('data-oe-type', field_type),
+                ('data-oe-translate', '1' if column.translate else '0'),
+                ('data-oe-expression', t_att['field']),
+            ]
+        )
+
+        return self.render_element(e, t_att, g_att, v, content or "")
 
 # leave this, al.