[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): def clear_cache(self, cr):
"""clear cache and update models. Notify other workers to restart their registry.""" """clear cache and update models. Notify other workers to restart their registry."""
self.precision_get.clear_cache(self) 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) RegistryManager.signal_registry_change(cr.dbname)
def create(self, cr, uid, data, context=None): 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). # The hard-coded super-user id (a.k.a. administrator, or root user).
SUPERUSER_ID = 1 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 Return the model registry for the given database, or the database mentioned
exist yet, it is created on the fly. 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) return modules.registry.RegistryManager.get(database_name)
#---------------------------------------------------------- #----------------------------------------------------------

View File

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

View File

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

View File

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