From ce0d89e7e75af2b379e98f559546f871dad1de49 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Sat, 2 Jul 2011 01:23:28 +0200 Subject: [PATCH] [IMP] fields.function: removed method=True param, added docstring The 'method' param was quite useless, as 100% of functions fields were using method=True. In addition, there is no need to distinguish methods and functions, as methods are unbound and passed as function objects in the declaration of a function field, so they only need a proper signature. Finally, docstring was added for fields.function class, based on current doc from developer book (in preparation of future API doc). (A fix in addons follows, getting rid of all the useless method=True params there too). bzr revid: odo@openerp.com-20110701232328-flgxulxva70vnyxr --- openerp/osv/fields.py | 200 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 190 insertions(+), 10 deletions(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index b7979f03812..3cf87a1e4fe 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -741,6 +741,192 @@ 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 `). + :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_template 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, ids, field_name, field_value, fnct_inv_arg, context) + + Callable that implements the ``write`` operation for the function field. + + :param orm_template model: model to which the field belongs (should be ``self`` for + a model method) + :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_template model: model to which the field belongs (should be ``self`` for + a model method) + :param orm_template 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_template 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) + } + + """ _classic_read = False _classic_write = False _prefetch = False @@ -750,10 +936,9 @@ class function(_column): # # 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, method=False, 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): _column.__init__(self, **args) self._obj = obj - self._method = method self._fnct = fnct self._fnct_inv = fnct_inv self._arg = arg @@ -834,11 +1019,7 @@ class function(_column): return result def get(self, cr, obj, ids, name, uid=False, context=None, values=None): - result = {} - if self._method: - result = self._fnct(obj, cr, uid, ids, name, self._arg, context) - else: - result = self._fnct(cr, obj._table, ids, name, self._arg, context) + result = self._fnct(obj, cr, uid, ids, name, self._arg, context) for id in ids: if self._multi and id in result: for field, value in result[id].iteritems(): @@ -968,7 +1149,7 @@ class related(function): def __init__(self, *arg, **args): self.arg = arg self._relations = [] - super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, method=True, fnct_search=self._fnct_search, **args) + super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args) if self.store is True: # TODO: improve here to change self.store = {...} according to related objects pass @@ -1005,7 +1186,7 @@ class dummy(function): def __init__(self, *arg, **args): self.arg = arg self._relations = [] - super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, method=True, fnct_search=None, **args) + super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args) # --------------------------------------------------------- # Serialized fields @@ -1164,7 +1345,6 @@ def field_to_dict(self, cr, user, context, field): res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False res['fnct_inv_arg'] = field._fnct_inv_arg or False res['func_obj'] = field._obj or False - res['func_method'] = field._method if isinstance(field, many2many): res['related_columns'] = list((field._id1, field._id2)) res['third_table'] = field._rel