From ea56dece85620fc404dd3d445777b2c845b4fb18 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 28 Nov 2013 16:36:30 +0100 Subject: [PATCH] [ADD] duration widget bzr revid: xmo@openerp.com-20131128153630-pouwmygw6vgyuj1l --- openerp/addons/base/ir/ir_qweb.py | 61 +++++++++++++++++-- .../addons/test_converter/tests/test_html.py | 57 ++++++++++++++--- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 51898344401..bc8549ab63f 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- import cStringIO -import json import datetime -import dateutil.relativedelta +import json import logging +import math import re import urllib import xml # FIXME use lxml and etree +import babel +import babel.dates +import dateutil.relativedelta import Image -import math import werkzeug.utils -from openerp.osv import osv, orm, fields import openerp.tools +from openerp.osv import osv, orm, fields +from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -458,7 +461,7 @@ class QWeb(orm.AbstractModel): """ 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"),\ + "li", "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" @@ -776,6 +779,54 @@ class MonetaryConverter(osv.AbstractModel): return self.qweb_object().eval_object( options['display_currency'], options['_qweb_context']) +TIMEDELTA_UNITS = ( + ('year', 3600 * 24 * 365), + ('month', 3600 * 24 * 30), + ('week', 3600 * 24 * 7), + ('day', 3600 * 24), + ('hour', 3600), + ('minute', 60), + ('second', 1) +) +class DurationConverter(osv.AbstractModel): + """ ``duration`` converter, to display integral or fractional values as + human-readable time spans (e.g. 1.5 as "1 hour 30 minutes"). + + Can be used on any numerical field. + + Has a mandatory option ``unit`` which can be one of ``second``, ``minute``, + ``hour``, ``day``, ``week`` or ``year``, used to interpret the numerical + field value before converting it. + + Sub-second values will be ignored. + """ + _name = 'ir.qweb.field.duration' + _inherit = 'ir.qweb.field' + + def value_to_html(self, cr, uid, value, column, options=None, context=None): + 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")) + + locale = babel.Locale.parse( + self.user_lang(cr, uid, context=context).code) + print locale + factor = units[options['unit']] + + sections = [] + r = value * factor + for unit, secs_per_unit in TIMEDELTA_UNITS: + v, r = divmod(r, secs_per_unit) + if not v: continue + section = babel.dates.format_timedelta( + v*secs_per_unit, threshold=1, locale=locale) + if section: + sections.append(section) + return u' '.join(sections) + + def get_field_type(column, options): """ Gets a t-field's effective type from the field's column and its options """ diff --git a/openerp/tests/addons/test_converter/tests/test_html.py b/openerp/tests/addons/test_converter/tests/test_html.py index 6d1f1cd9a56..98c784c39ef 100644 --- a/openerp/tests/addons/test_converter/tests/test_html.py +++ b/openerp/tests/addons/test_converter/tests/test_html.py @@ -22,15 +22,21 @@ class TestExport(common.TransactionCase): def get_column(self, name): return self.Model._all_columns[name].column - def get_converter(self, name): + def get_converter(self, name, type=None): 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) + for postfix in type, column._type, '': + fs = ['ir', 'qweb', 'field'] + if postfix is None: continue + if postfix: fs.append(postfix) + + try: + model = self.registry('.'.join(fs)) + break + except KeyError: pass + + return lambda value, options=None, context=None: model.value_to_html( + self.cr, self.uid, value, column, options=options, context=context) class TestBasicExport(TestExport): _model = 'test_converter.test_model' @@ -297,5 +303,38 @@ class TestDatetimeExport(TestBasicExport): # default lang/format is US self.assertEqual(value, '05/03/2011 00:12:13') -# o2m, m2m? -# reference? +class TestDurationExport(TestBasicExport): + def setUp(self): + super(TestDurationExport, self).setUp() + + self.registry('res.lang').load_lang(self.cr, self.uid, 'fr_FR') + + def test_negative(self): + converter = self.get_converter('float', 'duration') + + with self.assertRaises(ValueError): + converter(-4) + + def test_missing_unit(self): + converter = self.get_converter('float', 'duration') + + with self.assertRaises(ValueError): + converter(4) + + def test_basic(self): + converter = self.get_converter('float', 'duration') + + result = converter(4, {'unit': 'hour'}, {'lang': 'fr_FR'}) + self.assertEqual(result, u'4 heures') + + result = converter(50, {'unit': 'second'}, {'lang': 'fr_FR'}) + self.assertEqual(result, u'50 secondes') + + def test_multiple(self): + converter = self.get_converter('float', 'duration') + + result = converter(1.5, {'unit': 'hour'}, {'lang': 'fr_FR'}) + self.assertEqual(result, u"1 heure 30 minutes") + + result = converter(72, {'unit': 'second'}, {'lang': 'fr_FR'}) + self.assertEqual(result, u"1 minute 12 secondes")