diff --git a/addons/decimal_precision/tests/test_qweb_float.py b/addons/decimal_precision/tests/test_qweb_float.py index c481dba2fd9..e598f00d8ec 100644 --- a/addons/decimal_precision/tests/test_qweb_float.py +++ b/addons/decimal_precision/tests/test_qweb_float.py @@ -7,11 +7,14 @@ class TestFloatExport(common.TransactionCase): self.Model = self.registry('decimal.precision.test') def get_converter(self, name): - converter = self.registry('ir.qweb.field.float') - field = self.Model._fields[name] + float_obj = self.registry('ir.qweb.field.float') + _, precision = self.Model._fields[name].digits or (None, None) - return lambda value, options=None: converter.value_to_html( - self.cr, self.uid, value, field, options=options, context=None) + def converter(value, options=None): + record = self.Model.new(self.cr, self.uid, {name: value}, context=None) + return float_obj.record_to_html( + self.cr, self.uid, record, name, options or {}, context=None) + return converter def test_basic_float(self): converter = self.get_converter('float') @@ -30,12 +33,12 @@ class TestFloatExport(common.TransactionCase): converter(42.12345), "42.12") - converter = self.get_converter('float_4') + converter = self.get_converter('float') # don't use float_4 because the field value 42.12345 is orm converted to 42.1235 self.assertEqual( - converter(42.0), + converter(42.0, {'precision': 4}), '42.0000') self.assertEqual( - converter(42.12345), + converter(42.12345, {'precision': 4}), '42.1234') def test_precision_domain(self): @@ -57,7 +60,7 @@ class TestFloatExport(common.TransactionCase): converter(42.0, {'decimal_precision': 'B'}), '42.000000') - converter = self.get_converter('float_4') + converter = self.get_converter('float') # don't use float_4 because the field value 42.12345 is orm converted to 42.1235 self.assertEqual( converter(42.12345, {'decimal_precision': 'A'}), '42.12') diff --git a/addons/report/models/ir_qweb.py b/addons/report/models/ir_qweb.py index 68e9673d902..3e8dc558c08 100644 --- a/addons/report/models/ir_qweb.py +++ b/addons/report/models/ir_qweb.py @@ -12,7 +12,7 @@ class BarcodeConverter(osv.AbstractModel): _name = 'ir.qweb.field.barcode' _inherit = 'ir.qweb.field' - def value_to_html(self, cr, uid, value, field, options=None, context=None): + def value_to_html(self, cr, uid, value, options, context=None): options = options or {} barcode_type = options.get('type', 'Code128') barcode = self.pool['report'].barcode( diff --git a/openerp/addons/base/ir/ir_qweb/fields.py b/openerp/addons/base/ir/ir_qweb/fields.py index b82945ca155..f520bd25c9a 100644 --- a/openerp/addons/base/ir/ir_qweb/fields.py +++ b/openerp/addons/base/ir/ir_qweb/fields.py @@ -82,7 +82,7 @@ class FieldConverter(models.AbstractModel): return data @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): """ value_to_html(value, field, options=None) Converts a single value to its HTML version/output @@ -97,15 +97,8 @@ class FieldConverter(models.AbstractModel): Converts the specified field of the browse_record ``record`` to HTML :rtype: unicode """ - field = record._fields[field_name] - - try: - value = record[field_name] - return False if value is False else record.env[self._name].value_to_html(value, field, options=options) - except Exception: - _logger.warning("Could not get field %s for model %s", - field_name, record._name, exc_info=True) - return None + value = record[field_name] + return False if value is False else record.env[self._name].value_to_html(value, options=options) @api.model def user_lang(self): @@ -126,7 +119,7 @@ class IntegerConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): return unicodifier(self.user_lang().format('%d', value, grouping=True)) @@ -135,15 +128,15 @@ class FloatConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def precision(self, field, options=None): - _, precision = field.digits or (None, None) - return precision + def value_to_html(self, value, options): + if 'decimal_precision' in options: + precision = self.env['decimal.precision'].search([('name', '=', options['decimal_precision'])]).digits + else: + precision = options['precision'] - @api.model - def value_to_html(self, value, field, options=None): - precision = self.precision(field, options=options) fmt = '%f' if precision is None else '%.{precision}f' lang = self.user_lang() + formatted = lang.format(fmt.format(precision=precision), value, grouping=True) # %f does not strip trailing zeroes. %g does but its precision causes @@ -152,15 +145,23 @@ class FloatConverter(models.AbstractModel): # strip trailing 0. if precision is None: formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted) + return unicodifier(formatted) + @api.model + def record_to_html(self, record, field_name, options, values=None): + if 'precision' not in options and 'decimal_precision' not in options: + _, precision = record._fields[field_name].digits or (None, None) + options = dict(options, precision=precision) + return super(FloatConverter, self).record_to_html(record, field_name, options, values=values) + class DateConverter(models.AbstractModel): _name = 'ir.qweb.field.date' _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): if not value or len(value) < 10: return '' lang = self.user_lang() @@ -183,7 +184,7 @@ class DateTimeConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): if not value: return '' lang = self.user_lang() @@ -211,7 +212,7 @@ class TextConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): """ Escapes the value and converts newlines to br. This is bullshit. """ @@ -223,11 +224,16 @@ class SelectionConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): if not value: return '' - selection = dict(field.get_description(self.env)['selection']) - return html_escape(unicodifier(selection[value]) or u'', options) + return html_escape(unicodifier(options['selection'][value]) or u'', options) + + @api.model + def record_to_html(self, record, field_name, options, values=None): + if 'selection' not in options: + options = dict(options, selection=dict(record._fields[field_name].get_description(self.env)['selection'])) + return super(SelectionConverter, self).record_to_html(record, field_name, options, values=values) class ManyToOneConverter(models.AbstractModel): @@ -235,8 +241,7 @@ class ManyToOneConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def record_to_html(self, record, field_name, options, values=None): - value = record[field_name] + def value_to_html(self, value, options): if not value: return False value = value.sudo().display_name @@ -250,7 +255,7 @@ class HTMLConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): return unicodifier(value) or u'' @@ -267,7 +272,7 @@ class ImageConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): try: image = Image.open(StringIO(value.decode('base64'))) image.verify() @@ -297,27 +302,9 @@ class MonetaryConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def record_to_html(self, record, field_name, options, values=None): - # for backward compatibility to remove after v10 - if isinstance(options.get('display_currency'), str): - _logger.warning("Use new syntax for '%s' monetary widget 'display_currency' option (instead of deprecated JSON syntax) to improve speed." % options['display_currency']) - options['display_currency'] = safe_eval.safe_eval(options['display_currency'], mode='eval', globals_dict=values) - if isinstance(options.get('from_currency'), str): - _logger.warning("Use new syntax for '%s' monetary widget 'from_currency' option (instead of deprecated JSON syntax) to improve speed." % options['from_currency']) - options['from_currency'] = safe_eval.safe_eval(options['from_currency'], mode='eval', globals_dict=values) - # end backward + def value_to_html(self, value, options): + display_currency = options['display_currency'] - field = record._fields[field_name] - from_amount = record[field_name] - - #currency should be specified by monetary field - if not options.get('display_currency') and field.type == 'monetary' and field.currency_field: - options['display_currency'] = record[field.currency_field] - - pre, value, post = self._format_func_monetary(from_amount, options['display_currency'], options.get('from_currency')) - return u'{pre}{0}{post}'.format(value, pre=pre, post=post) - - def _format_func_monetary(self, from_amount, display_currency, from_currency=None): # lang.format mandates a sprintf-style format. These formats are non- # minimal (they have a default fixed precision instead), and # lang.format will not set one by default. currency.round will not @@ -325,11 +312,11 @@ class MonetaryConverter(models.AbstractModel): # (integer > 0) from the currency's rounding (a float generally < 1.0). fmt = "%.{0}f".format(display_currency.decimal_places) - if from_currency: - from_amount = from_currency.compute(from_amount, display_currency) + if options.get('from_currency'): + value = options['from_currency'].compute(value, display_currency) lang = self.user_lang() - formatted_amount = lang.format(fmt, display_currency.round(from_amount), + formatted_amount = lang.format(fmt, display_currency.round(value), grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}') pre = post = u'' @@ -338,8 +325,17 @@ class MonetaryConverter(models.AbstractModel): else: post = u'\N{NO-BREAK SPACE}{symbol}'.format(symbol=display_currency.symbol) - return pre, formatted_amount, post + return u'{pre}{0}{post}'.format(formatted_amount, pre=pre, post=post) + @api.model + def record_to_html(self, record, field_name, options, values=None): + options = dict(options) + #currency should be specified by monetary field + field = record._fields[field_name] + if not options.get('display_currency') and field.type == 'monetary' and field.currency_field: + options['display_currency'] = record[field.currency_field] + + return self.value_to_html(record[field_name], options) TIMEDELTA_UNITS = ( ('year', 3600 * 24 * 365), @@ -368,10 +364,12 @@ class DurationConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): units = dict(TIMEDELTA_UNITS) + if value < 0: raise ValueError(_("Durations can't be negative")) + if not options or options.get('unit') not in units: raise ValueError(_("A unit must be provided to duration widgets")) @@ -396,48 +394,52 @@ class RelativeDatetimeConverter(models.AbstractModel): _inherit = 'ir.qweb.field' @api.model - def value_to_html(self, value, field, options=None): + def value_to_html(self, value, options): locale = babel.Locale.parse(self.user_lang().code) if isinstance(value, basestring): value = fields.Datetime.from_string(value) # value should be a naive datetime in UTC. So is fields.Datetime.now() - reference = fields.Datetime.from_string(field.now()) + reference = fields.Datetime.from_string(options['now']) return unicodifier(babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale)) + @api.model + def record_to_html(self, record, field_name, options, values=None): + if 'now' not in options: + options = dict(options, now=record._fields[field_name].now()) + return super(RelativeDatetimeConverter, self).record_to_html(record, field_name, options, values=values) + class Contact(models.AbstractModel): _name = 'ir.qweb.field.contact' _inherit = 'ir.qweb.field.many2one' @api.model - def record_to_html(self, record, field_name, options, values=None): - opf = options and options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"] + def value_to_html(self, value, options): + if not value.exists(): + return False - value_rec = record[field_name] - if not value_rec: - return None - value_rec = value_rec.sudo().with_context(show_address=True) - value = value_rec.name_get()[0][1] + opf = options and options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"] + value = value.sudo().with_context(show_address=True) + name_get = value.name_get()[0][1] val = { - 'name': value.split("\n")[0], - 'address': escape("\n".join(value.split("\n")[1:])).strip(), - 'phone': value_rec.phone, - 'mobile': value_rec.mobile, - 'fax': value_rec.fax, - 'city': value_rec.city, - 'country_id': value_rec.country_id.display_name, - 'website': value_rec.website, - 'email': value_rec.email, + 'name': name_get.split("\n")[0], + 'address': escape("\n".join(name_get.split("\n")[1:])).strip(), + 'phone': value.phone, + 'mobile': value.mobile, + 'fax': value.fax, + 'city': value.city, + 'country_id': value.country_id.display_name, + 'website': value.website, + 'email': value.email, 'fields': opf, - 'object': value_rec, + 'object': value, 'options': options } - - return unicodifier(self.env.ref('base.contact').render(val, engine='ir.qweb')) + return self.env['ir.qweb'].render('base.contact', val) class QwebView(models.AbstractModel): diff --git a/openerp/addons/base/ir/ir_qweb/ir_qweb.py b/openerp/addons/base/ir/ir_qweb/ir_qweb.py index f57b6fbd437..a077a54b614 100644 --- a/openerp/addons/base/ir/ir_qweb/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb/ir_qweb.py @@ -2,11 +2,11 @@ import ast from urlparse import urlparse from lxml import html -import math from .qweb import QWeb, Contextifier from .assetsbundle import AssetsBundle from lxml import etree +from collections import OrderedDict from openerp import api, models, tools from openerp.tools import safe_eval @@ -144,6 +144,34 @@ class IrQWeb(models.AbstractModel, QWeb): )) ] + # for backward compatibility to remove after v10 + def _compile_widget_options(self, el, directive_type): + field_options = super(IrQWeb, self)._compile_widget_options(el, directive_type) + + if ('t-%s-options' % directive_type) in el.attrib: + if tools.config['dev_mode']: + _logger.warning("Use new syntax t-options instead of t-%s-options" % directive_type) + if not field_options: + field_options = el.attrib.pop('t-%s-options' % directive_type) + + if field_options and 'monetary' in field_options: + try: + options = "{'widget': 'monetary'" + for k, v in json.loads(field_options).iteritems(): + print k, v + if k in ('display_currency', 'from_currency'): + options = "%s, '%s': %s" % (options, k, v) + else: + options = "%s, '%s': '%s'" % (options, k, v) + options = "%s}" % options + field_options = options + _logger.warning("Use new syntax for '%s' monetary widget t-options (python dict instead of deprecated JSON syntax)." % etree.tostring(el)) + except ValueError: + pass + + return field_options + # end backward + # method called by computing code @tools.conditional( @@ -231,16 +259,35 @@ class IrQWeb(models.AbstractModel, QWeb): converter = self.env[model] if model in self.env else self.env['ir.qweb.field'] # get content - content = converter.record_to_html(record, field_name, field_options, values) + content = converter.record_to_html(record, field_name, field_options, values=values) attributes = converter.attributes(record, field_name, field_options, values) return (attributes, content, inherit_branding or translate) + def _get_widget(self, value, expression, tagName, field_options, options, values): + field_options['type'] = field_options['widget'] + field_options['tagName'] = tagName + field_options['expression'] = expression + + # field converter + model = 'ir.qweb.field.' + field_options['type'] + converter = self.env[model] if model in self.env else self.env['ir.qweb.field'] + + # get content + content = converter.value_to_html(value, field_options) + attributes = OrderedDict() + attributes['data-oe-type'] = field_options['type'] + attributes['data-oe-expression'] = field_options['expression'] + + return (attributes, content, None) + # formating methods def _format_func_monetary(self, value, display_currency, from_currency=None): - pre, value, post = self.env['ir.qweb.field.monetary']._format_func_monetary(value, display_currency, from_currency) - return u'{pre}{0}{post}'.format(value, pre=pre, post=post) + options = {} + options['display_currency'] = display_currency + options['from_currency'] = from_currency + return self.env['ir.qweb.field.monetary'].value_to_html(value, options) def _format_func_htmlcontact(self, value, options, values=None): return self.pool['ir.qweb.field.contact'].record_to_html( diff --git a/openerp/addons/base/ir/ir_qweb/qweb.py b/openerp/addons/base/ir/ir_qweb/qweb.py index dda4e1546c2..2d4f7c90a3a 100644 --- a/openerp/addons/base/ir/ir_qweb/qweb.py +++ b/openerp/addons/base/ir/ir_qweb/qweb.py @@ -6,7 +6,8 @@ import re import traceback from itertools import count from textwrap import dedent -from werkzeug.utils import escape +import werkzeug +from werkzeug.utils import escape as _escape from itertools import izip, tee import __builtin__ builtin_defaults = {name: getattr(__builtin__, name) for name in dir(__builtin__)} @@ -141,16 +142,8 @@ class QWebException(Exception): def __repr__(self): return str(self) - -import werkzeug -from werkzeug.utils import escape # Avoid DeprecationWarning while still remaining compatible with werkzeug pre-0.9 -if getattr(werkzeug, '__version__', '0.0') < '0.9.0': - def qweb_escape(text): - return escape(unicodifier(text), quote=True) -else: - def qweb_escape(text): - return escape(unicodifier(text)) +escape = (lambda text: _escape(text, quote=True)) if getattr(werkzeug, '__version__', '0.0') < '0.9.0' else _escape def unicodifier(val): if val is None or val is False: @@ -493,14 +486,14 @@ class QWeb(object): * collections * itertools Define: - * qweb_escape + * escape * unicodifier (empty string for a None or False, otherwise unicode string) """ return ast.parse(dedent(""" from collections import OrderedDict import itertools from itertools import repeat, imap - from openerp.addons.base.ir.ir_qweb.qweb import qweb_escape, unicodifier, foreach_iterator + from openerp.addons.base.ir.ir_qweb.qweb import escape, unicodifier, foreach_iterator """)) def _create_def(self, options, body, prefix='fn', lineno=None): @@ -684,11 +677,11 @@ class QWeb(object): return [ 'debug', 'groups', 'foreach', 'if', 'else', - 'field', + 'field', 'esc', 'raw', 'tag', 'call', 'set', - 'esc', 'raw', 'content', + 'content', ] def _is_static_node(self, el): @@ -704,7 +697,7 @@ class QWeb(object): content = self._compile_directive_content(el, options) if el.tag == 't': return content - tag = u'<%s%s' % (el.tag, u''.join([u' %s="%s"' % (name, qweb_escape(value)) for name, value in el.attrib.iteritems()])) + tag = u'<%s%s' % (el.tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in el.attrib.iteritems()])) if el.tag in self._void_elements: return [self._append(ast.Str(tag + '/>'))] + content else: @@ -795,7 +788,7 @@ class QWeb(object): # append(u' ') # append(name) # append(u'="') - # append(qweb_escape(value)) + # append(escape(unicodifier((value))) # append(u'"') body.append(ast.For( target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()), @@ -829,8 +822,12 @@ class QWeb(object): self._append(ast.Name(id='name', ctx=ast.Load())), self._append(ast.Str(u'="')), self._append(ast.Call( - func=ast.Name(id='qweb_escape', ctx=ast.Load()), - args=[ast.Name(id='value', ctx=ast.Load())], keywords=[], + func=ast.Name(id='escape', ctx=ast.Load()), + args=[ast.Call( + func=ast.Name(id='unicodifier', ctx=ast.Load()), + args=[ast.Name(id='value', ctx=ast.Load())], keywords=[], + starargs=None, kwargs=None + )], keywords=[], starargs=None, kwargs=None )), self._append(ast.Str(u'"')), @@ -844,6 +841,8 @@ class QWeb(object): def _compile_tag(self, el, content, options, attr_already_created=False): """ Compile the tag of the given element into a list of AST nodes. """ + if el.tag == 't': + return content body = [self._append(ast.Str(u'<%s' % el.tag))] body.extend(self._compile_all_attributes(el, options, attr_already_created)) if el.tag in self._void_elements: @@ -869,8 +868,6 @@ class QWeb(object): def _compile_directive_tag(self, el, options): el.attrib.pop('t-tag', None) content = self._compile_directives(el, options) - if el.tag == 't': - return content return self._compile_tag(el, content, options, False) def _compile_directive_set(self, el, options): @@ -925,44 +922,102 @@ class QWeb(object): )] def _compile_directive_esc(self, el, options): - return [ - # content = t-raw value - ast.Assign( - targets=[ast.Name(id='content', ctx=ast.Store())], - value=self._compile_expr0(el.attrib.pop('t-esc')), - ), - # if content is not None: display - self._if_content_is_not_Falsy( - # append(escape($content)) - body=[self._append(ast.Call( - func=ast.Name(id='qweb_escape', ctx=ast.Load()), - args=[ast.Name(id='content', ctx=ast.Load())], keywords=[], - starargs=None, kwargs=None - ))], - # append(body default value) - orelse=self._compile_directive_content(el, options), - ) - ] + field_options = self._compile_widget_options(el, 'esc') + content = self._compile_widget(el, el.attrib.pop('t-esc'), field_options, 'escape') + return content + self._compile_widget_value(el, options) def _compile_directive_raw(self, el, options): - return [ - # content = t-raw value - ast.Assign( - targets=[ast.Name(id='content', ctx=ast.Store())], - value=self._compile_expr0(el.attrib.pop('t-raw')), + field_options = self._compile_widget_options(el, 'raw') + content = self._compile_widget(el, el.attrib.pop('t-raw'), field_options) + return content + self._compile_widget_value(el, options) + + def _compile_widget(self, el, expression, field_options, escape=None): + # if isinstance(value, basestring): + # value = escape(unicodifier(value)) + escaped = ast.If( + test=ast.Call( + func=ast.Name(id='isinstance', ctx=ast.Load()), + args=[ + ast.Name(id='content', ctx=ast.Load()), + ast.Name(id='basestring', ctx=ast.Load()) + ], keywords=[], + starargs=None, kwargs=None ), - self._if_content_is_not_Falsy( - # append($content) - body=[self._append(ast.Call( - func=ast.Name(id='unicodifier', ctx=ast.Load()), - args=[ast.Name(id='content', ctx=ast.Load())], keywords=[], + body=[ast.Assign( + targets=[ast.Name(id='content', ctx=ast.Store())], + value=ast.Call( + func=ast.Name(id=escape, ctx=ast.Load()), + args=[ast.Call( + func=ast.Name(id='unicodifier', ctx=ast.Load()), + args=[ast.Name(id='content', ctx=ast.Load())], keywords=[], + starargs=None, kwargs=None + )], keywords=[], starargs=None, kwargs=None - ))], - # append(body default value) - orelse=self._compile_directive_content(el, options), - ) + ) if escape else ast.Name(id='content', ctx=ast.Load()) + )], + orelse=[] + ) + + if field_options: + return [ + # value = t-(esc|raw) + ast.Assign( + targets=[ast.Name(id='content', ctx=ast.Store())], + value=self._compile_expr0(expression) + ), + escaped, + # t_attrs, content, force_display = self._get_widget(value, expression, tagName, field options, template options, values) + ast.Assign( + targets=[ast.Tuple(elts=[ + ast.Name(id='t_attrs', ctx=ast.Store()), + ast.Name(id='content', ctx=ast.Store()), + ast.Name(id='force_display', ctx=ast.Store()) + ], ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id='self', ctx=ast.Load()), + attr='_get_widget', + ctx=ast.Load() + ), + args=[ + ast.Name(id='content', ctx=ast.Load()), + ast.Str(expression), + ast.Str(el.tag), + field_options and self._compile_expr(field_options) or ast.Dict(keys=[], values=[]), + ast.Name(id='options', ctx=ast.Load()), + ast.Name(id='values', ctx=ast.Load()), + ], + keywords=[], starargs=None, kwargs=None + ) + ) + ] + + return [ + # t_attrs, content, force_display = OrderedDict(), t-(esc|raw), None + ast.Assign( + targets=[ast.Tuple(elts=[ + ast.Name(id='t_attrs', ctx=ast.Store()), + ast.Name(id='content', ctx=ast.Store()), + ast.Name(id='force_display', ctx=ast.Store()), + ], ctx=ast.Store())], + value=ast.Tuple(elts=[ + ast.Call( + func=ast.Name(id='OrderedDict', ctx=ast.Load()), + args=[], + keywords=[], starargs=None, kwargs=None + ), + self._compile_expr0(expression), + ast.Name(id='None', ctx=ast.Load()), + ], ctx=ast.Load()) + ), + escaped ] + # for backward compatibility to remove after v10 + def _compile_widget_options(self, el, directive_type): + return el.attrib.pop('t-options', None) + # end backward + def _compile_directive_content(self, el, options): body = [] if el.text is not None: @@ -1060,10 +1115,10 @@ class QWeb(object): "t-field must have at least a dot like 'record.field_name'" expression = el.attrib.pop('t-field') - field_options = el.attrib.pop('t-field-options', None) + field_options = self._compile_widget_options(el, 'field') record, field_name = expression.rsplit('.', 1) - content = [ + return [ # t_attrs, content, force_display = self._get_field(record, field_name, expression, tagName, field options, template options, values) ast.Assign( targets=[ast.Tuple(elts=[ @@ -1089,8 +1144,9 @@ class QWeb(object): keywords=[], starargs=None, kwargs=None ) ) - ] + ] + self._compile_widget_value(el, options) + def _compile_widget_value(self, el, options): # if force_display: # display the tag without content orelse = [ast.If( @@ -1145,17 +1201,22 @@ class QWeb(object): ] # if content is not None: - # display the tag + # display the tag (unicodifier(content)) # else # if default_content: # display the tag with default content # elif force_display: # display the tag without content - content.append(self._if_content_is_not_Falsy( - body=self._compile_tag(el, [self._append(ast.Name(id='content', ctx=ast.Load()))], options, True), + return [self._if_content_is_not_Falsy( + body=self._compile_tag(el, [self._append( + ast.Call( + func=ast.Name(id='unicodifier', ctx=ast.Load()), + args=[ast.Name(id='content', ctx=ast.Load())], keywords=[], + starargs=None, kwargs=None + ) + )], options, True), orelse=orelse, - )) - return content + )] def _compile_directive_call(self, el, options): tmpl = el.attrib.pop('t-call') @@ -1299,7 +1360,16 @@ class QWeb(object): * string or None: content * boolean: force_display display the tag if the content and default_content are None """ - return (OrderedDict(), escape(unicodifier(getattr(record, field_name, None))), False) + return self._get_widget(getattr(record, field_name, None), expression, tagName, field_options, options, values) + + def _get_widget(self, value, expression, tagName, field_options, options, values): + """ + :returns: tuple: + * OrderedDict: attributes + * string or None: content + * boolean: force_display display the tag if the content and default_content are None + """ + return (OrderedDict(), value, False) # compile expression diff --git a/openerp/addons/test_converter/tests/test_html.py b/openerp/addons/test_converter/tests/test_html.py index 58128176eac..c42f9c36311 100644 --- a/openerp/addons/test_converter/tests/test_html.py +++ b/openerp/addons/test_converter/tests/test_html.py @@ -30,8 +30,11 @@ class TestExport(common.TransactionCase): break except KeyError: pass - return lambda value, options=None, context=None: model.value_to_html( - self.cr, self.uid, value, field, options=options, context=context) + def converter(value, options=None, context=None): + record = self.Model.new(self.cr, self.uid, {name: value}, context=context) + return model.record_to_html( + self.cr, self.uid, record, name, options or {}, context=context) + return converter class TestBasicExport(TestExport): _model = 'test_converter.test_model' @@ -188,36 +191,23 @@ class TestTextExport(TestBasicExport): class TestMany2OneExport(TestBasicExport): def test_many2one(self): Sub = self.registry('test_converter.test_model.sub') + converter = self.get_converter('many2one') - - id0 = self.Model.create(self.cr, self.uid, { - 'many2one': Sub.create(self.cr, self.uid, {'name': "Foo"}) - }) - id1 = self.Model.create(self.cr, self.uid, { - 'many2one': Sub.create(self.cr, self.uid, {'name': "Foo"}) - }) - - def converter(record): - converter = self.registry('ir.qweb.field.many2one') - field_name = 'many2one' - return converter.record_to_html(self.cr, self.uid, record, field_name, {}) - - value = converter(self.Model.browse(self.cr, self.uid, id0)) + value = converter(Sub.create(self.cr, self.uid, {'name': "Foo"})) self.assertEqual(value, "Foo") - value = converter(self.Model.browse(self.cr, self.uid, id1)) + value = converter(Sub.create(self.cr, self.uid, {'name': "Foo"})) self.assertEqual(value, "Fo<b>o</b>") class TestBinaryExport(TestBasicExport): def test_image(self): - field = self.get_field('binary') converter = self.registry('ir.qweb.field.image') with open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f: content = f.read() encoded_content = content.encode('base64') - value = converter.value_to_html(self.cr, self.uid, encoded_content, field) + value = converter.value_to_html(self.cr, self.uid, encoded_content, {}) self.assertEqual( value, '' % ( @@ -229,30 +219,23 @@ class TestBinaryExport(TestBasicExport): with self.assertRaises(ValueError): converter.value_to_html( - self.cr, self.uid, 'binary', content.encode('base64'), field) + self.cr, self.uid, 'binary', content.encode('base64'), {}) with open(os.path.join(directory, 'test_vectors', 'pptx'), 'rb') as f: content = f.read() with self.assertRaises(ValueError): converter.value_to_html( - self.cr, self.uid, 'binary', content.encode('base64'), field) + self.cr, self.uid, 'binary', content.encode('base64'), {}) class TestSelectionExport(TestBasicExport): def test_selection(self): - [record] = self.Model.browse(self.cr, self.uid, [self.Model.create(self.cr, self.uid, { - 'selection': 4, - 'selection_str': 'C', - })]) - - converter = self.registry('ir.qweb.field.selection') - - field_name = 'selection' - value = converter.record_to_html(self.cr, self.uid, record, field_name, {}) + converter = self.get_converter('selection') + value = converter(4) self.assertEqual(value, e(u"réponse ")) - field_name = 'selection_str' - value = converter.record_to_html(self.cr, self.uid, record, field_name, {}) + converter = self.get_converter('selection_str') + value = converter('C') self.assertEqual(value, u"Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?") class TestHTMLExport(TestBasicExport):