3
0
Fork 0

[MIG] web_widget_float_formula: v9 with i18n

* Update JS to use v9 module system
* Fix non-functioning logic for obtaining the value of an input element
* Eliminate redundant calls (e.g. there were multiple calls to eval)
* Modify formula cleanup to use localized decimal point and thousands separator
 characters
* Add JS unit tests
9.0
Oleg Bulkin 2016-09-12 11:33:29 -07:00
parent d79a4c20ba
commit d02ce7f647
10 changed files with 369 additions and 179 deletions

View File

@ -1,36 +1,61 @@
Allow to write simple mathematic formulas in Integer / Float fields .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
=================================================================== :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
* Possibility to tip a text like "=45 + 4/3 - 5 * (2 +1)"; ========================
* if the formula is correct, The result will be computed and displayed; Formulas in Float Fields
* if the formula is not correct, the initial text is displayed; ========================
Technical informations This module allows the use of simple math formulas in integer/float fields
---------------------- (e.g. "=45 + 4/3 - 5 * (2 + 1)").
* Overloads "instance.web.form.FieldFloat"; (so works for fields.integer & * Only supports parentheses, decimal points, thousands separators, and the
fields.float); operators "+", "-", "*", and "/"
* To compute, the module simply use the eval() javascript function; * Will use the decimal point and thousands separator characters associated
* Rounding computation is not done by this module (The module has the same with your language
behaviour if the user tips "=1/3" or if he tips "0.33[...]"); * If the formula is valid, the result will be computed and displayed, and the
* avoid code injonction by regexpr test: "=alert('security')" is not valid; formula will be stored for editing
* If the formula is not valid, it's retained in the field as text
**Technical Details**
* Overloads web.form_widgets.FieldFloat (so it works for fields.integer &
fields.float)
* Uses the eval() JS function to evaluate the formula
* Does not do any rounding (this is handled elsewhere)
* Avoids code injection by applying strict regex to formula prior to eval()
(e.g. "=alert('security')" would not get evaluated)
Installation
============
To install this module, simply follow the standard install process.
Configuration
=============
No configuration is needed or possible.
Usage Usage
===== =====
See demo here Video: http://www.youtube.com/watch?v=jQGdD34WYrA&hd=1 Install and enjoy. A short demo video can be found at
http://www.youtube.com/watch?v=jQGdD34WYrA.
Roadmap / Limit .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
=============== :alt: Try me on Runbot
* Only supports the four operators: "+" "-" "*" "/" and parenthesis; :target: https://runbot.odoo-community.org/runbot/162/9.0
Known Issues / Roadmap
======================
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback If you spotted it first, help us smash it by providing detailed and welcomed
`here <https://github.com/OCA/web/issues/new?body=module:%20web_widget_float_formula%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. feedback.
Credits Credits
======= =======
@ -39,17 +64,19 @@ Contributors
------------ ------------
* Sylvain Le Gal (https://twitter.com/legalsylvain) * Sylvain Le Gal (https://twitter.com/legalsylvain)
* Oleg Bulkin <o.bulkin@gmail.com>
Maintainer Maintainer
---------- ----------
.. image:: http://odoo-community.org/logo.png .. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association :alt: Odoo Community Association
:target: http://odoo-community.org :target: http://odoo-community.org
This module is maintained by the OCA. This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org. To contribute to this module, please visit http://odoo-community.org.

View File

@ -1,4 +0,0 @@
# -*- encoding: utf-8 -*-
###############################################################################
# See __openerp__.py file for Copyright and Licence Informations.
###############################################################################

View File

@ -1,19 +1,22 @@
# -*- encoding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### # Copyright GRAP
# See Copyright and Licence Informations undermentioned. # Copyright 2016 LasLabs Inc.
############################################################################### # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Web Widget - Formulas in Float fields', 'name': 'Web Widget - Formulas in Float Fields',
'version': '8.0.1.0.0', 'summary': 'Allow use of simple formulas in float fields',
'category': 'web', 'version': '9.0.1.0.0',
'author': 'GRAP,Odoo Community Association (OCA)', 'category': 'Web',
'author': 'GRAP, LasLabs, Odoo Community Association (OCA)',
'website': 'http://www.grap.coop', 'website': 'http://www.grap.coop',
'license': 'AGPL-3', 'license': 'AGPL-3',
'depends': [ 'depends': [
'web', 'web',
], ],
'data': [ 'data': [
'views/qweb.xml', 'views/web_widget_float_formula.xml',
], ],
'installable': False, 'installable': True,
'application': False,
} }

View File

@ -1,132 +0,0 @@
/*******************************************************************************
See __openerp__.py file for Copyright and Licence Informations.
*******************************************************************************/
openerp.web_widget_float_formula = function (instance) {
instance.web.FormView = instance.web.FormView.extend({
/***********************************************************************
Overload section
***********************************************************************/
/**
* Overload : '_process_save' function
1: to force computation of formula if the user realize a keydown directly after the formula input in a tree view ;
2: to clean up the '_formula_text' value in all case to avoid bugs in tree view ;
*/
_process_save: function(save_obj) {
for (var f in this.fields) {
if (!this.fields.hasOwnProperty(f)) { continue; }
f = this.fields[f];
if (f.hasOwnProperty('_formula_text')){
currentval = f.$('input').attr('value')
if (typeof currentval != 'undefined'){
formula = f._get_valid_expression(currentval);
if (formula){
f._compute_result();
}
}
f._clean_formula_text();
}
}
return this._super(save_obj);
},
});
instance.web.form.FieldFloat = instance.web.form.FieldFloat.extend({
/***********************************************************************
Overload section
***********************************************************************/
/**
* Overload : 'start' function to catch 'blur' and 'focus' events.
*/
start: function() {
this.on("blurred", this, this._compute_result);
this.on("focused", this, this._display_formula);
return this._super();
},
/**
* Overload : 'initialize_content' function to clean '_formula_text' value.
*/
initialize_content: function() {
this._clean_formula_text();
return this._super();
},
/***********************************************************************
Custom section
***********************************************************************/
/**
* keep in memory the formula to allow user to edit it again.
The formula has to be keeped in memory until a 'save' action.
*/
_formula_text: '',
/**
* Clean '_formula_text' value.
*/
_clean_formula_text: function() {
this._formula_text = '';
},
/**
* Return a valid formula from a val, if possible.
Otherwise, return false.
*/
_get_valid_expression: function(val) {
// Trim the value
currenttxt = val.toString().replace(/^\s+|\s+$/g, '');
// Test if the value is a formula
if (currenttxt[0] == '=') {
// allowed chars : [0-9] .,+-/*() and spaces
myreg = RegExp('[0-9]|\\s|\\.|,|\\(|\\)|\\+|\\-|\\*|\\/','g')
// Test to avoid code injonction in eval function.
if (currenttxt.substring(1).replace(myreg, '') == ''){
try {
// Try to compute
formula = currenttxt.substring(1).replace(/,/g,'.');
var floatval = eval(formula);
}catch (e) {}
if (typeof (floatval) != 'undefined'){
return formula;
}
}
}
return false;
},
/**
* test if the content of the field is a valid formula,
* compute the result, and replace the current value by the final result.
*/
_compute_result: function() {
var formula
// Erase old formula
this._formula_text = '';
formula = this._get_valid_expression(this.$el.find('input').attr('value'));
if (formula){
// Store new formula
this._formula_text = "=" + formula;
// put the result in the field
this.set_value(eval(formula));
// Force rendering anyway to avoid format loss if no change
this.render_value();
}
},
/**
* Display the stored formula in the field, to allow modification.
*/
_display_formula: function() {
if (this._formula_text != ''){
this.$el.find('input').val(this._formula_text);
}
},
});
};

View File

@ -0,0 +1,103 @@
/**
* Copyright GRAP
* Copyright 2016 LasLabs Inc.
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
**/
odoo.define('web_widget_float_formula', function(require) {
"use strict";
var form_view = require('web.FormView');
form_view.include({
// Ensure that formula is computed even if user saves right away and
// clean up '_formula_text' value to avoid bugs in tree view
_process_save: function(save_obj) {
for (var f in this.fields) {
if (!this.fields.hasOwnProperty(f)) { continue; }
f = this.fields[f];
if (f.hasOwnProperty('_formula_text')) {
f._compute_result();
f._clean_formula_text();
}
}
return this._super(save_obj);
},
});
var core = require('web.core');
core.bus.on('web_client_ready', null, function () {
// Import localization values used to eval formula
var translation_params = core._t.database.parameters;
var decimal_point = translation_params.decimal_point;
var thousands_sep = translation_params.thousands_sep;
var field_float = require('web.form_widgets').FieldFloat;
field_float.include({
start: function() {
this.on('blurred', this, this._compute_result);
this.on('focused', this, this._display_formula);
return this._super();
},
initialize_content: function() {
this._clean_formula_text();
return this._super();
},
_formula_text: '',
_clean_formula_text: function() {
this._formula_text = '';
},
_process_formula: function(formula) {
var clean_formula = formula.toString().replace(/^\s+|\s+$/g, '');
if (clean_formula[0] == '=') {
clean_formula = clean_formula.substring(1);
var myreg = RegExp('[0-9]|\\s|\\.|,|\\(|\\)|\\+|\\-|\\*|\\/','g');
if (clean_formula.replace(myreg, '') === '') {
return clean_formula;
}
}
return false;
},
_eval_formula: function(formula) {
var value;
formula = formula.replace(thousands_sep, '').replace(decimal_point, '.');
try {
value = eval(formula);
}
catch(e) {}
if (typeof value != 'undefined') {
return value;
}
return false;
},
_compute_result: function() {
this._clean_formula_text();
var formula = this._process_formula(this.$el.find('input').val());
if (formula !== false) {
var value = this._eval_formula(formula);
if (value !== false) {
this._formula_text = "=" + formula;
this.set_value(value);
// Force rendering to avoid format loss if there's no change
this.render_value();
}
}
},
// Display the formula stored in the field to allow modification
_display_formula: function() {
if (this._formula_text !== '') {
this.$el.find('input').val(this._formula_text);
}
},
});
});
});

View File

@ -0,0 +1,161 @@
/**
* Copyright 2016 LasLabs Inc.
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
**/
odoo.define_section('web_widget_float_formula', ['web.form_common', 'web.form_widgets', 'web.core'], function(test) {
'use strict';
window.test_setup = function(self, form_common, form_widgets, core) {
core.bus.trigger('web_client_ready');
var field_manager = new form_common.DefaultFieldManager(null, {});
var filler = {'attrs': {}}; // Needed to instantiate FieldFloat
self.field = new form_widgets.FieldFloat(field_manager, filler);
self.$element = $('<input>');
self.field.$el.append(self.$element);
};
test('Float fields should have a _formula_text property that defaults to an empty string',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.strictEqual(this.field._formula_text, '');
});
test('.initialize_content() on float fields should clear the _formula_text property',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = 'test';
this.field.initialize_content();
assert.strictEqual(this.field._formula_text, '');
});
test('._clean_formula_text() on float fields should clear the _formula_text property',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = 'test';
this.field._clean_formula_text();
assert.strictEqual(this.field._formula_text, '');
});
test('._process_formula() on float fields should return false when given invalid formulas',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.strictEqual(this.field._process_formula('2*3'), false);
assert.strictEqual(this.field._process_formula('=2*3a'), false);
});
test('._process_formula() on float fields should properly process a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.strictEqual(this.field._process_formula(' =2*3\n'), '2*3');
});
test('._eval_formula() on float fields should properly evaluate a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula('2*3'), 6);
});
test('._eval_formula() on float fields should properly handle alternative decimal points and thousands seps',
function(assert, form_common, form_widgets, core) {
var translation_params = core._t.database.parameters;
translation_params.decimal_point = ',';
translation_params.thousands_sep = '.';
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula('2.000*3,5'), 7000);
});
test('._eval_formula() on float fields should return false when given an input that evals to undefined',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula(''), false);
});
test('._eval_formula() on float fields should return false when given an input that cannot be evaluated',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula('*/'), false);
});
test('._compute_result() on float fields should always clean up _formula_text',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = 'test';
this.field._compute_result();
assert.strictEqual(this.field._formula_text, '');
});
test('._compute_result() should not change the value of the associated input when it is not a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.$element.val('=2*3a');
this.field._compute_result();
assert.strictEqual(this.$element.val(), '=2*3a');
});
test('._compute_result() should not change the value of the associated input when it cannot be evaled',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.$element.val('=*/');
this.field._compute_result();
assert.strictEqual(this.$element.val(), '=*/');
});
test('._compute_result() should behave properly when the current value of the input element is a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.$element.val('=2*3');
this.field._compute_result();
assert.equal(this.$element.val(), '6');
assert.strictEqual(this.field._formula_text, '=2*3');
});
test('._display_formula() should update the value of the input element when there is a stored formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = "test";
this.field._display_formula();
assert.equal(this.$element.val(), 'test');
});
test('.start() on float fields should add a handler that calls ._compute_result() when the field is blurred',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field.called = false;
this.field._compute_result = function() {
this.called = true;
};
this.field.start();
this.field.trigger('blurred');
assert.strictEqual(this.field.called, true);
});
test('.start() on float fields should add a handler that calls ._display_formula() when the field is focused',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field.called = false;
this.field._display_formula = function() {
this.called = true;
};
this.field.start();
this.field.trigger('focused');
assert.strictEqual(this.field.called, true);
});
});

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_js

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from openerp.tests import HttpCase
class TestJS(HttpCase):
def test_js(self):
self.phantom_js(
"/web/tests?module=web_widget_float_formula",
"",
login="admin",
)

View File

@ -1,9 +0,0 @@
<openerp>
<data>
<template id="assets_backend" name="web_widget_float_formula assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_widget_float_formula/static/src/js/models.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright GRAP
Copyright 2016 LasLabs Inc.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<template id="assets_backend" name="web_widget_float_formula Assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_widget_float_formula/static/src/js/web_widget_float_formula.js"/>
</xpath>
</template>
<template id="qunit_suite" name="web_widget_float_formula Test Assets" inherit_id="web.qunit_suite">
<xpath expr="//html/head" position="inside">
<script type="text/javascript" src="/web_widget_float_formula/static/tests/js/test_web_widget_float_formula.js"/>
</xpath>
</template>
</odoo>