diff --git a/CHANGELOG b/CHANGELOG index c8aedd873f..5050f7a18e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add digits mixin * Use argon2 or scrypt to hash password by default * Allow PYSON Expression as key for PYSON In with dict object * Add method to format warning name with records diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst index d44eed7c7e..11b4fd04ba 100644 --- a/doc/ref/models/fields.rst +++ b/doc/ref/models/fields.rst @@ -383,6 +383,8 @@ instance. 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 will be done. + The tuple can be replaced by a string containing the name of a + :class:`Many2One` pointing to a :class:`DigitsMixin`. Numeric ------- diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index 84bb81b653..e8d81a46d7 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -802,6 +802,21 @@ Instance methods: (1) the value. If no symbol parameter is supplied then the mixin uses the value of attribute named ``symbol``. +=========== +DigitsMixin +=========== + +.. class:: DigitsMixin + +A mixin_ to manage the digits of :attr:`Float.digits` and +:attr:`Numeric.digits` from a :class:`Model`. + +Instance methods: + +.. method:: DigitsMixin.get_digits() + + Return a tuple of two integers to use a ``digits`` attribute. + ================ sequence_ordered ================ diff --git a/trytond/model/__init__.py b/trytond/model/__init__.py index 9d3f3cf9aa..a2db19a1aa 100644 --- a/trytond/model/__init__.py +++ b/trytond/model/__init__.py @@ -10,6 +10,7 @@ from .dictschema import DictSchemaMixin from .match import MatchMixin from .union import UnionMixin from .symbol import SymbolMixin +from .digits import DigitsMixin from .multivalue import MultiValueMixin, ValueMixin from .descriptors import dualmethod from .order import sequence_ordered @@ -20,6 +21,6 @@ from .avatar import avatar_mixin __all__ = ['Model', 'ModelView', 'ModelStorage', 'ModelSingleton', 'ModelSQL', 'Check', 'Unique', 'Exclude', 'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin', 'dualmethod', - 'MultiValueMixin', 'ValueMixin', 'SymbolMixin', + 'MultiValueMixin', 'ValueMixin', 'SymbolMixin', 'DigitsMixin', 'EvalEnvironment', 'sequence_ordered', 'DeactivableMixin', 'tree', 'avatar_mixin'] diff --git a/trytond/model/digits.py b/trytond/model/digits.py new file mode 100644 index 0000000000..4761efc90e --- /dev/null +++ b/trytond/model/digits.py @@ -0,0 +1,34 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.cache import Cache +from trytond.rpc import RPC + + +class DigitsMixin: + __slots__ = () + + _digits_cache = Cache('_digits_mixin..get_digits', context=False) + + @classmethod + def __setup__(cls): + super().__setup__() + cls.__rpc__.update({ + 'get_digits': RPC(instantiate=0, cache=dict(days=1)), + }) + + def get_digits(self): + key = str(self) + digits = self._digits_cache.get(key) + if digits is not None: + return digits + digits = self._get_digits() + self._digits_cache.set(key, digits) + return digits + + def _get_digits(self): + return (16, self.digits) + + @classmethod + def write(cls, *args): + super().write(*args) + cls._digits_cache.clear() diff --git a/trytond/model/fields/float.py b/trytond/model/fields/float.py index 0534dd125f..c68ba0b445 100644 --- a/trytond/model/fields/float.py +++ b/trytond/model/fields/float.py @@ -6,13 +6,15 @@ from .field import Field def digits_validate(value): if value: - assert isinstance(value, tuple), 'digits must be a tuple' - for i in value: - assert isinstance(i, (int, PYSON)), \ - 'digits must be tuple of integers or PYSON' - if isinstance(i, PYSON): - assert i.types().issubset(set([int, int])), \ - 'PYSON digits must return an integer' + assert isinstance(value, (tuple, str)), \ + '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' + if isinstance(i, PYSON): + assert i.types().issubset(set([int, int])), \ + 'PYSON digits must return an integer' class Float(Field): @@ -31,6 +33,12 @@ class Float(Field): :param digits: a list of two integers defining the total of digits and the number of decimals of the float. ''' + if isinstance(digits, str): + if depends is None: + depends = [digits] + elif digits not in depends: + depends = depends.copy() + depends.append(digits) super(Float, self).__init__(string=string, help=help, required=required, readonly=readonly, domain=domain, states=states, select=select, on_change=on_change, on_change_with=on_change_with, diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 3bac904a72..74840f0500 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -1268,7 +1268,7 @@ class ModelStorage(Model): gettext('ir.msg_size_validation_record', **error_args)) - def digits_test(value, digits, field_name): + def digits_test(record, digits, field_name): def raise_error(value): error_args = cls.__names__(field_name) error_args['digits'] = digits[1] @@ -1276,6 +1276,14 @@ class ModelStorage(Model): raise DigitsValidationError( gettext('ir.msg_digits_validation_record', **error_args)) + + value = getattr(record, field_name) + if isinstance(digits, str): + digit_record = getattr(record, digits) + if digit_record: + digits = digit_record.get_digits() + else: + digits = None if (value is None or not digits or any(d is None for d in digits)): @@ -1294,12 +1302,10 @@ class ModelStorage(Model): for record in records: digits = _record_eval_pyson( record, pyson_digits, encoded=True) - digits_test(getattr(record, field_name), digits, - field_name) + digits_test(record, digits, field_name) else: for record in records: - digits_test(getattr(record, field_name), - field.digits, field_name) + digits_test(record, field.digits, field_name) if hasattr(field, 'forbidden_chars'): for record in records: diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py index 864db2918f..1d645a5846 100644 --- a/trytond/tests/test_tryton.py +++ b/trytond/tests/test_tryton.py @@ -338,7 +338,10 @@ class ModuleTestCase(unittest.TestCase): fields = set() fields |= get_eval_fields(field.domain) if hasattr(field, 'digits'): - fields |= get_eval_fields(field.digits) + if isinstance(field.digits, str): + fields.add(field.digits) + else: + fields |= get_eval_fields(field.digits) if hasattr(field, 'add_remove'): fields |= get_eval_fields(field.add_remove) if hasattr(field, 'size'):