[IMP] fields: turn field.digits and column.digits into dynamic properties

The computed value of parameter digits is no longer stored into fields and
columns; instead the value is recomputed everytime it is needed.  Note that
performance is not an issue, since the method `get_precision` of model
'decimal.precision' is cached by the orm.  This simplifies the management of
digits on fields and saves about 300Kb per registry.
This commit is contained in:
Raphael Collet 2015-03-03 14:25:10 +01:00
parent bf703fd9a3
commit 9aad3d873b
5 changed files with 62 additions and 51 deletions

View File

@ -48,11 +48,6 @@ class decimal_precision(orm.Model):
def clear_cache(self, cr):
"""clear cache and update models. Notify other workers to restart their registry."""
self.precision_get.clear_cache(self)
env = openerp.api.Environment(cr, SUPERUSER_ID, {})
for model in self.pool.values():
for field in model._fields.values():
if field.type == 'float':
field._setup_digits(env)
RegistryManager.signal_registry_change(cr.dbname)
def create(self, cr, uid, data, context=None):

View File

@ -56,11 +56,15 @@ del time
# The hard-coded super-user id (a.k.a. administrator, or root user).
SUPERUSER_ID = 1
def registry(database_name):
def registry(database_name=None):
"""
Return the model registry for the given database. If the registry does not
exist yet, it is created on the fly.
Return the model registry for the given database, or the database mentioned
on the current thread. If the registry does not exist yet, it is created on
the fly.
"""
if database_name is None:
import threading
database_name = threading.currentThread().dbname
return modules.registry.RegistryManager.get(database_name)
#----------------------------------------------------------

View File

@ -991,26 +991,28 @@ class Float(Field):
"""
type = 'float'
_digits = None # digits argument passed to class initializer
digits = None # digits as computed by setup()
group_operator = None # operator for aggregating values
def __init__(self, string=None, digits=None, **kwargs):
super(Float, self).__init__(string=string, _digits=digits, **kwargs)
@property
def digits(self):
if callable(self._digits):
with registry().cursor() as cr:
return self._digits(cr)
else:
return self._digits
def _setup_digits(self, env):
""" Setup the digits for `self` and its corresponding column """
self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
if self.digits:
assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
"Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
if self.column:
self.column.digits_change(env.cr)
pass
def _setup_regular(self, env):
super(Float, self)._setup_regular(env)
self._setup_digits(env)
_related_digits = property(attrgetter('digits'))
_related__digits = property(attrgetter('_digits'))
_related_group_operator = property(attrgetter('group_operator'))
_description_digits = property(attrgetter('digits'))
@ -1021,10 +1023,9 @@ class Float(Field):
def convert_to_cache(self, value, record, validate=True):
# apply rounding here, otherwise value in cache may be wrong!
if self.digits:
return float_round(float(value or 0.0), precision_digits=self.digits[1])
else:
return float(value or 0.0)
value = float(value or 0.0)
digits = self.digits
return float_round(value, precision_digits=digits[1]) if digits else value
class _String(Field):
@ -1790,7 +1791,7 @@ class Id(Field):
raise TypeError("field 'id' cannot be assigned")
# imported here to avoid dependency cycle issues
from openerp import SUPERUSER_ID
from openerp import SUPERUSER_ID, registry
from .exceptions import Warning, AccessError, MissingError
from .models import BaseModel, MAGIC_COLUMNS
from .osv import fields

View File

@ -659,8 +659,6 @@ class BaseModel(object):
# process store of low-level function fields
for fname, column in cls._columns.iteritems():
if hasattr(column, 'digits_change'):
column.digits_change(cr)
# filter out existing store about this field
pool._store_function[cls._name] = [
stored

View File

@ -51,7 +51,7 @@ from openerp.tools.translate import _
from openerp.tools import float_round, float_repr
from openerp.tools import html_sanitize
import simplejson
from openerp import SUPERUSER_ID
from openerp import SUPERUSER_ID, registry
_logger = logging.getLogger(__name__)
@ -334,36 +334,42 @@ class html(text):
import __builtin__
def _symbol_set_float(self, x):
result = __builtin__.float(x or 0.0)
digits = self.digits
if digits:
precision, scale = digits
result = float_repr(float_round(result, precision_digits=scale), precision_digits=scale)
return result
class float(_column):
_type = 'float'
_symbol_c = '%s'
_symbol_f = lambda x: __builtin__.float(x or 0.0)
_symbol_set = (_symbol_c, _symbol_f)
_symbol_get = lambda self,x: x or 0.0
@property
def digits(self):
if self._digits_compute:
with registry().cursor() as cr:
return self._digits_compute(cr)
else:
return self._digits
def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
_column.__init__(self, string=string, required=required, **args)
self.digits = digits
# synopsis: digits_compute(cr) -> (precision, scale)
self.digits_compute = digits_compute
def new(self, _computed_field=False, **args):
# float columns are database-dependent, so always recreate them
return type(self)(**args)
self._digits = digits
self._digits_compute = digits_compute
self._symbol_f = lambda x: _symbol_set_float(self, x)
self._symbol_set = (self._symbol_c, self._symbol_f)
def to_field_args(self):
args = super(float, self).to_field_args()
args['digits'] = self.digits_compute or self.digits
args['digits'] = self._digits_compute or self._digits
return args
def digits_change(self, cr):
if self.digits_compute:
self.digits = self.digits_compute(cr)
if self.digits:
precision, scale = self.digits
self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
precision_digits=scale),
precision_digits=scale))
pass
class date(_column):
_type = 'date'
@ -1240,10 +1246,22 @@ class function(_column):
# function fields are not copied by default
copy = False
@property
def digits(self):
if self._digits_compute:
with registry().cursor() as cr:
return self._digits_compute(cr)
else:
return self._digits
#
# multi: compute several fields in one call
#
def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, store=False, multi=False, **args):
# pop attributes that should not be assigned to self
self._digits = args.pop('digits', (16,2))
self._digits_compute = args.pop('digits_compute', None)
_column.__init__(self, **args)
self._obj = obj
self._fnct = fnct
@ -1253,8 +1271,6 @@ class function(_column):
if 'relation' in args:
self._obj = args['relation']
self.digits = args.get('digits', (16,2))
self.digits_compute = args.get('digits_compute', None)
if callable(args.get('selection')):
from openerp import api
self.selection = api.expected(api.cr_uid_context, args['selection'])
@ -1283,6 +1299,10 @@ class function(_column):
self._symbol_c = char._symbol_c
self._symbol_f = lambda x: _symbol_set_char(self, x)
self._symbol_set = (self._symbol_c, self._symbol_f)
elif type == 'float':
self._symbol_c = float._symbol_c
self._symbol_f = lambda x: _symbol_set_float(self, x)
self._symbol_set = (self._symbol_c, self._symbol_f)
else:
type_class = globals().get(type)
if type_class is not None:
@ -1304,7 +1324,7 @@ class function(_column):
args = super(function, self).to_field_args()
args['store'] = bool(self.store)
if self._type in ('float',):
args['digits'] = self.digits_compute or self.digits
args['digits'] = self._digits_compute or self._digits
elif self._type in ('selection', 'reference'):
args['selection'] = self.selection
elif self._type in ('many2one', 'one2many', 'many2many'):
@ -1312,14 +1332,7 @@ class function(_column):
return args
def digits_change(self, cr):
if self._type == 'float':
if self.digits_compute:
self.digits = self.digits_compute(cr)
if self.digits:
precision, scale = self.digits
self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
precision_digits=scale),
precision_digits=scale))
pass
def search(self, cr, uid, obj, name, args, context=None):
if not self._fnct_search: