summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphael Collet <rco@openerp.com>2016-08-22 11:33:46 +0200
committerRaphael Collet <rco@openerp.com>2016-08-31 17:21:48 +0200
commit2b587d34ff30147c03baf16e998effe46ce5d9cf (patch)
tree850f8746198eb00775293e806f60c22970514e52
parent0fc697a3f78dca4d6df1e8c73668242bc0467386 (diff)
[REM] openerp.osv.fields: remove old-style function fields
-rw-r--r--addons/membership/models/partner.py5
-rw-r--r--openerp/api.py8
-rw-r--r--openerp/fields.py4
-rw-r--r--openerp/models.py353
-rw-r--r--openerp/modules/registry.py12
-rw-r--r--openerp/osv/expression.py32
-rw-r--r--openerp/osv/fields.py525
-rw-r--r--openerp/report/report_sxw.py4
8 files changed, 16 insertions, 927 deletions
diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py
index 1fcdcdfaeb8..9b863bf4cce 100644
--- a/addons/membership/models/partner.py
+++ b/addons/membership/models/partner.py
@@ -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):
diff --git a/openerp/api.py b/openerp/api.py
index 67678e3ffb1..b8b4f1ed2d9 100644
--- a/openerp/api.py
+++ b/openerp/api.py
@@ -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``. """
diff --git a/openerp/fields.py b/openerp/fields.py
index c8a3dd8d010..7c3a8644cdd 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -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:
diff --git a/openerp/models.py b/openerp/models.py
index 68449f0d4df..a2c7fe67149 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -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):
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index 97cb20866c6..e440f17ef84 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -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()
@@ -93,16 +91,6 @@ class Registry(Mapping):
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
field is guaranteed to be strictly greater than the value of the
diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py
index 3b2e3970cc9..837750e5286 100644
--- a/openerp/osv/expression.py
+++ b/openerp/osv/expression.py
@@ -909,38 +909,6 @@ class expression(object):
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
# -------------------------------------------------
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index da55a4bada3..f65ae38ce4d 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -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:
@@ -1160,500 +1159,6 @@ def sanitize_binary_value(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
- )
diff --git a/openerp/report/report_sxw.py b/openerp/report/report_sxw.py
index 8054414bf1e..6d2b2434d36 100644
--- a/openerp/report/report_sxw.py
+++ b/openerp/report/report_sxw.py
@@ -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: