Add digits mixin

issue10596
review338511002
This commit is contained in:
Cédric Krier
2021-08-21 09:05:34 +02:00
parent 9bf9b3a910
commit fb9238b8bf
8 changed files with 84 additions and 14 deletions
+1
View File
@@ -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
+2
View File
@@ -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
-------
+15
View File
@@ -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
================
+2 -1
View File
@@ -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']
+34
View File
@@ -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()
+15 -7
View File
@@ -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,
+11 -5
View File
@@ -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:
+4 -1
View File
@@ -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'):