[IMP] tools.safe_eval_qweb: methods intended to provide more restricted alternatives...
[odoo/odoo.git] / openerp / tools / qweb.py
index 7919de8..abe60fb 100644 (file)
@@ -1,53 +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__)
 
-BUILTINS = {
-    'False': False,
-    'None': None,
-    'True': True,
-    'abs': abs,
-    'bool': bool,
-    'dict': dict,
-    'filter': filter,
-    'len': len,
-    'list': list,
-    'map': map,
-    'max': max,
-    'min': min,
-    'reduce': reduce,
-    'repr': repr,
-    'round': round,
-    'set': set,
-    'str': str,
-    'tuple': tuple,
-}
-
-class QWebContext(dict):
-    def __init__(self, data, undefined_handler=None):
-        self.undefined_handler = undefined_handler
-        dic = BUILTINS.copy()
-        dic.update(data)
-        dict.__init__(self, dic)
-        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
@@ -71,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_'
@@ -114,6 +76,11 @@ class QWebXml(object):
             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()))
 
@@ -124,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)
@@ -132,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:
@@ -157,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):
@@ -170,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")
@@ -184,7 +152,7 @@ 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')
@@ -194,6 +162,8 @@ class QWebXml(object):
                     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):
@@ -222,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)
 
@@ -236,7 +208,19 @@ class QWebXml(object):
                 val = val.encode("utf8")
         else:
             att, val = self.eval_object(av, v)
-        return val and ' %s="%s"' % (att, cgi.escape(str(val), 1)) or " "
+        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):
@@ -248,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):
@@ -260,9 +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, types.TupleType)):
+            if isinstance(enum, (list, tuple)):
                 size = len(enum)
             elif hasattr(enum, 'count'):
                 size = enum.count()
@@ -283,7 +267,7 @@ class QWebXml(object):
                     d["%s_parity" % var] = 'even'
                 if 'as' in t_att:
                     d[var] = i
-                elif isinstance(i, types.DictType):
+                elif isinstance(i, dict):
                     d.update(i)
                 ru.append(self.render_element(e, t_att, g_att, d))
                 index += 1
@@ -301,7 +285,7 @@ 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(self.eval_format(t_att["call"], d), d)
 
@@ -316,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.