[IMP] qweb: add t-options on t-esc and t-raw who use 'ir.qweb.field' models
Remove 'field' from 'value_to_html' method attributes
't-esc' escape the result of the widget.
(Added 'remove_span' as option for monetary widget)
E.g.: <t t-esc="32" t-options='t-options='{"widget": "monetary", "display_currency": website.currency_id, "remove_span": True}'>
result: $ 365.00
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}<span class="oe_currency_value">{0}</span>{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}<span class="oe_currency_value">{0}</span>{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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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': "Fo<b>o</b>"})
|
||||
})
|
||||
|
||||
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': "Fo<b>o</b>"}))
|
||||
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, '<img src="data:image/jpeg;base64,%s">' % (
|
||||
@@ -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 <D>"))
|
||||
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user