[REM] openerp.osv.fields: remove old-style function fields
This commit is contained in:
@@ -161,9 +161,8 @@ class Partner(models.Model):
|
||||
|
||||
@api.model
|
||||
def _cron_update_membership(self):
|
||||
partners = self.search([('membership_state', 'in', ['invoiced', 'paid'])])
|
||||
if partners:
|
||||
partners._store_set_values(['membership_state'])
|
||||
# used to recompute 'membership_state'; should no longer be necessary
|
||||
pass
|
||||
|
||||
@api.multi
|
||||
def create_membership_invoice(self, product_id=None, datas=None):
|
||||
|
||||
@@ -1105,13 +1105,6 @@ class Environment(Mapping):
|
||||
finally:
|
||||
self.all.recompute = tmp
|
||||
|
||||
@property
|
||||
def recompute_old(self):
|
||||
return self.all.recompute_old
|
||||
|
||||
def clear_recompute_old(self):
|
||||
del self.all.recompute_old[:]
|
||||
|
||||
|
||||
class Environments(object):
|
||||
""" A common object for all environments in a request. """
|
||||
@@ -1120,7 +1113,6 @@ class Environments(object):
|
||||
self.todo = {} # recomputations {field: [records]}
|
||||
self.mode = False # flag for draft/onchange
|
||||
self.recompute = True
|
||||
self.recompute_old = [] # list of old api compute fields to recompute
|
||||
|
||||
def add(self, env):
|
||||
""" Add the environment ``env``. """
|
||||
|
||||
@@ -447,10 +447,6 @@ class Field(object):
|
||||
if not attrs.get('readonly'):
|
||||
attrs['inverse'] = self._inverse_sparse
|
||||
|
||||
# fix for function fields overridden by regular columns
|
||||
if not isinstance(attrs.get('origin'), (NoneType, fields.function)):
|
||||
attrs.pop('store', None)
|
||||
|
||||
self.set_all_attrs(attrs)
|
||||
|
||||
if not self.string and not self.related:
|
||||
|
||||
+12
-341
@@ -201,11 +201,6 @@ def get_pg_type(f, type_override=None):
|
||||
pg_type = ('int4', 'INTEGER')
|
||||
else:
|
||||
pg_type = ('varchar', pg_varchar(getattr(f, 'size', None)))
|
||||
elif issubclass(field_type, fields.function):
|
||||
if f._type == 'selection':
|
||||
pg_type = ('varchar', pg_varchar())
|
||||
else:
|
||||
pg_type = get_pg_type(f, getattr(fields, f._type))
|
||||
else:
|
||||
_logger.warning('%s type not supported!', field_type)
|
||||
pg_type = None
|
||||
@@ -700,44 +695,6 @@ class BaseModel(object):
|
||||
child_class = type(pool[child_name])
|
||||
child_class._build_model_attributes(pool)
|
||||
|
||||
@classmethod
|
||||
def _init_function_fields(cls, pool, cr):
|
||||
# initialize the list of non-stored function fields for this model
|
||||
pool._pure_function_fields[cls._name] = []
|
||||
|
||||
# process store of low-level function fields
|
||||
for fname, column in cls._columns.iteritems():
|
||||
# filter out existing store about this field
|
||||
pool._store_function[cls._name] = [
|
||||
stored
|
||||
for stored in pool._store_function.get(cls._name, [])
|
||||
if (stored[0], stored[1]) != (cls._name, fname)
|
||||
]
|
||||
if not isinstance(column, fields.function):
|
||||
continue
|
||||
if not column.store:
|
||||
# register it on the pool for invalidation
|
||||
pool._pure_function_fields[cls._name].append(fname)
|
||||
continue
|
||||
# process store parameter
|
||||
store = column.store
|
||||
if store is True:
|
||||
get_ids = lambda self, cr, uid, ids, c={}: ids
|
||||
store = {cls._name: (get_ids, None, column.priority, None)}
|
||||
for model, spec in store.iteritems():
|
||||
if len(spec) == 4:
|
||||
(fnct, fields2, order, length) = spec
|
||||
elif len(spec) == 3:
|
||||
(fnct, fields2, order) = spec
|
||||
length = None
|
||||
else:
|
||||
raise UserError(_('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.') % (fname, cls._name))
|
||||
pool._store_function.setdefault(model, [])
|
||||
t = (cls._name, fname, fnct, tuple(fields2) if fields2 else None, order, length)
|
||||
if t not in pool._store_function[model]:
|
||||
pool._store_function[model].append(t)
|
||||
pool._store_function[model].sort(key=lambda x: x[4])
|
||||
|
||||
@api.model
|
||||
def _add_manual_fields(self, partial):
|
||||
manual_fields = self.pool.get_manual_fields(self._cr, self._name)
|
||||
@@ -2258,25 +2215,6 @@ class BaseModel(object):
|
||||
self.invalidate_cache(['parent_left', 'parent_right'])
|
||||
return True
|
||||
|
||||
@api.model_cr
|
||||
def _update_store(self, column, name):
|
||||
_logger.info("storing computed values of fields.function '%s'", name)
|
||||
cr = self._cr
|
||||
setc, setf = column._symbol_set
|
||||
update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, name, setc)
|
||||
cr.execute('SELECT id FROM "%s"' % self._table)
|
||||
ids = [row[0] for row in cr.fetchall()]
|
||||
for sub_ids in cr.split_for_in_conditions(ids, AUTOINIT_RECALCULATE_STORED_FIELDS):
|
||||
res = column.get(cr, self._model, sub_ids, name, SUPERUSER_ID, {})
|
||||
for id, val in res.iteritems():
|
||||
if column._multi:
|
||||
val = val[name]
|
||||
# if val is a many2one, just write the ID
|
||||
if isinstance(val, tuple):
|
||||
val = val[0]
|
||||
if column._type == 'boolean' or val is not False:
|
||||
cr.execute(update_query, (setf(val), id))
|
||||
|
||||
@api.model
|
||||
def _check_selection_field_value(self, field, value):
|
||||
""" Check whether value is among the valid values for the given
|
||||
@@ -2291,9 +2229,7 @@ class BaseModel(object):
|
||||
# fields which were required but have been removed (or will be added by
|
||||
# another module)
|
||||
cr = self._cr
|
||||
cols = [name
|
||||
for name, column in self._columns.iteritems()
|
||||
if not (isinstance(column, fields.function) and not column.store)]
|
||||
cols = list(self._columns)
|
||||
cr.execute("SELECT a.attname, a.attnotnull"
|
||||
" FROM pg_class c, pg_attribute a"
|
||||
" WHERE c.relname=%s"
|
||||
@@ -2551,16 +2487,7 @@ class BaseModel(object):
|
||||
f_pg_type = res['typname']
|
||||
f_pg_size = res['size']
|
||||
f_pg_notnull = res['attnotnull']
|
||||
if isinstance(column, fields.function) and not column.store and \
|
||||
not getattr(column, 'nodrop', False):
|
||||
_logger.info('column %s (%s) converted to a function, removed from table %s',
|
||||
name, column.string, self._table)
|
||||
cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, name))
|
||||
cr.commit()
|
||||
_schema.debug("Table '%s': dropped column '%s' with cascade", self._table, name)
|
||||
f_obj_type = None
|
||||
else:
|
||||
f_obj_type = get_pg_type(column) and get_pg_type(column)[0]
|
||||
f_obj_type = get_pg_type(column) and get_pg_type(column)[0]
|
||||
|
||||
if f_obj_type:
|
||||
converted = False
|
||||
@@ -2667,7 +2594,7 @@ class BaseModel(object):
|
||||
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
|
||||
_schema.debug(msg, self._table, name, column._type)
|
||||
|
||||
if isinstance(column, fields.many2one) or (isinstance(column, fields.function) and column._type == 'many2one' and column.store):
|
||||
if isinstance(column, fields.many2one):
|
||||
dest_model = self.env[column._obj]
|
||||
if dest_model._auto and dest_model._table != 'ir_actions':
|
||||
self._m2o_fix_foreign_key(self._table, name, dest_model, column.ondelete)
|
||||
@@ -2683,13 +2610,6 @@ class BaseModel(object):
|
||||
if has_rows:
|
||||
self._init_column(name)
|
||||
|
||||
# remember the functions to call for the stored fields
|
||||
if isinstance(column, fields.function):
|
||||
order = 10
|
||||
if column.store is not True: # i.e. if column.store is a dict
|
||||
order = column.store[column.store.keys()[0]][2]
|
||||
todo_end.append((order, self._update_store, (column, name)))
|
||||
|
||||
# remember new-style stored fields with compute method
|
||||
if self._fields[name].compute:
|
||||
stored_fields.append(self._fields[name])
|
||||
@@ -3102,9 +3022,6 @@ class BaseModel(object):
|
||||
with tools.ignore(*exceptions):
|
||||
field.setup_triggers(self)
|
||||
|
||||
# register stuff about low-level function fields
|
||||
cls._init_function_fields(cls.pool, self._cr)
|
||||
|
||||
# register constraints and onchange methods
|
||||
cls._init_constraints_onchanges()
|
||||
|
||||
@@ -3268,17 +3185,6 @@ class BaseModel(object):
|
||||
# discard fields that must be recomputed
|
||||
if not (f.compute and self.env.field_todo(f))
|
||||
)
|
||||
elif field.column._multi:
|
||||
# prefetch all function fields with the same value for 'multi'
|
||||
multi = field.column._multi
|
||||
fs.update(
|
||||
f
|
||||
for f in self._fields.itervalues()
|
||||
# select stored fields with the same multi
|
||||
if f.column and f.column._multi == multi
|
||||
# discard fields with groups that the user may not access
|
||||
if not (f.groups and not self.user_has_groups(f.groups))
|
||||
)
|
||||
|
||||
# special case: discard records to recompute for field
|
||||
records -= self.env.field_todo(field)
|
||||
@@ -3400,33 +3306,13 @@ class BaseModel(object):
|
||||
# determine the fields that must be processed now;
|
||||
# for the sake of simplicity, we ignore inherited fields
|
||||
fields_post = [f for f in field_names if not self._columns[f]._classic_write]
|
||||
|
||||
# Compute POST fields, grouped by multi
|
||||
by_multi = defaultdict(list)
|
||||
for f in fields_post:
|
||||
by_multi[self._columns[f]._multi].append(f)
|
||||
|
||||
for multi, fs in by_multi.iteritems():
|
||||
if multi:
|
||||
res2 = self._columns[fs[0]].get(cr, self._model, ids, fs, user, context=context, values=result)
|
||||
assert res2 is not None, \
|
||||
'The function field "%s" on the "%s" model returned None\n' \
|
||||
'(a dictionary was expected).' % (fs[0], self._name)
|
||||
for vals in result:
|
||||
# TOCHECK : why got string instend of dict in python2.6
|
||||
# if isinstance(res2[vals['id']], str): res2[vals['id']] = safe_eval(res2[vals['id']])
|
||||
multi_fields = res2.get(vals['id'], {})
|
||||
if multi_fields:
|
||||
for f in fs:
|
||||
vals[f] = multi_fields.get(f, [])
|
||||
else:
|
||||
for f in fs:
|
||||
res2 = self._columns[f].get(cr, self._model, ids, f, user, context=context, values=result)
|
||||
for vals in result:
|
||||
if res2:
|
||||
vals[f] = res2[vals['id']]
|
||||
else:
|
||||
vals[f] = []
|
||||
res2 = self._columns[f].get(cr, self._model, ids, f, user, context=context, values=result)
|
||||
for vals in result:
|
||||
if res2:
|
||||
vals[f] = res2[vals['id']]
|
||||
else:
|
||||
vals[f] = []
|
||||
|
||||
# Warn about deprecated fields now that fields_pre and fields_post are computed
|
||||
for f in field_names:
|
||||
@@ -3650,8 +3536,7 @@ class BaseModel(object):
|
||||
if not self:
|
||||
return True
|
||||
|
||||
# for recomputing old-style and new-style fields
|
||||
result_store = self._store_get_values(self._fields)
|
||||
# for recomputing fields
|
||||
self.modified(self._fields)
|
||||
|
||||
self._check_concurrency()
|
||||
@@ -3712,16 +3597,6 @@ class BaseModel(object):
|
||||
# changes made in the database, like cascading delete!
|
||||
self.invalidate_cache()
|
||||
|
||||
# recompute old-style fields
|
||||
for order, model_name, ids, fields in result_store:
|
||||
ids = set(ids) - set(self.ids) if model_name == self._name else ids
|
||||
if ids:
|
||||
model = self.env[model_name]
|
||||
query = "SELECT id FROM %s WHERE id IN %%s" % model._table
|
||||
cr.execute(query, (tuple(ids),))
|
||||
recs = model.browse([row[0] for row in cr.fetchall()])
|
||||
recs._store_set_values(fields)
|
||||
|
||||
# recompute new-style fields
|
||||
if self.env.recompute and self._context.get('recompute', True):
|
||||
self.recompute()
|
||||
@@ -3861,16 +3736,6 @@ class BaseModel(object):
|
||||
|
||||
cr = self._cr
|
||||
|
||||
# for recomputing old-style fields
|
||||
deleted_related = defaultdict(list) # {model_name: ids}
|
||||
for name, val in vals.iteritems():
|
||||
field = self._fields[name]
|
||||
if field.type in ('one2many', 'many2many') and val:
|
||||
for command in val:
|
||||
if isinstance(command, (tuple, list)) and command[0] == 2:
|
||||
deleted_related[field.comodel_name].append(command[1])
|
||||
result_store = self._store_get_values(list(vals))
|
||||
|
||||
# for recomputing new-style fields
|
||||
extra_fields = ['write_date', 'write_uid'] if self._log_access else []
|
||||
self.modified(list(vals) + extra_fields)
|
||||
@@ -3968,6 +3833,7 @@ class BaseModel(object):
|
||||
if not key.startswith('default_')}
|
||||
|
||||
# call the 'set' method of fields which are not classic_write
|
||||
result_store = []
|
||||
for name in sorted(upd_todo, key=lambda name: self._columns[name].priority):
|
||||
column = self._columns[name]
|
||||
for id in self.ids:
|
||||
@@ -4060,23 +3926,6 @@ class BaseModel(object):
|
||||
|
||||
self.invalidate_cache(['parent_left', 'parent_right'])
|
||||
|
||||
result_store += self._store_get_values(vals)
|
||||
|
||||
done = defaultdict(set) # {(model, fields): ids}
|
||||
self.env.recompute_old.extend(result_store)
|
||||
while self.env.recompute_old:
|
||||
sorted_recompute_old = sorted(self.env.recompute_old)
|
||||
self.env.clear_recompute_old()
|
||||
for order, model_name, ids, fnames in sorted_recompute_old:
|
||||
key = (model_name, tuple(fnames))
|
||||
# avoid to do several times the same computation
|
||||
ids = [id for id in ids
|
||||
if id not in done[key]
|
||||
if id not in deleted_related[model_name]]
|
||||
done[key].update(ids)
|
||||
recs = self.env[model_name].browse(ids)
|
||||
recs._store_set_values(fnames)
|
||||
|
||||
# recompute new-style fields
|
||||
if self.env.recompute and self._context.get('recompute', True):
|
||||
self.recompute()
|
||||
@@ -4202,27 +4051,7 @@ class BaseModel(object):
|
||||
if column._classic_write:
|
||||
setc, setf = column._symbol_set
|
||||
updates.append((name, setc, setf(val)))
|
||||
|
||||
# for the function fields that receive a value, we set them
|
||||
# directly in the database (they may be required), but we also
|
||||
# need to trigger the _fct_inv()
|
||||
if (hasattr(column, '_fnct_inv')) and not isinstance(column, fields.related):
|
||||
# TODO: this way to special case the related fields is
|
||||
# really creepy but it shouldn't be changed at one week of
|
||||
# the release candidate. It seems the only good way to
|
||||
# handle correctly this is to add an attribute to make a
|
||||
# field `really readonly´ and thus totally ignored by the
|
||||
# create()... otherwise if, for example, the related has a
|
||||
# default value (for usability) then the fct_inv is called
|
||||
# and it may raise some access rights error. Changing this
|
||||
# is a too big change for now, and is thus postponed after
|
||||
# the release but, definitively, the behavior shouldn't be
|
||||
# different for related and function fields.
|
||||
upd_todo.append(name)
|
||||
elif not isinstance(column, fields.related):
|
||||
# TODO: this special case should be removed because there is no
|
||||
# good reason to special case the fields related. See the above
|
||||
# comment for further explanations.
|
||||
else:
|
||||
upd_todo.append(name)
|
||||
|
||||
if hasattr(column, 'selection') and val:
|
||||
@@ -4313,20 +4142,7 @@ class BaseModel(object):
|
||||
# check Python constraints
|
||||
self._validate_fields(vals)
|
||||
|
||||
result_store += self._store_get_values(list(set(vals.keys() + self._inherits.values())))
|
||||
self.env.recompute_old.extend(result_store)
|
||||
|
||||
if self.env.recompute and self._context.get('recompute', True):
|
||||
done = []
|
||||
while self.env.recompute_old:
|
||||
sorted_recompute_old = sorted(self.env.recompute_old)
|
||||
self.env.clear_recompute_old()
|
||||
for order, model_name, ids, fnames in sorted_recompute_old:
|
||||
if (model_name, ids, fnames) not in done:
|
||||
recs = self.env[model_name].browse(ids)
|
||||
recs._store_set_values(fnames)
|
||||
done.append((model_name, ids, fnames))
|
||||
|
||||
# recompute new-style fields
|
||||
self.recompute()
|
||||
|
||||
@@ -4334,142 +4150,6 @@ class BaseModel(object):
|
||||
self.create_workflow()
|
||||
return id_new
|
||||
|
||||
@api.multi
|
||||
def _store_get_values(self, fields):
|
||||
""" Returns an ordered list of fields.function to call due to an update
|
||||
operation on ``fields`` of records ``self``, obtained by calling the
|
||||
'store' triggers of these fields, as setup by their 'store' attribute.
|
||||
|
||||
:return: [(priority, model_name, [record_ids,], [function_fields,])]
|
||||
"""
|
||||
fields = fields or []
|
||||
|
||||
# use indexed names for the details of the triggers:
|
||||
MODEL, FUNC, TARGET_FUNC, FIELDS, PRIORITY = range(5)
|
||||
|
||||
# only keep store triggers that should be triggered for the ``fields``
|
||||
# being written to.
|
||||
triggers = [
|
||||
trigger
|
||||
for trigger in self.pool._store_function.get(self._name, [])
|
||||
if not trigger[FIELDS] or set(fields).intersection(trigger[FIELDS])
|
||||
]
|
||||
|
||||
to_compute_map = defaultdict(lambda: defaultdict(set))
|
||||
target_id_results = {}
|
||||
for trigger in triggers:
|
||||
target_func_id = id(trigger[TARGET_FUNC])
|
||||
if target_func_id not in target_id_results:
|
||||
# use admin user for accessing objects having rules defined on store fields
|
||||
ids = trigger[TARGET_FUNC](self._model, self._cr, SUPERUSER_ID, self.ids, self._context)
|
||||
target_id_results[target_func_id] = filter(None, ids)
|
||||
target_ids = target_id_results[target_func_id]
|
||||
|
||||
# the compound key must consider the priority and model name
|
||||
key = (trigger[PRIORITY], trigger[MODEL])
|
||||
for target_id in target_ids:
|
||||
to_compute_map[key][target_id].add(tuple(trigger))
|
||||
|
||||
# Here to_compute_map looks like:
|
||||
# { (10, 'model_a') : { target_id1: [ (trigger_1_tuple, trigger_2_tuple) ], ... }
|
||||
# (20, 'model_a') : { target_id2: [ (trigger_3_tuple, trigger_4_tuple) ], ... }
|
||||
# (99, 'model_a') : { target_id1: [ (trigger_5_tuple, trigger_6_tuple) ], ... }
|
||||
# }
|
||||
|
||||
# Now we need to generate the batch function calls list
|
||||
# call_map =
|
||||
# { (10, 'model_a') : [(10, 'model_a', [record_ids,], [function_fields,])] }
|
||||
call_map = defaultdict(list)
|
||||
for ((priority, model), id_map) in to_compute_map.iteritems():
|
||||
trigger_ids_maps = defaultdict(list)
|
||||
# function_ids_maps =
|
||||
# { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] }
|
||||
for target_id, triggers in id_map.iteritems():
|
||||
trigger_ids_maps[tuple(triggers)].append(target_id)
|
||||
for triggers, target_ids in trigger_ids_maps.iteritems():
|
||||
call_map[(priority, model)].append((priority, model, target_ids, [t[FUNC] for t in triggers]))
|
||||
|
||||
return list(itertools.chain.from_iterable(v for k, v in sorted(call_map.iteritems())))
|
||||
|
||||
@api.multi
|
||||
def _store_set_values(self, fields):
|
||||
""" Call the fields.function's "implementation function" for all
|
||||
``fields``, on records ``self`` (taking care of respecting ``multi``
|
||||
attributes), and store the resulting values in the database directly.
|
||||
"""
|
||||
if not self:
|
||||
return True
|
||||
|
||||
from .fields import Datetime
|
||||
cr = self._cr
|
||||
field_dict = defaultdict(list)
|
||||
if self._log_access:
|
||||
cr.execute('SELECT id, write_date FROM %s WHERE id IN %%s' % self._table, [tuple(self.ids)])
|
||||
for id, write_date in cr.fetchall():
|
||||
if write_date:
|
||||
write_date = Datetime.from_string(write_date)
|
||||
for trigger in self.pool._store_function.get(self._name, []):
|
||||
if trigger[5]:
|
||||
up_write_date = write_date + datetime.timedelta(hours=trigger[5])
|
||||
if datetime.datetime.now() < up_write_date:
|
||||
if trigger[1] in fields:
|
||||
field_dict[id].append(trigger[1])
|
||||
|
||||
todo = defaultdict(list)
|
||||
for name in fields:
|
||||
todo[self._columns[name]._multi].append(name)
|
||||
|
||||
for key, names in todo.iteritems():
|
||||
if key:
|
||||
column = self._columns[names[0]]
|
||||
# use admin user for accessing objects having rules defined on store fields
|
||||
result = column.get(cr, self._model, self.ids, names, SUPERUSER_ID, context=self._context)
|
||||
for id, vals in result.iteritems():
|
||||
if field_dict:
|
||||
for name in field_dict[id]:
|
||||
vals.pop(name, None)
|
||||
updates = [] # list of (column, pattern, value)
|
||||
for name, val in vals.iteritems():
|
||||
if name not in names:
|
||||
continue
|
||||
column = self._columns[name]
|
||||
if column._type == 'many2one':
|
||||
try:
|
||||
val = val[0]
|
||||
except:
|
||||
pass
|
||||
setc, setf = column._symbol_set
|
||||
updates.append((name, setc, setf(val)))
|
||||
if updates:
|
||||
query = 'UPDATE "%s" SET %s WHERE id=%%s' % (
|
||||
self._table, ','.join('"%s"=%s' % u[:2] for u in updates),
|
||||
)
|
||||
params = tuple(u[2] for u in updates)
|
||||
cr.execute(query, params + (id,))
|
||||
|
||||
else:
|
||||
for name in names:
|
||||
column = self._columns[name]
|
||||
# use admin user for accessing objects having rules defined on store fields
|
||||
result = column.get(cr, self._model, self.ids, name, SUPERUSER_ID, context=self._context)
|
||||
for rid in result.keys():
|
||||
if rid in field_dict and name in field_dict[rid]:
|
||||
result.pop(rid)
|
||||
for id, val in result.iteritems():
|
||||
if column._type == 'many2one':
|
||||
try:
|
||||
val = val[0]
|
||||
except:
|
||||
pass
|
||||
setc, setf = column._symbol_set
|
||||
query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, name, setc)
|
||||
cr.execute(query, (setf(val), id))
|
||||
|
||||
# invalidate and mark new-style fields to recompute
|
||||
self.modified(fields)
|
||||
|
||||
return True
|
||||
|
||||
# TODO: ameliorer avec NULL
|
||||
@api.model
|
||||
def _where_calc(self, domain, active_test=True):
|
||||
@@ -5754,15 +5434,6 @@ class BaseModel(object):
|
||||
for fname in fnames:
|
||||
spec += self._fields[fname].modified(self)
|
||||
|
||||
cached_fields = {
|
||||
field
|
||||
for env in self.env.all
|
||||
for field in env.cache
|
||||
}
|
||||
# invalidate non-stored fields.function which are currently cached
|
||||
spec += [(f, None) for f in self.pool.pure_function_fields
|
||||
if f in cached_fields]
|
||||
|
||||
self.env.invalidate(spec)
|
||||
|
||||
def _recompute_check(self, field):
|
||||
|
||||
@@ -29,8 +29,6 @@ class Registry(Mapping):
|
||||
super(Registry, self).__init__()
|
||||
self.models = {} # model name/model instance mapping
|
||||
self._sql_error = {}
|
||||
self._store_function = {}
|
||||
self._pure_function_fields = {} # {model: [field, ...], ...}
|
||||
self._init = True
|
||||
self._init_parent = {}
|
||||
self._assertion_report = assertion_report.assertion_report()
|
||||
@@ -92,16 +90,6 @@ class Registry(Mapping):
|
||||
def model_cache(self):
|
||||
return RegistryManager.model_cache
|
||||
|
||||
@lazy_property
|
||||
def pure_function_fields(self):
|
||||
""" Return the list of pure function fields (field objects) """
|
||||
fields = []
|
||||
for mname, fnames in self._pure_function_fields.iteritems():
|
||||
model_fields = self[mname]._fields
|
||||
for fname in fnames:
|
||||
fields.append(model_fields[fname])
|
||||
return fields
|
||||
|
||||
@lazy_property
|
||||
def field_sequence(self):
|
||||
""" Return a function mapping a field to an integer. The value of a
|
||||
|
||||
@@ -908,38 +908,6 @@ class expression(object):
|
||||
for elem in reversed(domain):
|
||||
push(create_substitution_leaf(leaf, elem, model, internal=True))
|
||||
|
||||
# -------------------------------------------------
|
||||
# FUNCTION FIELD
|
||||
# -> not stored: error if no _fnct_search, otherwise handle the result domain
|
||||
# -> stored: management done in the remaining of parsing
|
||||
# -------------------------------------------------
|
||||
|
||||
elif isinstance(column, fields.function) and not column.store:
|
||||
# this is a function field that is not stored
|
||||
if not column._fnct_search:
|
||||
_logger.error(
|
||||
"Field '%s' (%s) can not be searched: "
|
||||
"non-stored function field without fnct_search",
|
||||
column.string, left)
|
||||
# avoid compiling stack trace if not needed
|
||||
if _logger.isEnabledFor(logging.DEBUG):
|
||||
_logger.debug(''.join(traceback.format_stack()))
|
||||
# ignore it: generate a dummy leaf
|
||||
fct_domain = []
|
||||
else:
|
||||
fct_domain = column.search(cr, uid, model._model, left, [leaf.leaf], context=context)
|
||||
|
||||
if not fct_domain:
|
||||
leaf.leaf = TRUE_LEAF
|
||||
push(leaf)
|
||||
else:
|
||||
# we assume that the expression is valid
|
||||
# we create a dummy leaf for forcing the parsing of the resulting expression
|
||||
for domain_element in reversed(fct_domain):
|
||||
push(create_substitution_leaf(leaf, domain_element, model))
|
||||
# self.push(create_substitution_leaf(leaf, TRUE_LEAF, model))
|
||||
# self.push(create_substitution_leaf(leaf, AND_OPERATOR, model))
|
||||
|
||||
# -------------------------------------------------
|
||||
# RELATIONAL FIELDS
|
||||
# -------------------------------------------------
|
||||
|
||||
@@ -848,7 +848,6 @@ class one2many(_column):
|
||||
if act[0] == 0:
|
||||
act[2][self._fields_id] = id
|
||||
id_new = obj.create(cr, user, act[2], context=context)
|
||||
result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
|
||||
elif act[0] == 1:
|
||||
obj.write(cr, user, [act[1]], act[2], context=context)
|
||||
elif act[0] == 2:
|
||||
@@ -1159,500 +1158,6 @@ def sanitize_binary_value(value):
|
||||
return value
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Function fields
|
||||
# ---------------------------------------------------------
|
||||
class function(_column):
|
||||
"""
|
||||
A field whose value is computed by a function (rather
|
||||
than being read from the database).
|
||||
|
||||
:param fnct: the callable that will compute the field value.
|
||||
:param arg: arbitrary value to be passed to ``fnct`` when computing the value.
|
||||
:param fnct_inv: the callable that will allow writing values in that field
|
||||
(if not provided, the field is read-only).
|
||||
:param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
|
||||
writing a value.
|
||||
:param str type: type of the field simulated by the function field
|
||||
:param fnct_search: the callable that allows searching on the field
|
||||
(if not provided, search will not return any result).
|
||||
:param store: store computed value in database
|
||||
(see :ref:`The *store* parameter <field-function-store>`).
|
||||
:type store: True or dict specifying triggers for field computation
|
||||
:param multi: name of batch for batch computation of function fields.
|
||||
All fields with the same batch name will be computed by
|
||||
a single function call. This changes the signature of the
|
||||
``fnct`` callable.
|
||||
|
||||
.. _field-function-fnct: The ``fnct`` parameter
|
||||
|
||||
.. rubric:: The ``fnct`` parameter
|
||||
|
||||
The callable implementing the function field must have the following signature:
|
||||
|
||||
.. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
|
||||
|
||||
Implements the function field.
|
||||
|
||||
:param orm model: model to which the field belongs (should be ``self`` for
|
||||
a model method)
|
||||
:param field_name(s): name of the field to compute, or if ``multi`` is provided,
|
||||
list of field names to compute.
|
||||
:type field_name(s): str | [str]
|
||||
:param arg: arbitrary value passed when declaring the function field
|
||||
:rtype: dict
|
||||
:return: mapping of ``ids`` to computed values, or if multi is provided,
|
||||
to a map of field_names to computed values
|
||||
|
||||
The values in the returned dictionary must be of the type specified by the type
|
||||
argument in the field declaration.
|
||||
|
||||
Here is an example with a simple function ``char`` function field::
|
||||
|
||||
# declarations
|
||||
def compute(self, cr, uid, ids, field_name, arg, context):
|
||||
result = {}
|
||||
# ...
|
||||
return result
|
||||
_columns['my_char'] = fields.function(compute, type='char', size=50)
|
||||
|
||||
# when called with ``ids=[1,2,3]``, ``compute`` could return:
|
||||
{
|
||||
1: 'foo',
|
||||
2: 'bar',
|
||||
3: False # null values should be returned explicitly too
|
||||
}
|
||||
|
||||
If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
|
||||
of the field names that should be computed. Each value in the returned
|
||||
dictionary must then be a dictionary mapping field names to values.
|
||||
|
||||
Here is an example where two function fields (``name`` and ``age``)
|
||||
are both computed by a single function field::
|
||||
|
||||
# declarations
|
||||
def compute(self, cr, uid, ids, field_names, arg, context):
|
||||
result = {}
|
||||
# ...
|
||||
return result
|
||||
_columns['name'] = fields.function(compute_person_data, type='char',\
|
||||
size=50, multi='person_data')
|
||||
_columns[''age'] = fields.function(compute_person_data, type='integer',\
|
||||
multi='person_data')
|
||||
|
||||
# when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
|
||||
{
|
||||
1: {'name': 'Bob', 'age': 23},
|
||||
2: {'name': 'Sally', 'age': 19},
|
||||
3: {'name': 'unknown', 'age': False}
|
||||
}
|
||||
|
||||
.. _field-function-fnct-inv:
|
||||
|
||||
.. rubric:: The ``fnct_inv`` parameter
|
||||
|
||||
This callable implements the write operation for the function field
|
||||
and must have the following signature:
|
||||
|
||||
.. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
|
||||
|
||||
Callable that implements the ``write`` operation for the function field.
|
||||
|
||||
:param orm model: model to which the field belongs (should be ``self`` for
|
||||
a model method)
|
||||
:param int id: the identifier of the object to write on
|
||||
:param str field_name: name of the field to set
|
||||
:param fnct_inv_arg: arbitrary value passed when declaring the function field
|
||||
:return: True
|
||||
|
||||
When writing values for a function field, the ``multi`` parameter is ignored.
|
||||
|
||||
.. _field-function-fnct-search:
|
||||
|
||||
.. rubric:: The ``fnct_search`` parameter
|
||||
|
||||
This callable implements the search operation for the function field
|
||||
and must have the following signature:
|
||||
|
||||
.. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
|
||||
|
||||
Callable that implements the ``search`` operation for the function field by expanding
|
||||
a search criterion based on the function field into a new domain based only on
|
||||
columns that are stored in the database.
|
||||
|
||||
:param orm model: model to which the field belongs (should be ``self`` for
|
||||
a model method)
|
||||
:param orm model_again: same value as ``model`` (seriously! this is for backwards
|
||||
compatibility)
|
||||
:param str field_name: name of the field to search on
|
||||
:param list criterion: domain component specifying the search criterion on the field.
|
||||
:rtype: list
|
||||
:return: domain to use instead of ``criterion`` when performing the search.
|
||||
This new domain must be based only on columns stored in the database, as it
|
||||
will be used directly without any translation.
|
||||
|
||||
The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
|
||||
The most generic way to implement ``fnct_search`` is to directly search for the records that
|
||||
match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
|
||||
``[('id','in',[1,3,5])]``.
|
||||
|
||||
.. _field-function-store:
|
||||
|
||||
.. rubric:: The ``store`` parameter
|
||||
|
||||
The ``store`` parameter allows caching the result of the field computation in the
|
||||
database, and defining the triggers that will invalidate that cache and force a
|
||||
recomputation of the function field.
|
||||
When not provided, the field is computed every time its value is read.
|
||||
The value of ``store`` may be either ``True`` (to recompute the field value whenever
|
||||
any field in the same record is modified), or a dictionary specifying a more
|
||||
flexible set of recomputation triggers.
|
||||
|
||||
A trigger specification is a dictionary that maps the names of the models that
|
||||
will trigger the computation, to a tuple describing the trigger rule, in the
|
||||
following form::
|
||||
|
||||
store = {
|
||||
'trigger_model': (mapping_function,
|
||||
['trigger_field1', 'trigger_field2'],
|
||||
priority),
|
||||
}
|
||||
|
||||
A trigger rule is defined by a 3-item tuple where:
|
||||
|
||||
* The ``mapping_function`` is defined as follows:
|
||||
|
||||
.. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
|
||||
|
||||
Callable that maps record ids of a trigger model to ids of the
|
||||
corresponding records in the source model (whose field values
|
||||
need to be recomputed).
|
||||
|
||||
:param orm model: trigger_model
|
||||
:param list trigger_ids: ids of the records of trigger_model that were
|
||||
modified
|
||||
:rtype: list
|
||||
:return: list of ids of the source model whose function field values
|
||||
need to be recomputed
|
||||
|
||||
* The second item is a list of the fields who should act as triggers for
|
||||
the computation. If an empty list is given, all fields will act as triggers.
|
||||
* The last item is the priority, used to order the triggers when processing them
|
||||
after any write operation on a model that has function field triggers. The
|
||||
default priority is 10.
|
||||
|
||||
In fact, setting store = True is the same as using the following trigger dict::
|
||||
|
||||
store = {
|
||||
'model_itself': (lambda self, cr, uid, ids, context: ids,
|
||||
[],
|
||||
10)
|
||||
}
|
||||
|
||||
"""
|
||||
_properties = True
|
||||
|
||||
__slots__ = [
|
||||
'_type',
|
||||
'_classic_read',
|
||||
'_classic_write',
|
||||
'_symbol_c',
|
||||
'_symbol_f',
|
||||
'_symbol_set',
|
||||
'_symbol_get',
|
||||
|
||||
'_fnct',
|
||||
'_arg',
|
||||
'_fnct_inv',
|
||||
'_fnct_inv_arg',
|
||||
'_fnct_search',
|
||||
'_multi',
|
||||
'store',
|
||||
|
||||
'_digits',
|
||||
'_digits_compute',
|
||||
'selection',
|
||||
'_obj',
|
||||
]
|
||||
|
||||
@property
|
||||
def digits(self):
|
||||
if self._digits_compute:
|
||||
with LazyCursor() 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):
|
||||
self._classic_read = False
|
||||
self._classic_write = False
|
||||
self._prefetch = False
|
||||
self._symbol_c = '%s'
|
||||
self._symbol_f = _symbol_set
|
||||
self._symbol_set = (self._symbol_c, self._symbol_f)
|
||||
self._symbol_get = None
|
||||
|
||||
# pop attributes that should not be assigned to self
|
||||
self._digits = args.pop('digits', (16,2))
|
||||
self._digits_compute = args.pop('digits_compute', None)
|
||||
self._obj = args.pop('relation', obj)
|
||||
|
||||
# function fields are not copied by default
|
||||
args['copy'] = args.get('copy', False)
|
||||
|
||||
_column.__init__(self, **args)
|
||||
|
||||
self._type = type
|
||||
self._fnct = fnct
|
||||
self._arg = arg
|
||||
self._fnct_inv = fnct_inv
|
||||
self._fnct_inv_arg = fnct_inv_arg
|
||||
self._fnct_search = fnct_search
|
||||
self.store = store
|
||||
self._multi = multi
|
||||
|
||||
if not fnct_inv:
|
||||
self.readonly = 1
|
||||
|
||||
if not fnct_search and not store:
|
||||
self.selectable = False
|
||||
|
||||
if callable(args.get('selection')):
|
||||
from openerp import api
|
||||
self.selection = api.expected(api.cr_uid_context, args['selection'])
|
||||
|
||||
if store:
|
||||
if self._type != 'many2one':
|
||||
# m2o fields need to return tuples with name_get, not just foreign keys
|
||||
self._classic_read = True
|
||||
self._classic_write = True
|
||||
if type=='binary':
|
||||
self._symbol_get=lambda x:x and str(x)
|
||||
else:
|
||||
self._prefetch = True
|
||||
|
||||
if type == 'char':
|
||||
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:
|
||||
self._symbol_c = type_class._symbol_c
|
||||
self._symbol_f = type_class._symbol_f
|
||||
self._symbol_set = type_class._symbol_set
|
||||
|
||||
def new(self, _computed_field=False, **args):
|
||||
if _computed_field:
|
||||
# field is computed, we need an instance of a non-function column
|
||||
type_class = globals()[self._type]
|
||||
return type_class(**args)
|
||||
else:
|
||||
# HACK: function fields are tricky to recreate, simply return a copy
|
||||
import copy
|
||||
return copy.copy(self)
|
||||
|
||||
def to_field_args(self):
|
||||
args = super(function, self).to_field_args()
|
||||
args['store'] = bool(self.store)
|
||||
args['company_dependent'] = False
|
||||
if self._type in ('float',):
|
||||
args['digits'] = self._digits_compute or self._digits
|
||||
elif self._type in ('binary',):
|
||||
# limitation: binary function fields cannot be stored in attachments
|
||||
args['attachment'] = False
|
||||
elif self._type in ('selection', 'reference'):
|
||||
args['selection'] = self.selection
|
||||
elif self._type in ('many2one', 'one2many', 'many2many'):
|
||||
args['comodel_name'] = self._obj
|
||||
return args
|
||||
|
||||
def digits_change(self, cr):
|
||||
pass
|
||||
|
||||
def search(self, cr, uid, obj, name, args, context=None):
|
||||
if not self._fnct_search:
|
||||
#CHECKME: should raise an exception
|
||||
return []
|
||||
return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
|
||||
|
||||
def postprocess(self, cr, uid, obj, field, value=None, context=None):
|
||||
return self._postprocess_batch(cr, uid, obj, field, {0: value}, context=context)[0]
|
||||
|
||||
def _postprocess_batch(self, cr, uid, obj, field, values, context=None):
|
||||
if not values:
|
||||
return values
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
field_type = obj._columns[field]._type
|
||||
new_values = dict(values)
|
||||
|
||||
if field_type == 'binary':
|
||||
if context.get('bin_size'):
|
||||
# client requests only the size of binary fields
|
||||
for rid, value in values.iteritems():
|
||||
if value:
|
||||
new_values[rid] = get_nice_size(value)
|
||||
elif not context.get('bin_raw'):
|
||||
for rid, value in values.iteritems():
|
||||
if value:
|
||||
new_values[rid] = sanitize_binary_value(value)
|
||||
|
||||
return new_values
|
||||
|
||||
def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
|
||||
multi = self._multi
|
||||
# if we already have a value, don't recompute it.
|
||||
# This happen if case of stored many2one fields
|
||||
if values and not multi and name in values[0]:
|
||||
result = dict((v['id'], v[name]) for v in values)
|
||||
elif values and multi and all(n in values[0] for n in name):
|
||||
result = dict((v['id'], dict((n, v[n]) for n in name)) for v in values)
|
||||
else:
|
||||
result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
|
||||
if multi:
|
||||
swap = {}
|
||||
for rid, values in result.iteritems():
|
||||
for f, v in values.iteritems():
|
||||
if f not in name:
|
||||
continue
|
||||
swap.setdefault(f, {})[rid] = v
|
||||
|
||||
for field, values in swap.iteritems():
|
||||
new_values = self._postprocess_batch(cr, uid, obj, field, values, context)
|
||||
for rid, value in new_values.iteritems():
|
||||
result[rid][field] = value
|
||||
|
||||
else:
|
||||
result = self._postprocess_batch(cr, uid, obj, name, result, context)
|
||||
|
||||
return result
|
||||
|
||||
def set(self, cr, obj, id, name, value, user=None, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
if self._fnct_inv:
|
||||
self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
|
||||
|
||||
@classmethod
|
||||
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
|
||||
# Function fields are supposed to emulate a basic field type,
|
||||
# so they can delegate to the basic type for record name rendering
|
||||
return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Related fields
|
||||
# ---------------------------------------------------------
|
||||
|
||||
class related(function):
|
||||
"""Field that points to some data inside another field of the current record.
|
||||
|
||||
Example::
|
||||
|
||||
_columns = {
|
||||
'foo_id': fields.many2one('my.foo', 'Foo'),
|
||||
'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
|
||||
}
|
||||
"""
|
||||
__slots__ = ['arg', '_relations']
|
||||
|
||||
def _related_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
|
||||
# assume self._arg = ('foo', 'bar', 'baz')
|
||||
# domain = [(name, op, val)] => search [('foo.bar.baz', op, val)]
|
||||
field = '.'.join(self._arg)
|
||||
return map(lambda x: (field, x[1], x[2]), domain)
|
||||
|
||||
def _related_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
for instance in obj.browse(cr, uid, ids, context=context):
|
||||
# traverse all fields except the last one
|
||||
for field in self.arg[:-1]:
|
||||
instance = instance[field][:1]
|
||||
if instance:
|
||||
# write on the last field of the target record
|
||||
instance.write({self.arg[-1]: values})
|
||||
|
||||
def _related_read(self, obj, cr, uid, ids, field_name, args, context=None):
|
||||
res = {}
|
||||
for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||
value = record
|
||||
# traverse all fields except the last one
|
||||
for field in self.arg[:-1]:
|
||||
value = value[field][:1]
|
||||
# read the last field on the target record
|
||||
res[record.id] = value[self.arg[-1]]
|
||||
|
||||
if self._type == 'many2one':
|
||||
# res[id] is a recordset; convert it to (id, name) or False.
|
||||
# Perform name_get as root, as seeing the name of a related object depends on
|
||||
# access right of source document, not target, so user may not have access.
|
||||
value_ids = list(set(value.id for value in res.itervalues() if value))
|
||||
value_name = dict(obj.pool[self._obj].name_get(cr, SUPERUSER_ID, value_ids, context=context))
|
||||
res = dict((id, bool(value) and (value.id, value_name[value.id])) for id, value in res.iteritems())
|
||||
|
||||
elif self._type in ('one2many', 'many2many'):
|
||||
# res[id] is a recordset; convert it to a list of ids
|
||||
res = dict((id, value.ids) for id, value in res.iteritems())
|
||||
|
||||
return res
|
||||
|
||||
def __init__(self, *arg, **args):
|
||||
self.arg = arg
|
||||
self._relations = []
|
||||
super(related, self).__init__(self._related_read, arg, self._related_write, fnct_inv_arg=arg, fnct_search=self._related_search, **args)
|
||||
if self.store is True:
|
||||
# TODO: improve here to change self.store = {...} according to related objects
|
||||
pass
|
||||
|
||||
|
||||
class sparse(function):
|
||||
__slots__ = ['serialization_field']
|
||||
|
||||
def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def _sparse_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def _sparse_read(self, obj, cr, uid, ids, field_names, args, context=None):
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def __init__(self, serialization_field, **kwargs):
|
||||
self.serialization_field = serialization_field
|
||||
super(sparse, self).__init__(self._sparse_read, fnct_inv=self._sparse_write, multi='__sparse_multi', **kwargs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Dummy fields
|
||||
# ---------------------------------------------------------
|
||||
|
||||
class dummy(function):
|
||||
__slots__ = ['arg', '_relations']
|
||||
|
||||
def _dummy_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
|
||||
return []
|
||||
|
||||
def _dummy_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
|
||||
return False
|
||||
|
||||
def _dummy_read(self, obj, cr, uid, ids, field_name, args, context=None):
|
||||
return {}
|
||||
|
||||
def __init__(self, *arg, **args):
|
||||
self.arg = arg
|
||||
self._relations = []
|
||||
super(dummy, self).__init__(self._dummy_read, arg, self._dummy_write, fnct_inv_arg=arg, fnct_search=self._dummy_search, **args)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Serialized fields
|
||||
# ---------------------------------------------------------
|
||||
@@ -1679,33 +1184,3 @@ class serialized(_column):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['_prefetch'] = kwargs.get('_prefetch', False)
|
||||
super(serialized, self).__init__(*args, **kwargs)
|
||||
|
||||
# TODO: review completly this class for speed improvement
|
||||
class property(function):
|
||||
__slots__ = []
|
||||
|
||||
def to_field_args(self):
|
||||
args = super(property, self).to_field_args()
|
||||
args['company_dependent'] = True
|
||||
return args
|
||||
|
||||
def _property_search(self, tobj, cr, uid, obj, name, domain, context=None):
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def _property_write(self, obj, cr, uid, id, prop_name, value, obj_dest, context=None):
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def _property_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def __init__(self, **args):
|
||||
if 'view_load' in args:
|
||||
_logger.warning("view_load attribute is deprecated on ir.fields. Args: %r", args)
|
||||
args = dict(args)
|
||||
args['obj'] = args.pop('relation', '') or args.get('obj', '')
|
||||
super(property, self).__init__(
|
||||
fnct=self._property_read,
|
||||
fnct_inv=self._property_write,
|
||||
fnct_search=self._property_search,
|
||||
**args
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ from . import common
|
||||
from . import preprocess
|
||||
from .interface import report_rml
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field
|
||||
from openerp.osv.fields import float as float_field, datetime as datetime_field
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.tools.translate import _
|
||||
@@ -177,7 +177,7 @@ class rml_parse(object):
|
||||
else:
|
||||
d = res_digits(self.cr)[1]
|
||||
elif (hasattr(obj, '_field') and\
|
||||
isinstance(obj._field, (float_field, function_field)) and\
|
||||
isinstance(obj._field, float_field) and\
|
||||
obj._field.digits):
|
||||
d = obj._field.digits[1]
|
||||
if not d and d is not 0:
|
||||
|
||||
Reference in New Issue
Block a user