Hoang Gia Minh
5 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 351 additions and 0 deletions
-
82web_widget_float_formula/README.rst
-
0web_widget_float_formula/__init__.py
-
22web_widget_float_formula/__manifest__.py
-
BINweb_widget_float_formula/static/description/icon.png
-
103web_widget_float_formula/static/src/js/web_widget_float_formula.js
-
102web_widget_float_formula/static/tests/js/test_web_widget_float_formula.js
-
5web_widget_float_formula/tests/__init__.py
-
16web_widget_float_formula/tests/test_js.py
-
21web_widget_float_formula/views/web_widget_float_formula.xml
@ -0,0 +1,82 @@ |
|||||
|
.. 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 |
||||
|
|
||||
|
======================== |
||||
|
Formulas in Float Fields |
||||
|
======================== |
||||
|
|
||||
|
This module allows the use of simple math formulas in integer/float fields |
||||
|
(e.g. "=45 + 4/3 - 5 * (2 + 1)"). |
||||
|
|
||||
|
* Only supports parentheses, decimal points, thousands separators, and the |
||||
|
operators "+", "-", "*", and "/" |
||||
|
* Will use the decimal point and thousands separator characters associated |
||||
|
with your language |
||||
|
* If the formula is valid, the result will be computed and displayed, and the |
||||
|
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 |
||||
|
===== |
||||
|
|
||||
|
Install and enjoy. A short demo video can be found at |
||||
|
http://www.youtube.com/watch?v=jQGdD34WYrA. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/162/10.0 |
||||
|
|
||||
|
Known Issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
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. |
||||
|
If you spotted it first, help us smash it by providing detailed and welcomed |
||||
|
feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Sylvain Le Gal (https://twitter.com/legalsylvain) |
||||
|
* Oleg Bulkin <o.bulkin@gmail.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: http://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: http://odoo-community.org |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright GRAP |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
{ |
||||
|
'name': 'Web Widget - Formulas in Float Fields', |
||||
|
'summary': 'Allow use of simple formulas in float fields', |
||||
|
'version': '11.0.1.0.0', |
||||
|
'category': 'Web', |
||||
|
'author': 'GRAP, LasLabs, Odoo Community Association (OCA)', |
||||
|
'website': 'http://www.grap.coop', |
||||
|
'license': 'AGPL-3', |
||||
|
'depends': [ |
||||
|
'web', |
||||
|
], |
||||
|
'data': [ |
||||
|
'views/web_widget_float_formula.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'application': False, |
||||
|
} |
After Width: 128 | Height: 128 | Size: 3.0 KiB |
@ -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 core = require('web.core'); |
||||
|
var basic_fields = require('web.basic_fields'); |
||||
|
var FieldFloat = basic_fields.FieldFloat; |
||||
|
FieldFloat.include({ |
||||
|
start: function() { |
||||
|
this._super(); |
||||
|
this.on('blurred', this, this._compute_result); |
||||
|
this.on('focused', this, this._display_formula); |
||||
|
|
||||
|
this.setUpFocus(); |
||||
|
}, |
||||
|
|
||||
|
setUpFocus: function() { |
||||
|
var self = this; |
||||
|
this.$el.on({ |
||||
|
focus: function() { self.trigger('focused'); }, |
||||
|
blur: function() { self.trigger('blurred'); } |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
commitChanges: function() { |
||||
|
this._compute_result(); |
||||
|
}, |
||||
|
|
||||
|
_formula_text: '', |
||||
|
|
||||
|
_clean_formula_text: function() { |
||||
|
this._formula_text = ''; |
||||
|
}, |
||||
|
|
||||
|
_process_formula: function(formula) { |
||||
|
try{ |
||||
|
formula = formula.toString(); |
||||
|
} catch (ex) { |
||||
|
return false; |
||||
|
} |
||||
|
var clean_formula = formula.toString().replace(/^\s+|\s+$/g, ''); |
||||
|
if (clean_formula[0] == '=') { |
||||
|
clean_formula = clean_formula.substring(1); |
||||
|
var myreg = new RegExp('[0-9]|\\s|\\.|,|\\(|\\)|\\+|\\-|\\*|\\/', 'g'); |
||||
|
if (clean_formula.replace(myreg, '') === '') { |
||||
|
return clean_formula; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
}, |
||||
|
|
||||
|
_eval_formula: function(formula) { |
||||
|
// 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 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 input = this.$input.val(); |
||||
|
|
||||
|
var formula = this._process_formula(input); |
||||
|
if (formula !== false) { |
||||
|
var value = this._eval_formula(formula); |
||||
|
if (value !== false) { |
||||
|
this._formula_text = "=" + formula; |
||||
|
|
||||
|
var decimal_point = core._t.database.parameters.decimal_point; |
||||
|
|
||||
|
// _setValue
|
||||
|
this._setValue(value.toString().replace(decimal_point, '.')) |
||||
|
this._render(); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Display the formula stored in the field to allow modification
|
||||
|
_display_formula: function() { |
||||
|
if (this._formula_text !== '') { |
||||
|
this.$input.val(this._formula_text); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
}); |
@ -0,0 +1,102 @@ |
|||||
|
/** |
||||
|
* 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 core = require('web.core'); |
||||
|
var testUtils = require('web.test_utils'); |
||||
|
var FormView = require('web.FormView'); |
||||
|
|
||||
|
var createView = testUtils.createView; |
||||
|
var triggerKeypressEvent = testUtils.triggerKeypressEvent; |
||||
|
|
||||
|
var assertClose = function(assert, actual, expected, message) { |
||||
|
var pass = Math.abs(actual - expected) < 0.00001; |
||||
|
|
||||
|
assert.pushResult({ |
||||
|
result: pass, |
||||
|
actual: actual, |
||||
|
expected: expected, |
||||
|
message: message |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
QUnit.module('web_widget_float_formula', { |
||||
|
beforeEach: function() { |
||||
|
this.data = { |
||||
|
foo: { |
||||
|
fields: { |
||||
|
bar: { string: "Bar", type: "float" } |
||||
|
}, |
||||
|
records: [ |
||||
|
{ id: 1, bar: 1.2 } |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
QUnit.test('Float fields in form view', function(assert) { |
||||
|
assert.expect(8); |
||||
|
|
||||
|
var form = createView({ |
||||
|
View: FormView, |
||||
|
model: 'foo', |
||||
|
data: this.data, |
||||
|
res_id: 1, |
||||
|
arch: '<form><field name="bar"/></form>', |
||||
|
viewOptions: { |
||||
|
mode: 'edit', |
||||
|
}, |
||||
|
mockRPC: function (route, args) { |
||||
|
if (args.method === 'write') { |
||||
|
assert.step('save'); |
||||
|
} |
||||
|
return this._super.apply(this, arguments); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
assertClose(assert, form.$('input').val(), 1.2); |
||||
|
|
||||
|
form.$('input').val('=(1 + 2) / 3').blur(); |
||||
|
assertClose(assert, form.$('input').val(), 1, |
||||
|
'blur event should trigger compute event'); |
||||
|
|
||||
|
form.$('input').focus(); |
||||
|
assert.strictEqual(form.$('input').val(), '=(1 + 2) / 3', |
||||
|
'focus event should display the forumla'); |
||||
|
|
||||
|
form.$('input').val('=(1 * 2x) /').blur(); |
||||
|
assert.strictEqual(form.$('input').val(), '=(1 * 2x) /', |
||||
|
'invalid formula should not be calculated'); |
||||
|
|
||||
|
_.extend(core._t.database.parameters, { |
||||
|
grouping: [3, 0], |
||||
|
decimal_point: ',', |
||||
|
thousands_sep: '.' |
||||
|
}); |
||||
|
|
||||
|
form.$('input').val('=2.000*3,5').blur(); |
||||
|
assert.strictEqual(form.$('input').val(), "7.000,00", |
||||
|
'eval should handle decimal point and thousands separator properly'); |
||||
|
|
||||
|
_.extend(core._t.database.parameters, { |
||||
|
grouping: [3, 0], |
||||
|
decimal_point: '.', |
||||
|
thousands_sep: ',' |
||||
|
}); |
||||
|
|
||||
|
form.$('input').val('=3-2'); |
||||
|
|
||||
|
form.$buttons.find('.o_form_button_save').click(); |
||||
|
assert.verifySteps(['save'], 'should have saved'); |
||||
|
assertClose(assert, form.$('.o_field_widget').text(), 1, |
||||
|
'save should also trigger compute result') |
||||
|
|
||||
|
form.destroy(); |
||||
|
}); |
||||
|
|
||||
|
}); |
@ -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 |
@ -0,0 +1,16 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import odoo.tests |
||||
|
|
||||
|
|
||||
|
class FloatFormulaSuite(odoo.tests.HttpCase): |
||||
|
|
||||
|
def test_js(self): |
||||
|
self.phantom_js( |
||||
|
"/web/tests?module=web_widget_float_formula", |
||||
|
"", |
||||
|
"", |
||||
|
login="admin" |
||||
|
) |
@ -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="//t[@t-set='head']" position="inside"> |
||||
|
<script type="text/javascript" src="/web_widget_float_formula/static/tests/js/test_web_widget_float_formula.js"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue