diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py
index 35184f7a565..6fb675d23c2 100644
--- a/openerp/addons/base/ir/__init__.py
+++ b/openerp/addons/base/ir/__init__.py
@@ -37,6 +37,7 @@ import ir_config_parameter
import osv_memory_autovacuum
import ir_mail_server
import ir_fields
+import ir_qweb
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/ir/ir_fields.py b/openerp/addons/base/ir/ir_fields.py
index de45d2d1f1f..39f7f99b5fa 100644
--- a/openerp/addons/base/ir/ir_fields.py
+++ b/openerp/addons/base/ir/ir_fields.py
@@ -149,30 +149,6 @@ class ir_fields_converter(orm.Model):
return functools.partial(
converter, cr, uid, model, column, context=context)
- def from_field(self, cr, uid, model, column, totype='str', context=None):
- """ Fetches a converter for the provided column object, to the
- specified type.
-
- A converter is a callable taking a value as returned by a read on the
- column, and returning a formatted value of type ``totype``.
-
- By default, tries to get a method on itself with a name matching the
- pattern ``_$totype_from_${column._type}`` and returns it.
-
- Error conditions are similar to :meth:`~.to_field`
-
- :param column: column object to generate a value from
- :param str totype:
- :return: a function (column.read_type -> totype)
- :rtype: Callable | None
- """
- converter = getattr(
- self, '_%s_from_%s' % (totype, column._type), None)
- if not converter: return None
-
- return functools.partial(
- converter, cr, uid, model, column, context=context)
-
def _str_to_boolean(self, cr, uid, model, column, value, context=None):
# all translatables used for booleans
true, yes, false, no = _(u"true"), _(u"yes"), _(u"false"), _(u"no")
@@ -460,56 +436,3 @@ class ir_fields_converter(orm.Model):
commands.append(CREATE(writable))
return commands, warnings
-
- def _html_from_passthrough(self, cr, uid, model, column, value, context=None):
- """
- escapes the value and returns it directly
- """
- return werkzeug.utils.escape(value), []
-
- _html_from_char = _html_from_integer = \
- _html_from_date = _html_from_datetime = _html_from_passthrough
-
- def _html_from_float(self, cr, uid, model, column, value, context=None):
- width, precision = column.digits or (None, None)
- if precision is None:
- fmt = '{value}'
- else:
- fmt = '{value:.{precision}f}'
-
- return werkzeug.utils.escape(
- fmt.format(value=value, width=width, precision=precision, )), []
-
- def _html_from_text(self, cr, uid, model, column, value, context=None):
- """
- Escapes the value and converts newlines to br. This is bullshit.
- """
- return werkzeug.utils.escape(value).replace('\n', '
\n'), []
-
- def _html_from_selection(self, cr, uid, model, column, value, context=None):
- selection = dict(openerp.osv.fields.selection.reify(
- cr, uid, model, column, context=context))
- return werkzeug.utils.escape(selection[value]), []
-
- def _html_from_html(self, cr, uid, model, column, value, context=None):
- return value, []
-
- def _html_from_many2one(self, cr, uid, model, column, value, context=None):
- return werkzeug.utils.escape(value.name_get()[0][1]).replace('\n', '
\n'), []
-
- def _html_from_binary(self, cr, uid, model, column, value, context=None):
- try:
- image = Image.open(cStringIO.StringIO(value.decode('base64')))
- except IOError:
- raise ValueError("Non-image binary fields can not be converted to HTML")
- try: image.verify()
- except: # no idea what "suitable exceptions" are
- raise ValueError("Invalid image content")
-
- mime = PIL_MIME_MAPPING.get(image.format)
- if mime is None:
- raise ValueError("Unknown PIL format %s" % image.format)
-
- return ('
' % (mime, value)), []
-
-PIL_MIME_MAPPING = {'PNG': 'image/png', 'JPEG': 'image/jpeg', 'GIF': 'image/gif', }
diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py
new file mode 100644
index 00000000000..3fe5cb03c03
--- /dev/null
+++ b/openerp/addons/base/ir/ir_qweb.py
@@ -0,0 +1,647 @@
+# -*- coding: utf-8 -*-
+import cStringIO
+import json
+import datetime
+import dateutil
+import logging
+import re
+import traceback
+import urllib
+import xml # FIXME use lxml and etree
+
+import Image
+import werkzeug.utils
+
+from openerp.osv import osv, orm, fields
+
+_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,
+ 'quote': urllib.quote,
+ 'urlencode': urllib.urlencode,
+ 'datetime': datetime,
+ # dateutil.relativedelta is an old-style class and cannot be directly
+ # instanciated wihtin a jinja2 expression, so a lambda "proxy" is
+ # is needed, apparently.
+ 'relativedelta': lambda *a, **kw : dateutil.relativedelta.relativedelta(*a, **kw),
+}
+
+## We use a jinja2 sandboxed environment to render qWeb templates.
+#from openerp.tools.safe_eval import safe_eval as eval
+#from jinja2.sandbox import SandboxedEnvironment
+#from jinja2.exceptions import SecurityError, UndefinedError
+#UNSAFE = ["browse", "search", "read", "unlink", "read_group"]
+#SAFE = ["_name"]
+
+class QWebContext(dict):
+ def __init__(self, data, undefined_handler=None, loader=None):
+ self.loader = loader
+ self.undefined_handler = undefined_handler
+ dic = BUILTINS.copy()
+ dic.update(data)
+ super(QWebContext, self).__init__(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))
+
+ def safe_eval(self, expr):
+ # This is too slow, we should cached compiled expressions attribute of
+ # qweb to will be changed into a model object ir.qweb.
+ #
+ # The cache should be on qweb, and qweb context contructor take qweb as
+ # argument to store the cache.
+ #
+ #class QWebSandboxedEnvironment(SandboxedEnvironment):
+ # def is_safe_attribute(self, obj, attr, value):
+ # if str(attr) in SAFE:
+ # res = True
+ # else:
+ # res = super(QWebSandboxedEnvironment, self).is_safe_attribute(obj, attr, value)
+ # if str(attr) in UNSAFE or not res:
+ # raise SecurityError("access to attribute '%s' of '%s' object is unsafe." % (attr,obj))
+ # return res
+ #env = qWebSandboxedEnvironment(variable_start_string="${", variable_end_string="}")
+ #env.globals.update(context)
+ #env.compile_expression(expr)()
+ return eval(expr, None, self)
+
+ 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
+
+ The templating engine use a very simple syntax, "magic" xml attributes, to
+ produce any kind of texutal output (even non-xml).
+
+ QWebXml:
+ the template engine core implements the basic magic attributes:
+
+ t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
+
+
+ - loader: function that return a template
+
+ QWeb rendering can be used for many tasks. As a result, customizations
+ made by one task context (to either the main qweb rendering or to specific
+ fields rendering) could break other tasks.
+
+ To avoid that, ``ir.qweb`` was consciously made inheritable and the "root"
+ of an object hierarchy. If you need extensions or alterations which could
+ be incompatible with other subsystems, you should create a local object
+ inheriting from ``ir.qweb`` and customize that.
+
+ If you need to customize t-fields rendering, subclass the ir.qweb.field
+ model (and its sub-models) then override :meth:`~.get_converter_for` to
+ fetch the right field converters for your qweb model.
+ """
+
+ _name = 'ir.qweb'
+
+ node = xml.dom.Node
+ _void_elements = frozenset([
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
+ 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'])
+ _format_regex = re.compile(
+ '(?:'
+ # ruby-style pattern
+ '#\{(.+?)\}'
+ ')|(?:'
+ # jinja-style pattern
+ '\{\{(.+?)\}\}'
+ ')')
+
+ 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):
+ self._render_tag[tag] = func
+
+ def load_document(self, x):
+ """
+ Loads an XML document and installs any contained template in the engine
+ """
+ if hasattr(x, 'documentElement'):
+ dom = x
+ elif x.startswith("%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)
+
+ # Attributes
+ def render_att_att(self, e, an, av, v):
+ 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(av, v)
+ if isinstance(val, unicode):
+ val = val.encode("utf8")
+ else:
+ att, val = self.eval_object(av, v)
+ 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):
+ inner = self.eval_str(t_att["raw"], v)
+ return self.render_element(e, t_att, g_att, v, inner)
+
+ def render_tag_rawf(self, e, t_att, g_att, v):
+ inner = self.eval_format(t_att["rawf"], v)
+ return self.render_element(e, t_att, g_att, v, inner)
+
+ def render_tag_esc(self, e, t_att, g_att, 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 = 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):
+ expr = t_att["foreach"]
+ enum = self.eval_object(expr, v)
+ if enum is not None:
+ var = t_att.get('as', expr).replace('.', '_')
+ d = v.copy()
+ size = -1
+ if isinstance(enum, (list, tuple)):
+ size = len(enum)
+ elif hasattr(enum, 'count'):
+ size = enum.count()
+ d["%s_size" % var] = size
+ d["%s_all" % var] = enum
+ index = 0
+ ru = []
+ for i in enum:
+ d["%s_value" % var] = i
+ d["%s_index" % var] = index
+ d["%s_first" % var] = index == 0
+ d["%s_even" % var] = index % 2
+ d["%s_odd" % var] = (index + 1) % 2
+ d["%s_last" % var] = index + 1 == size
+ if index % 2:
+ d["%s_parity" % var] = 'odd'
+ else:
+ d["%s_parity" % var] = 'even'
+ 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:
+ 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):
+ return self.render_element(e, t_att, g_att, v)
+ else:
+ return ""
+
+ def render_tag_call(self, e, t_att, g_att, v):
+ d = v if 'import' in t_att else v.copy()
+ d[0] = self.render_element(e, t_att, g_att, 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:
+ v[t_att["set"]] = self.eval_object(t_att["value"], v)
+ elif "valuef" in t_att:
+ v[t_att["set"]] = self.eval_format(t_att["valuef"], v)
+ else:
+ v[t_att["set"]] = self.render_element(e, t_att, g_att, v)
+ return ""
+
+ def render_tag_field(self, e, t_att, g_att, v):
+ """ eg: +1 555 555 8069"""
+ 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_name = t_att["field"].rsplit('.', 1)
+ record = self.eval_object(record, v)
+
+ column = record._model._all_columns[field_name].column
+ options = json.loads(t_att.get('field-options') or '{}')
+ field_type = get_field_type(column, options)
+
+ converter = self.get_converter_for(field_type)
+
+ return converter.to_html(record._cr, record._uid, field_name, record, options,
+ e, t_att, g_att, v)
+
+ def get_converter_for(self, field_type):
+ return self.pool.get('ir.qweb.field.' + field_type,
+ self.pool['ir.qweb.field'])
+
+
+class FieldConverter(osv.AbstractModel):
+ """ Used to convert a t-field specification into an output HTML field.
+
+ :meth:`~.to_html` is the entry point of this conversion from QWeb, it:
+
+ * converts the record value to html using :meth:`~.record_to_html`
+ * generates the metadata attributes (``data-oe-``) to set on the root
+ result node
+ * generates the root result node itself through :meth:`~.render_element`
+ """
+ _name = 'ir.qweb.field'
+
+ def attributes(self, cr, uid, field_name, record, options,
+ source_element, g_att, t_att, qweb_context):
+ """
+ Generates the metadata attributes (prefixed by ``data-oe-`` for the
+ root node of the field conversion. Attribute values are escaped by the
+ parent using ``werkzeug.utils.escape``.
+
+ The default attributes are:
+
+ * ``model``, the name of the record's model
+ * ``id`` the id of the record to which the field belongs
+ * ``field`` the name of the converted field
+ * ``type`` the logical field type (widget, may not match the column's
+ ``type``, may not be any _column subclass name)
+ * ``translate``, a boolean flag (``0`` or ``1``) denoting whether the
+ column is translatable
+ * ``expression``, the original expression
+
+ :returns: iterable of (attribute name, attribute value) pairs.
+ """
+ column = record._model._all_columns[field_name].column
+ field_type = get_field_type(column, options)
+ return [
+ ('data-oe-model', record._model._name),
+ ('data-oe-id', record.id),
+ ('data-oe-field', field_name),
+ ('data-oe-type', field_type),
+ ('data-oe-expression', t_att['field']),
+ ]
+
+ def value_to_html(self, cr, uid, value, column, options=None):
+ """ Converts a single value to its HTML version/output
+ """
+ return werkzeug.utils.escape(value)
+
+ def record_to_html(self, cr, uid, field_name, record, column, options=None):
+ """ Converts the specified field of the browse_record ``record`` to
+ HTML
+ """
+ return self.value_to_html(
+ cr, uid, record[field_name], column, options=None)
+
+ def to_html(self, cr, uid, field_name, record, options,
+ source_element, t_att, g_att, qweb_context):
+ """ Converts a ``t-field`` to its HTML output. A ``t-field`` may be
+ extended by a ``t-field-options``, which is a JSON-serialized mapping
+ of configuration values.
+
+ A default configuration key is ``widget`` which can override the
+ field's own ``_type``.
+ """
+ content = None
+ try:
+ content = self.record_to_html(
+ cr, uid, field_name, record,
+ record._model._all_columns[field_name].column,
+ options)
+ except Exception:
+ _logger.warning("Could not get field %s for model %s",
+ field_name, record._model._name, exc_info=True)
+
+ g_att += ''.join(
+ ' %s="%s"' % (name, werkzeug.utils.escape(value))
+ for name, value in self.attributes(
+ cr, uid, field_name, record, options,
+ source_element, g_att, t_att, qweb_context)
+ )
+
+ return self.render_element(cr, uid, source_element, t_att, g_att,
+ qweb_context, content)
+
+ def qweb_object(self):
+ return self.pool['ir.qweb']
+
+ def render_element(self, cr, uid, source_element, t_att, g_att,
+ qweb_context, content):
+ """ Final rendering hook, by default just calls ir.qweb's ``render_element``
+ """
+ return self.qweb_object().render_element(
+ source_element, t_att, g_att, qweb_context, content or '')
+
+class FloatConverter(osv.AbstractModel):
+ _name = 'ir.qweb.field.float'
+ _inherit = 'ir.qweb.field'
+
+ def value_to_html(self, cr, uid, value, column, options=None):
+ width, precision = column.digits or (None, None)
+ fmt = '{value}' if precision is None else '{value:.{precision}f}'
+
+ return werkzeug.utils.escape(
+ fmt.format(value=value, width=width, precision=precision, ))
+
+class TextConverter(osv.AbstractModel):
+ _name = 'ir.qweb.field.text'
+ _inherit = 'ir.qweb.field'
+
+ def value_to_html(self, cr, uid, value, column, options=None):
+ """
+ Escapes the value and converts newlines to br. This is bullshit.
+ """
+ return werkzeug.utils.escape(value).replace('\n', '
\n')
+
+class SelectionConverter(osv.AbstractModel):
+ _name = 'ir.qweb.field.selection'
+ _inherit = 'ir.qweb.field'
+
+ def record_to_html(self, cr, uid, field_name, record, column, options=None):
+ # FIXME: context
+ value = record[field_name]
+ selection = dict(fields.selection.reify(
+ cr, uid, record._model, column))
+ return self.value_to_html(
+ cr, uid, selection[value], column, options=options)
+
+class ManyToOneConverter(osv.AbstractModel):
+ _name = 'ir.qweb.field.many2one'
+ _inherit = 'ir.qweb.field'
+
+ def value_to_html(self, cr, uid, value, column, options=None):
+ return werkzeug.utils.escape(value.name_get()[0][1]).replace('\n', '
\n')
+
+class HTMLConverter(osv.AbstractModel):
+ _name = 'ir.qweb.field.html'
+ _inherit = 'ir.qweb.field'
+
+ def value_to_html(self, cr, uid, value, column, options=None):
+ return value
+
+class ImageConverter(osv.AbstractModel):
+ """ ``image`` widget rendering, inserts a data:uri-using image tag in the
+ document. May be overridden by e.g. the website module to generate links
+ instead.
+
+ .. todo:: what happens if different output need different converters? e.g.
+ reports may need embedded images or FS links whereas website
+ needs website-aware
+ """
+ _name = 'ir.qweb.field.image'
+ _inherit = 'ir.qweb.field'
+
+ def value_to_html(self, cr, uid, value, column, options=None):
+ try:
+ image = Image.open(cStringIO.StringIO(value.decode('base64')))
+ image.verify()
+ except IOError:
+ raise ValueError("Non-image binary fields can not be converted to HTML")
+ except: # image.verify() throws "suitable exceptions", I have no idea what they are
+ raise ValueError("Invalid image content")
+
+ return '
' % (Image.MIME[image.format], value)
+
+class MonetaryConverter(osv.AbstractModel):
+ """ ``monetary`` converter, has a mandatory option
+ ``display_currency``.
+
+ .. note:: the monetary converter internally adds the qweb context to its
+ options mapping, so that the context is available to callees.
+ It's set under the ``_qweb_context`` key.
+ """
+ _name = 'ir.qweb.field.monetary'
+ _inherit = 'ir.qweb.field'
+
+ def to_html(self, cr, uid, field_name, record, options,
+ source_element, t_att, g_att, qweb_context):
+ options['_qweb_context'] = qweb_context
+ return super(MonetaryConverter, self).to_html(
+ cr, uid, field_name, record, options,
+ source_element, t_att, g_att, qweb_context)
+
+ def record_to_html(self, cr, uid, field_name, record, column, options):
+ Currency = self.pool['res.currency']
+ display = self.display_currency(cr, uid, options)
+
+ symbol_pre = symbol_post = space_pre = space_post = u''
+ if display.position == 'before':
+ space_pre = u' '
+ symbol_pre = display.symbol
+ else:
+ space_post = u' '
+ symbol_post = display.symbol
+
+ return u'{symbol_pre}{space_pre}' \
+ u'{0}' \
+ u'{space_post}{symbol_post}'.format(
+ Currency.round(cr, uid, display, record[field_name]),
+ space_pre=space_pre,
+ symbol_pre=symbol_pre,
+ space_post=space_post,
+ symbol_post=symbol_post,)
+
+ def display_currency(self, cr, uid, options):
+ return self.qweb_object().eval_object(
+ options['display_currency'], options['_qweb_context'])
+
+def get_field_type(column, options):
+ """ Gets a t-field's effective type from the field's column and its options
+ """
+ return options.get('widget', column._type)
+
+# vim:et:
diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py
index b77eaf38631..6fa609826f4 100644
--- a/openerp/addons/base/ir/ir_ui_view.py
+++ b/openerp/addons/base/ir/ir_ui_view.py
@@ -27,6 +27,7 @@ import os
import time
import HTMLParser
+from lxml import etree
import openerp
from openerp import tools
@@ -34,7 +35,7 @@ from openerp.osv import fields, osv, orm
from openerp.tools import graph, SKIPPED_ELEMENT_TYPES
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.view_validation import valid_view
-from openerp.tools import misc, qweb
+from openerp.tools import misc
from openerp.osv.orm import browse_record, browse_record_list
_logger = logging.getLogger(__name__)
@@ -766,7 +767,7 @@ class view(osv.osv):
self.translate_qweb(cr, uid, id_, node, lang, context)
return arch
- def render(self, cr, uid, id_or_xml_id, values, context=None):
+ def render(self, cr, uid, id_or_xml_id, values, engine='ir.qweb', context=None):
if not context:
context = {}
@@ -783,8 +784,10 @@ class view(osv.osv):
def loader(name):
return self.read_template(cr, uid, name, context=context)
- engine = qweb.QWebXml(loader=loader, undefined_handler=lambda key, v: None)
- return engine.render(id_or_xml_id, values)
+
+ return self.pool[engine].render(
+ id_or_xml_id, values,
+ loader=loader, undefined_handler=lambda key, v: None)
# maybe used to print the workflow ?
diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py
index a496a1eccdf..61128a5ce99 100644
--- a/openerp/addons/base/res/res_currency.py
+++ b/openerp/addons/base/res/res_currency.py
@@ -40,20 +40,21 @@ class res_currency(osv.osv):
if context is None:
context = {}
res = {}
- if 'date' in context:
- date = context['date']
- else:
- date = time.strftime('%Y-%m-%d')
- date = date or time.strftime('%Y-%m-%d')
+
+ date = context.get('date') or time.strftime('%Y-%m-%d')
# Convert False values to None ...
currency_rate_type = context.get('currency_rate_type_id') or None
# ... and use 'is NULL' instead of '= some-id'.
operator = '=' if currency_rate_type else 'is'
for id in ids:
- cr.execute("SELECT currency_id, rate FROM res_currency_rate WHERE currency_id = %s AND name <= %s AND currency_rate_type_id " + operator +" %s ORDER BY name desc LIMIT 1" ,(id, date, currency_rate_type))
+ cr.execute('SELECT rate FROM res_currency_rate '
+ 'WHERE currency_id = %s '
+ 'AND name <= %s '
+ 'AND currency_rate_type_id ' + operator + ' %s '
+ 'ORDER BY name desc LIMIT 1',
+ (id, date, currency_rate_type))
if cr.rowcount:
- id, rate = cr.fetchall()[0]
- res[id] = rate
+ res[id] = cr.fetchone()[0]
elif not raise_on_no_rate:
res[id] = 0
else:
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index a9266504f74..1401b188e0a 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -111,4 +111,12 @@
"access_ir_mail_server","ir_mail_server","model_ir_mail_server","group_system",1,1,1,1
"access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1
-
+access_ir_qweb,access_ir_qweb,model_ir_qweb,,0,0,0,0
+access_ir_qweb_field,access_ir_qweb_field,model_ir_qweb_field,,0,0,0,0
+access_ir_qweb_field_float,access_ir_qweb_field_float,model_ir_qweb_field_float,,0,0,0,0
+access_ir_qweb_field_text,access_ir_qweb_field_text,model_ir_qweb_field_text,,0,0,0,0
+access_ir_qweb_field_selection,access_ir_qweb_field_selection,model_ir_qweb_field_selection,,0,0,0,0
+access_ir_qweb_field_many2one,access_ir_qweb_field_many2one,model_ir_qweb_field_many2one,,0,0,0,0
+access_ir_qweb_field_html,access_ir_qweb_field_html,model_ir_qweb_field_html,,0,0,0,0
+access_ir_qweb_field_image,access_ir_qweb_field_image,model_ir_qweb_field_image,,0,0,0,0
+access_ir_qweb_field_monetary,access_ir_qweb_field_monetary,model_ir_qweb_field_monetary,,0,0,0,0
diff --git a/openerp/tests/addons/test_converter/ir.model.access.csv b/openerp/tests/addons/test_converter/ir.model.access.csv
index 66937821c94..ff4ac975996 100644
--- a/openerp/tests/addons/test_converter/ir.model.access.csv
+++ b/openerp/tests/addons/test_converter/ir.model.access.csv
@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_converter_model,access_converter_model,model_test_converter_test_model,,1,1,1,1
access_test_converter_test_model_sub,access_test_converter_test_model_sub,model_test_converter_test_model_sub,,1,1,1,1
+access_test_converter_monetary,access_test_converter_monetary,model_test_converter_monetary,,1,1,1,1
diff --git a/openerp/tests/addons/test_converter/models.py b/openerp/tests/addons/test_converter/models.py
index 9be3c9d675e..9074ba8178f 100644
--- a/openerp/tests/addons/test_converter/models.py
+++ b/openerp/tests/addons/test_converter/models.py
@@ -36,3 +36,11 @@ class test_model_sub(orm.Model):
_columns = {
'name': fields.char()
}
+
+
+class test_model_monetary(orm.Model):
+ _name = 'test_converter.monetary'
+
+ _columns = {
+ 'value': fields.float(digits=(16, 55)),
+ }
diff --git a/openerp/tests/addons/test_converter/tests/test_html.py b/openerp/tests/addons/test_converter/tests/test_html.py
index b4ad5c35679..d433c814958 100644
--- a/openerp/tests/addons/test_converter/tests/test_html.py
+++ b/openerp/tests/addons/test_converter/tests/test_html.py
@@ -1,77 +1,169 @@
# -*- encoding: utf-8 -*-
-import base64
+import json
import os
+import xml.dom.minidom
from openerp.tests import common
+from openerp.addons.base.ir import ir_qweb
directory = os.path.dirname(__file__)
-class TestHTMLExport(common.TransactionCase):
+impl = xml.dom.minidom.getDOMImplementation()
+doc = impl.createDocument(None, None, None)
+
+class TestExport(common.TransactionCase):
+ _model = None
+
def setUp(self):
- super(TestHTMLExport, self).setUp()
- self.Converter = self.registry('ir.fields.converter')
- self.Model = self.registry('test_converter.test_model')
+ super(TestExport, self).setUp()
+ self.Model = self.registry(self._model)
+ self.columns = self.Model._all_columns
def get_column(self, name):
return self.Model._all_columns[name].column
def get_converter(self, name):
- return self.Converter.from_field(
- self.cr, self.uid, self.Model, self.get_column(name),
- totype="html")
+ column = self.get_column(name)
+ try:
+ model = self.registry('ir.qweb.field.' + column._type)
+ except KeyError:
+ model = self.registry('ir.qweb.field')
+ return lambda value: model.value_to_html(
+ self.cr, self.uid, value, column)
+
+class TestBasicExport(TestExport):
+ _model = 'test_converter.test_model'
+
+class TestCharExport(TestBasicExport):
def test_char(self):
converter = self.get_converter('char')
- value, warnings = converter('foo')
+ value = converter('foo')
self.assertEqual(value, 'foo')
- self.assertEqual(warnings, [])
- value, warnings = converter("foo")
+ value = converter("foo")
self.assertEqual(value, "foo<bar>")
- self.assertEqual(warnings, [])
+class TestIntegerExport(TestBasicExport):
def test_integer(self):
converter = self.get_converter('integer')
- value, warnings = converter(42)
+ value = converter(42)
self.assertEqual(value, "42")
- self.assertEqual(warnings, [])
+class TestFloatExport(TestBasicExport):
def test_float(self):
converter = self.get_converter('float')
- value, warnings = converter(42.0)
+ value = converter(42.0)
self.assertEqual(value, "42.0")
- self.assertEqual(warnings, [])
- value, warnings = converter(42.0100)
+ value = converter(42.0100)
self.assertEqual(value, "42.01")
- self.assertEqual(warnings, [])
- value, warnings = converter(42.01234)
+ value = converter(42.01234)
self.assertEqual(value, "42.01234")
- self.assertEqual(warnings, [])
def test_numeric(self):
converter = self.get_converter('numeric')
- value, warnings = converter(42.0)
+ value = converter(42.0)
self.assertEqual(value, '42.00')
- self.assertEqual(warnings, [])
- value, warnings = converter(42.01234)
+ value = converter(42.01234)
self.assertEqual(value, '42.01')
- self.assertEqual(warnings, [])
+class TestCurrencyExport(TestExport):
+ _model = 'test_converter.monetary'
+
+ def setUp(self):
+ super(TestCurrencyExport, self).setUp()
+ self.Currency = self.registry('res.currency')
+ self.base = self.create(self.Currency, name="Source", symbol=u'source')
+
+ def create(self, model, context=None, **values):
+ return model.browse(
+ self.cr, self.uid,
+ model.create(self.cr, self.uid, values, context=context),
+ context=context)
+
+ def convert(self, obj, dest):
+ converter = self.registry('ir.qweb.field.monetary')
+ options = {
+ 'widget': 'monetary',
+ 'display_currency': 'c2'
+ }
+ converted = converter.to_html(
+ self.cr, self.uid, 'value', obj, options,
+ doc.createElement('span'),
+ {'field': 'obj.value', 'field-options': json.dumps(options)},
+ '', ir_qweb.QWebContext({'obj': obj, 'c2': dest, }))
+ return converted
+
+ def test_currency_post(self):
+ currency = self.create(self.Currency, name="Test", symbol=u"test")
+ obj = self.create(self.Model, value=0.12)
+
+ converted = self.convert(obj, dest=currency)
+
+ self.assertEqual(
+ converted,
+ ''
+ '0.12'
+ ' {symbol}'.format(
+ obj=obj,
+ symbol=currency.symbol.encode('utf-8')
+ ),)
+
+ def test_currency_pre(self):
+ currency = self.create(
+ self.Currency, name="Test", symbol=u"test", position='before')
+ obj = self.create(self.Model, value=0.12)
+
+ converted = self.convert(obj, dest=currency)
+
+ self.assertEqual(
+ converted,
+ ''
+ '{symbol} '
+ '0.12'
+ ''.format(
+ obj=obj,
+ symbol=currency.symbol.encode('utf-8')
+ ),)
+
+ def test_currency_precision(self):
+ """ Precision should be the currency's, not the float field's
+ """
+ currency = self.create(self.Currency, name="Test", symbol=u"test",)
+ obj = self.create(self.Model, value=0.1234567)
+
+ converted = self.convert(obj, dest=currency)
+
+ self.assertEqual(
+ converted,
+ ''
+ '0.12'
+ ' {symbol}'.format(
+ obj=obj,
+ symbol=currency.symbol.encode('utf-8')
+ ),)
+
+class TestTextExport(TestBasicExport):
def test_text(self):
converter = self.get_converter('text')
- value, warnings = converter("This is my text-kai")
+ value = converter("This is my text-kai")
self.assertEqual(value, "This is my text-kai")
- self.assertEqual(warnings, [])
- value, warnings = converter("""
+ value = converter("""
. The current line (address) in the buffer.
$ The last line in the buffer.
n The nth, line in the buffer where n is a number in the range [0,$].
@@ -89,9 +181,8 @@ class TestHTMLExport(common.TransactionCase):
-n The nth previous line, where n is a non-negative number.
+ The next line. This is equivalent to +1 and may be repeated with cumulative effect.
""")
- self.assertEqual(warnings, [])
- value, warnings = converter("""
+ value = converter("""
fgdkls;hjas;lj fdslkj d;lasjfa lkdja lfks
fldkjsfhs fldskjh
""")
@@ -99,65 +190,78 @@ class TestHTMLExport(common.TransactionCase):
fgdkls;hjas;lj <b>fdslkj</b> d;lasjfa lkdja <a href=http://spam.com>lfks</a>
fldkjsfhs <i style="color: red"><a href="http://spamspam.com">fldskjh</a></i>
""")
- self.assertEqual(warnings, [])
+class TestMany2OneExport(TestBasicExport):
def test_many2one(self):
converter = self.get_converter('many2one')
Sub = self.registry('test_converter.test_model.sub')
id0 = Sub.create(self.cr, self.uid, {'name': "Foo"})
- value, warnings = converter(Sub.browse(self.cr, self.uid, id0))
+ value = converter(Sub.browse(self.cr, self.uid, id0))
self.assertEqual(value, "Foo")
- self.assertEqual(warnings, [])
id1 = Sub.create(self.cr, self.uid, {'name': "Foo"})
- value, warnings = converter(Sub.browse(self.cr, self.uid, id1))
+ value = converter(Sub.browse(self.cr, self.uid, id1))
self.assertEqual(value, "Fo<b>o</b>")
- self.assertEqual(warnings, [])
- def test_binary(self):
- converter = self.get_converter('binary')
+class TestBinaryExport(TestBasicExport):
+ def test_image(self):
+ column = self.get_column('binary')
+ converter = self.registry('ir.qweb.field.image')
+
with open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f:
content = f.read()
- value, warnings = converter(content.encode('base64'))
- self.assertEqual(
- value, '
' % (
- content.encode('base64')
- ))
- self.assertEqual(warnings, [])
+ encoded_content = content.encode('base64')
+ value = converter.value_to_html(
+ self.cr, self.uid, encoded_content, column)
+ self.assertEqual(
+ value, '
' % (
+ encoded_content
+ ))
with open(os.path.join(directory, 'test_vectors', 'pdf'), 'rb') as f:
content = f.read()
- with self.assertRaises(ValueError):
- converter(content.encode('base64'))
+ with self.assertRaises(ValueError):
+ converter.value_to_html(
+ self.cr, self.uid, 'binary', content.encode('base64'), column)
with open(os.path.join(directory, 'test_vectors', 'pptx'), 'rb') as f:
content = f.read()
- with self.assertRaises(ValueError):
- converter(content.encode('base64'))
+ with self.assertRaises(ValueError):
+ converter.value_to_html(
+ self.cr, self.uid, 'binary', content.encode('base64'), column)
+class TestSelectionExport(TestBasicExport):
def test_selection(self):
- converter = self.get_converter('selection')
+ [record] = self.Model.browse(self.cr, self.uid, [self.Model.create(self.cr, self.uid, {
+ 'selection': 2,
+ 'selection_str': 'C',
+ })])
- value, warnings = converter(2)
+ column_name = 'selection'
+ column = self.get_column(column_name)
+ converter = self.registry('ir.qweb.field.selection')
+
+ value = converter.record_to_html(
+ self.cr, self.uid, column_name, record, column)
self.assertEqual(value, "réponse B")
- self.assertEqual(warnings, [])
- converter = self.get_converter('selection_str')
-
- value, warnings = converter('C')
+ column_name = 'selection_str'
+ column = self.get_column(column_name)
+ value = converter.record_to_html(
+ self.cr, self.uid, column_name, record, column)
self.assertEqual(value, "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?")
- self.assertEqual(warnings, [])
+class TestHTMLExport(TestBasicExport):
def test_html(self):
converter = self.get_converter('html')
input = 'span'
- value, warnings = converter(input)
+ value = converter(input)
self.assertEqual(value, input)
- self.assertEqual(warnings, [])
- # o2m, m2m?
- # reference?
+
+# o2m, m2m?
+# reference?
diff --git a/openerp/tests/test_qweb.py b/openerp/tests/test_qweb.py
index 96ee0cf74af..662946c175e 100644
--- a/openerp/tests/test_qweb.py
+++ b/openerp/tests/test_qweb.py
@@ -1,26 +1,18 @@
# -*- coding: utf-8 -*-
import cgi
-from collections import namedtuple
from xml.dom import minidom as dom
import common
-
-from ..tools import qweb
+from openerp.addons.base.ir import ir_qweb
+import openerp.addons.base.ir.ir_qweb
impl = dom.getDOMImplementation()
document = impl.createDocument(None, None, None)
-Request = namedtuple('Request', 'cr uid registry')
-class RegistryProxy(object):
- def __init__(self, func):
- self.func = func
- def __getitem__(self, name):
- return self.func(name)
-
class TestQWebTField(common.TransactionCase):
def setUp(self):
super(TestQWebTField, self).setUp()
- self.engine = qweb.QWebXml()
+ self.engine = self.registry('ir.qweb')
def test_trivial(self):
field = document.createElement('span')
@@ -32,16 +24,14 @@ class TestQWebTField(common.TransactionCase):
})
root_company = Companies.browse(self.cr, self.uid, company_id)
- result = self.engine.render_node(field, {
+ result = self.engine.render_node(field, ir_qweb.QWebContext({
'company': root_company,
- 'request': Request(self.cr, self.uid, RegistryProxy(self.registry))
- })
+ }))
self.assertEqual(
result,
'%s' % (
company_id,
"My Test Company",))
@@ -57,15 +47,13 @@ class TestQWebTField(common.TransactionCase):
})
root_company = Companies.browse(self.cr, self.uid, company_id)
- result = self.engine.render_node(field, {
+ result = self.engine.render_node(field, ir_qweb.QWebContext({
'company': root_company,
- 'request': Request(self.cr, self.uid, RegistryProxy(self.registry))
- })
+ }))
self.assertEqual(
result,
'%s' % (
company_id,
cgi.escape(s.encode('utf-8')),))
@@ -77,7 +65,7 @@ class TestQWebTField(common.TransactionCase):
with self.assertRaisesRegexp(
AssertionError,
r'^RTE widgets do not work correctly'):
- self.engine.render_node(field, {'company': None})
+ self.engine.render_node(field, ir_qweb.QWebContext({'company': None}))
def test_reject_t_tag(self):
field = document.createElement('t')
@@ -86,4 +74,4 @@ class TestQWebTField(common.TransactionCase):
with self.assertRaisesRegexp(
AssertionError,
r'^t-field can not be used on a t element'):
- self.engine.render_node(field, {'company': None})
+ self.engine.render_node(field, ir_qweb.QWebContext({'company': None}))
diff --git a/openerp/tools/qweb.py b/openerp/tools/qweb.py
deleted file mode 100644
index 2683ea0b1ca..00000000000
--- a/openerp/tools/qweb.py
+++ /dev/null
@@ -1,411 +0,0 @@
-import datetime
-import dateutil
-import logging
-import re
-import traceback
-import urllib
-import xml # FIXME use lxml and etree
-
-import werkzeug.utils
-
-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,
- 'quote': urllib.quote,
- 'urlencode': urllib.urlencode,
- 'datetime': datetime,
- # dateutil.relativedelta is an old-style class and cannot be directly
- # instanciated wihtin a jinja2 expression, so a lambda "proxy" is
- # is needed, apparently.
- 'relativedelta': lambda *a, **kw : dateutil.relativedelta.relativedelta(*a, **kw),
-}
-
-## We use a jinja2 sandboxed environment to render qWeb templates.
-#from openerp.tools.safe_eval import safe_eval as eval
-#from jinja2.sandbox import SandboxedEnvironment
-#from jinja2.exceptions import SecurityError, UndefinedError
-#UNSAFE = ["browse", "search", "read", "unlink", "read_group"]
-#SAFE = ["_name"]
-
-class QWebContext(dict):
- def __init__(self, data, undefined_handler=None):
- self.undefined_handler = undefined_handler
- dic = BUILTINS.copy()
- dic.update(data)
- super(QWebContext, self).__init__(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))
-
- def safe_eval(self, expr):
- # This is too slow, we should cached compiled expressions attribute of
- # qweb to will be changed into a model object ir.qweb.
- #
- # The cache should be on qweb, and qweb context contructor take qweb as
- # argument to store the cache.
- #
- #class QWebSandboxedEnvironment(SandboxedEnvironment):
- # def is_safe_attribute(self, obj, attr, value):
- # if str(attr) in SAFE:
- # res = True
- # else:
- # res = super(QWebSandboxedEnvironment, self).is_safe_attribute(obj, attr, value)
- # if str(attr) in UNSAFE or not res:
- # raise SecurityError("access to attribute '%s' of '%s' object is unsafe." % (attr,obj))
- # return res
- #env = qWebSandboxedEnvironment(variable_start_string="${", variable_end_string="}")
- #env.globals.update(context)
- #env.compile_expression(expr)()
- return eval(expr, None, self)
-
-class QWebXml(object):
- """QWeb Xml templating engine
-
- The templating engine use a very simple syntax, "magic" xml attributes, to
- produce any kind of texutal output (even non-xml).
-
- QWebXml:
- the template engine core implements the basic magic attributes:
-
- t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
-
-
- - loader: function that return a template
-
-
- """
- 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 = {}
- prefix = 'render_att_'
- for i in [j for j in dir(self) if j.startswith(prefix)]:
- name = i[len(prefix):].replace('_', '-')
- self._render_att[name] = getattr(self.__class__, i)
-
- def register_tag(self, tag, func):
- self._render_tag[tag] = func
-
- def add_template(self, x):
- if hasattr(x, 'documentElement'):
- dom = x
- elif x.startswith("%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)
-
- # Attributes
- def render_att_att(self, e, an, av, v):
- 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(av, v)
- if isinstance(val, unicode):
- val = val.encode("utf8")
- else:
- att, val = self.eval_object(av, v)
- 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):
- inner = self.eval_str(t_att["raw"], v)
- return self.render_element(e, t_att, g_att, v, inner)
-
- def render_tag_rawf(self, e, t_att, g_att, v):
- inner = self.eval_format(t_att["rawf"], v)
- return self.render_element(e, t_att, g_att, v, inner)
-
- def render_tag_esc(self, e, t_att, g_att, 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 = 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):
- expr = t_att["foreach"]
- 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)
- size = -1
- if isinstance(enum, (list, tuple)):
- size = len(enum)
- elif hasattr(enum, 'count'):
- size = enum.count()
- d["%s_size" % var] = size
- d["%s_all" % var] = enum
- index = 0
- ru = []
- for i in enum:
- d["%s_value" % var] = i
- d["%s_index" % var] = index
- d["%s_first" % var] = index == 0
- d["%s_even" % var] = index % 2
- d["%s_odd" % var] = (index + 1) % 2
- d["%s_last" % var] = index + 1 == size
- if index % 2:
- d["%s_parity" % var] = 'odd'
- else:
- d["%s_parity" % var] = 'even'
- 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:
- 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):
- return self.render_element(e, t_att, g_att, v)
- else:
- return ""
-
- def render_tag_call(self, e, t_att, g_att, v):
- if "import" in t_att:
- d = v
- else:
- d = QWebContext(v.copy(), self.undefined_handler)
- d[0] = self.render_element(e, t_att, g_att, 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:
- v[t_att["set"]] = self.eval_object(t_att["value"], v)
- elif "valuef" in t_att:
- v[t_att["set"]] = self.eval_format(t_att["valuef"], v)
- else:
- v[t_att["set"]] = self.render_element(e, t_att, g_att, v)
- return ""
-
- def render_tag_field(self, e, t_att, g_att, v):
- """ eg: +1 555 555 8069"""
- 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)
-
- 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:
- 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)
-
- 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 "")
-
-# vim:et: