+11
-8
@@ -1976,13 +1976,16 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!digits || !digits.every(function(e) {
|
||||
return e !== null;
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
var shift = Math.round(Math.log(Math.abs(factor)) / Math.LN10);
|
||||
return [digits[0] + shift, digits[1] - shift];
|
||||
var int_size = digits[0];
|
||||
if (int_size !== null) {
|
||||
int_size += shift;
|
||||
}
|
||||
var dec_size = digits[1];
|
||||
if (dec_size !== null) {
|
||||
dec_size += shift;
|
||||
}
|
||||
return [int_size, dec_size];
|
||||
},
|
||||
get_symbol: function(record, symbol) {
|
||||
if (record && (symbol in record.model.fields)) {
|
||||
@@ -2040,7 +2043,7 @@
|
||||
if (value !== null) {
|
||||
value /= factor;
|
||||
var digits = this.digits(record);
|
||||
if (digits) {
|
||||
if (digits && (digits[1] !== null)) {
|
||||
// Round to avoid float precision error
|
||||
// after the division by factor
|
||||
value = value.toFixed(digits[1]);
|
||||
@@ -2061,7 +2064,7 @@
|
||||
useGrouping: grouping,
|
||||
};
|
||||
var digits = this.digits(record, factor);
|
||||
if (digits) {
|
||||
if (digits && (digits[1] !== null)) {
|
||||
options.minimumFractionDigits = digits[1];
|
||||
options.maximumFractionDigits = digits[1];
|
||||
}
|
||||
|
||||
+33
-11
@@ -2180,13 +2180,27 @@ function eval_pyson(value){
|
||||
display: function() {
|
||||
var record = this.record;
|
||||
var step = 'any';
|
||||
var max, min;
|
||||
if (record) {
|
||||
var digits = this.digits;
|
||||
if (digits) {
|
||||
step = Math.pow(10, -digits[1]).toFixed(digits[1]);
|
||||
if (digits[1] !== null) {
|
||||
step = Math.pow(10, -digits[1]).toFixed(digits[1]);
|
||||
}
|
||||
if (digits[0] !== null) {
|
||||
max = '9'.repeat(digits[0]);
|
||||
if (digits[1] !== null) {
|
||||
max += '.' + '9'.repeat(digits[1]);
|
||||
} else {
|
||||
max += 1;
|
||||
}
|
||||
min = '-' + max;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.input.attr('step', step);
|
||||
this.input.attr('max', max);
|
||||
this.input.attr('min', min);
|
||||
Sao.View.Form.Float._super.display.call(this);
|
||||
}
|
||||
});
|
||||
@@ -5567,13 +5581,7 @@ function eval_pyson(value){
|
||||
get digits() {
|
||||
var record = this.parent_widget.record;
|
||||
if (record) {
|
||||
var digits = record.expr_eval(this.definition.digits);
|
||||
if (!digits || !digits.every(function(e) {
|
||||
return e !== null;
|
||||
})) {
|
||||
return null;
|
||||
}
|
||||
return digits;
|
||||
return record.expr_eval(this.definition.digits);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -5592,13 +5600,27 @@ function eval_pyson(value){
|
||||
set_value: function(value) {
|
||||
var step = 'any',
|
||||
options = {};
|
||||
var max, min;
|
||||
var digits = this.digits;
|
||||
if (digits) {
|
||||
step = Math.pow(10, -digits[1]).toFixed(digits[1]);
|
||||
options.minimumFractionDigits = digits[1];
|
||||
options.maximumFractionDigits = digits[1];
|
||||
if (digits[1] !== null) {
|
||||
step = Math.pow(10, -digits[1]).toFixed(digits[1]);
|
||||
options.minimumFractionDigits = digits[1];
|
||||
options.maximumFractionDigits = digits[1];
|
||||
}
|
||||
if (digits[0] !== null) {
|
||||
max = '9'.repeat(digits[0]);
|
||||
if (digits[1] !== null) {
|
||||
max += '.' + '9'.repeat(digits[1]);
|
||||
} else {
|
||||
max += 1;
|
||||
}
|
||||
min = '-' + max;
|
||||
}
|
||||
}
|
||||
this.input.attr('step', step);
|
||||
this.input.attr('max', max);
|
||||
this.input.attr('min', min);
|
||||
Sao.View.Form.Dict.Float._super.set_value.call(this, value, options);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import gettext
|
||||
import locale
|
||||
import math
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
from gi.repository import Gdk, GObject, Gtk
|
||||
@@ -64,13 +65,18 @@ class NumberEntry(Gtk.Entry, Gtk.Editable):
|
||||
value = Decimal(locale.delocalize(text, self.monetary))
|
||||
except (ValueError, InvalidOperation):
|
||||
return position
|
||||
try:
|
||||
if (value and self.__digits is not None
|
||||
and (round(value, self.__digits) != value
|
||||
or value.as_tuple().exponent < -self.__digits)):
|
||||
if self.__digits:
|
||||
int_size, dec_size = self.__digits
|
||||
try:
|
||||
if int_size is not None and value:
|
||||
if math.ceil(math.log10(abs(value))) > int_size:
|
||||
return position
|
||||
if dec_size is not None:
|
||||
if (round(value, dec_size) != value
|
||||
or value.as_tuple().exponent < -dec_size):
|
||||
return position
|
||||
except InvalidOperation:
|
||||
return position
|
||||
except InvalidOperation:
|
||||
return position
|
||||
length = len(new_text.encode('utf-8'))
|
||||
buffer_.insert_text(position, new_text, length)
|
||||
return position + length
|
||||
|
||||
@@ -472,7 +472,7 @@ class FloatField(Field):
|
||||
d = value * factor
|
||||
if not isinstance(d, Decimal):
|
||||
d = Decimal(repr(d))
|
||||
if digits:
|
||||
if digits and digits[1] is not None:
|
||||
p = int(digits[1])
|
||||
elif d == d.to_integral_value():
|
||||
p = 0
|
||||
|
||||
@@ -301,15 +301,12 @@ class DictFloatEntry(DictIntegerEntry):
|
||||
def digits(self):
|
||||
record = self.parent_widget.record
|
||||
if record:
|
||||
digits = record.expr_eval(self.definition.get('digits'))
|
||||
if not digits or any(d is None for d in digits):
|
||||
return
|
||||
return digits
|
||||
return record.expr_eval(self.definition.get('digits'))
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
digits = self.digits
|
||||
if digits:
|
||||
if digits and all(digits):
|
||||
return sum(digits)
|
||||
else:
|
||||
return 18
|
||||
@@ -318,15 +315,19 @@ class DictFloatEntry(DictIntegerEntry):
|
||||
return self.widget.value
|
||||
|
||||
def set_value(self, value):
|
||||
digits = self.digits
|
||||
if digits:
|
||||
self.widget.digits = digits[1]
|
||||
else:
|
||||
self.widget.digits = None
|
||||
self.widget.digits = digits = self.digits
|
||||
self.widget.set_width_chars(self.width)
|
||||
if isinstance(value, (int, float, Decimal)):
|
||||
txt_val = locale.localize(
|
||||
'{0:.{1}f}'.format(value, digits[1]), True)
|
||||
d = value
|
||||
if not isinstance(d, Decimal):
|
||||
d = Decimal(repr(d))
|
||||
if digits and digits[1] is not None:
|
||||
p = int(digits[1])
|
||||
elif d == d.to_integral_value():
|
||||
p = 0
|
||||
else:
|
||||
p = -int(d.as_tuple().exponent)
|
||||
txt_val = locale.localize('{0:.{1}f}'.format(d, p), True)
|
||||
else:
|
||||
txt_val = ''
|
||||
self.widget.set_text(txt_val)
|
||||
|
||||
@@ -14,15 +14,11 @@ class Float(Integer):
|
||||
@property
|
||||
def width(self):
|
||||
digits = self.digits
|
||||
if digits:
|
||||
if digits and all(digits):
|
||||
return sum(digits)
|
||||
else:
|
||||
return self.attrs.get('width', 18)
|
||||
|
||||
def display(self):
|
||||
digits = self.digits
|
||||
if digits:
|
||||
self.entry.digits = digits[1]
|
||||
else:
|
||||
self.entry.digits = None
|
||||
self.entry.digits = self.digits
|
||||
super(Float, self).display()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
* Validate integer digits
|
||||
* Add support for DATE_TRUNC from INTERVAL to SQLite backend
|
||||
* Add support for EXTRACT from INTERVAL to SQLite backend
|
||||
* Add msg_gettext and msg_ngettext to Report context
|
||||
|
||||
@@ -430,7 +430,7 @@ Float
|
||||
|
||||
Integers can be replaced by a :class:`~trytond.pyson.PYSON` statement.
|
||||
If digits is ``None`` or any values of the tuple is ``None``, no validation
|
||||
on the numbers is done.
|
||||
on this part of the numbers is done.
|
||||
The tuple can be replaced by a string containing the name of a
|
||||
:class:`Many2One` pointing to a :class:`~trytond.model.DigitsMixin`.
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="text">The value "%(value)s" for field "%(field)s" in record "%(record)s" of "%(model)s" is too long (%(size)i > %(max_size)i).</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_digits_validation_record">
|
||||
<field name="text">The number of digits in the value "%(value)r" for field "%(field)s" in record "%(record)s" of "%(model)s" exceeds the limit of "%(digits)i".</field>
|
||||
<field name="text">The number of digits in the value "%(value)r" for field "%(field)s" in record "%(record)s" of "%(model)s" exceeds the limit of "%(digits)r".</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_forbidden_char_validation_record">
|
||||
<field name="text">The value "%(value)s" for field "%(field)s" in record "%(record)s" of "%(model)s" contains some invalid chars "%(chars)s".</field>
|
||||
|
||||
@@ -26,7 +26,7 @@ class DigitsMixin:
|
||||
return digits
|
||||
|
||||
def _get_digits(self):
|
||||
return (16, self.digits)
|
||||
return (None, self.digits)
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
|
||||
@@ -12,11 +12,11 @@ def digits_validate(value):
|
||||
'digits must be a tuple or a string'
|
||||
if isinstance(value, tuple):
|
||||
for i in value:
|
||||
assert isinstance(i, (int, PYSON)), \
|
||||
'digits must be tuple of integers or PYSON'
|
||||
assert isinstance(i, (type(None), int, PYSON)), \
|
||||
"digits must be tuple of integers, PYSON or None"
|
||||
if isinstance(i, PYSON):
|
||||
assert i.types() == {int}, \
|
||||
'PYSON digits must return an integer'
|
||||
assert i.types() <= {int, type(None)}, \
|
||||
"PYSON digits must return an integer or None"
|
||||
|
||||
|
||||
def _get_digits_depends(field):
|
||||
|
||||
@@ -5,6 +5,7 @@ import base64
|
||||
import csv
|
||||
import datetime
|
||||
import decimal
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
import warnings
|
||||
@@ -1455,7 +1456,7 @@ class ModelStorage(Model):
|
||||
def digits_test(record, digits, field_name):
|
||||
def raise_error(value):
|
||||
error_args = cls.__names__(field_name, record)
|
||||
error_args['digits'] = digits[1]
|
||||
error_args['digits'] = digits
|
||||
raise DigitsValidationError(
|
||||
gettext('ir.msg_digits_validation_record',
|
||||
**error_args))
|
||||
@@ -1467,16 +1468,20 @@ class ModelStorage(Model):
|
||||
digits = digit_record.get_digits()
|
||||
else:
|
||||
digits = None
|
||||
if (value is None
|
||||
or not digits
|
||||
or any(d is None for d in digits)):
|
||||
if value is None or not digits:
|
||||
return
|
||||
if isinstance(value, Decimal):
|
||||
if value.as_tuple().exponent < -digits[1]:
|
||||
raise_error(value)
|
||||
else:
|
||||
if round(value, digits[1]) != value:
|
||||
|
||||
int_size, dec_size = digits
|
||||
if int_size is not None and value:
|
||||
if math.ceil(math.log10(abs(value))) > int_size:
|
||||
raise_error(value)
|
||||
if dec_size is not None:
|
||||
if isinstance(value, Decimal):
|
||||
if value.as_tuple().exponent < -dec_size:
|
||||
raise_error(value)
|
||||
else:
|
||||
if round(value, dec_size) != value:
|
||||
raise_error(value)
|
||||
# validate digits
|
||||
if getattr(field, 'digits', None):
|
||||
if is_pyson(field.digits):
|
||||
|
||||
@@ -143,6 +143,52 @@ class FieldFloatTestCase(TestCase):
|
||||
|
||||
self.assertEqual(record.float, 0.123456789012345)
|
||||
|
||||
@with_transaction()
|
||||
def test_float_int_digits_valid(self):
|
||||
"Test create float with limit integer digits"
|
||||
pool = Pool()
|
||||
Float = pool.get('test.float_digits')
|
||||
|
||||
record, = Float.create([{
|
||||
'float': 1234567890123456,
|
||||
}])
|
||||
|
||||
self.assertEqual(record.float, 1234567890123456)
|
||||
|
||||
@with_transaction()
|
||||
def test_float_negative_int_digits_valid(self):
|
||||
"Test create negative float with limit integer digits"
|
||||
pool = Pool()
|
||||
Float = pool.get('test.float_digits')
|
||||
|
||||
record, = Float.create([{
|
||||
'float': -1234567890123456,
|
||||
}])
|
||||
|
||||
self.assertEqual(record.float, -1234567890123456)
|
||||
|
||||
@with_transaction()
|
||||
def test_float_int_digits_invalid(self):
|
||||
"Test create float with invalid integer digits"
|
||||
pool = Pool()
|
||||
Float = pool.get('test.float_digits')
|
||||
|
||||
with self.assertRaises(DigitsValidationError):
|
||||
Float.create([{
|
||||
'float': '12345678901234567',
|
||||
}])
|
||||
|
||||
@with_transaction()
|
||||
def test_float_negative_int_digits_invalid(self):
|
||||
"Test create negative float with invalid integer digits"
|
||||
pool = Pool()
|
||||
Float = pool.get('test.float_digits')
|
||||
|
||||
with self.assertRaises(DigitsValidationError):
|
||||
Float.create([{
|
||||
'float': -12345678901234567,
|
||||
}])
|
||||
|
||||
@with_transaction()
|
||||
def test_search_equals(self):
|
||||
"Test search float equals"
|
||||
|
||||
@@ -175,7 +175,7 @@ class FieldNumericTestCase(TestCase):
|
||||
|
||||
Numeric.create([{
|
||||
'digits': 2,
|
||||
'numeric': Decimal('10e25'),
|
||||
'numeric': Decimal('10e15'),
|
||||
}])
|
||||
|
||||
@with_transaction()
|
||||
@@ -189,6 +189,52 @@ class FieldNumericTestCase(TestCase):
|
||||
'numeric': Decimal('1.10'),
|
||||
}])
|
||||
|
||||
@with_transaction()
|
||||
def test_create_int_digits_valid(self):
|
||||
"Test create numeric with limit integer digits"
|
||||
pool = Pool()
|
||||
Numeric = pool.get('test.numeric_digits')
|
||||
|
||||
record, = Numeric.create([{
|
||||
'numeric': Decimal('10e15'),
|
||||
}])
|
||||
|
||||
self.assertEqual(record.numeric, Decimal('10e15'))
|
||||
|
||||
@with_transaction()
|
||||
def test_create_negative_int_digits_valid(self):
|
||||
"Test create negative numeric with limit integer digits"
|
||||
pool = Pool()
|
||||
Numeric = pool.get('test.numeric_digits')
|
||||
|
||||
record, = Numeric.create([{
|
||||
'numeric': Decimal('-10e15'),
|
||||
}])
|
||||
|
||||
self.assertEqual(record.numeric, Decimal('-10e15'))
|
||||
|
||||
@with_transaction()
|
||||
def test_create_int_digits_invalid(self):
|
||||
"Test create numeric with invalid integer digits"
|
||||
pool = Pool()
|
||||
Numeric = pool.get('test.numeric_digits')
|
||||
|
||||
with self.assertRaises(DigitsValidationError):
|
||||
Numeric.create([{
|
||||
'numeric': Decimal('10e16'),
|
||||
}])
|
||||
|
||||
@with_transaction()
|
||||
def test_create_negative_int_digits_invalid(self):
|
||||
"Test create negative numeric with invalid integer digits"
|
||||
pool = Pool()
|
||||
Numeric = pool.get('test.numeric_digits')
|
||||
|
||||
with self.assertRaises(DigitsValidationError):
|
||||
Numeric.create([{
|
||||
'numeric': Decimal('-10e16'),
|
||||
}])
|
||||
|
||||
@with_transaction()
|
||||
def test_search_equals(self):
|
||||
"Test search numeric equals"
|
||||
|
||||
Reference in New Issue
Block a user