defattrsetter(attr, value): """ Return a function that sets ``attr`` on its argument and returns it. """ returnlambda method: setattr(method, attr, value) or method
defonchange(self, values, field_name, field_onchange): ... ... ... todo = list(names or nametree)
# process names in order while todo: # apply field-specific onchange methods for name in todo: if field_onchange.get(name): record._onchange_eval(name, field_onchange[name], result) done.add(name)
# determine which fields to process for the next pass todo = [ name for name in nametree if name notin done and snapshot0.has_changed(name) ]
ifnot env.context.get('recursive_onchanges', True): todo = []
# make the snapshot with the final values of record snapshot1 = Snapshot(record, nametree)
# determine values that have changed by comparing snapshots self.invalidate_cache() result['value'] = snapshot1.diff(snapshot0)
# format warnings warnings = result.pop('warnings') if len(warnings) == 1: title, message, type = warnings.pop() ifnot type: type = 'dialog' result['warning'] = dict(title=title, message=message, type=type) elif len(warnings) > 1: # concatenate warning titles and messages title = _("Warnings") message = '\n\n'.join([warn_title + '\n\n' + warn_message for warn_title, warn_message, warn_type in warnings]) result['warning'] = dict(title=title, message=message, type='dialog')
# process names in order while todo: # apply field-specific onchange methods for name in todo: if field_onchange.get(name): record._onchange_eval(name, field_onchange[name], result) done.add(name)
def_onchange_eval(self, field_name, onchange, result): """ Apply onchange method(s) for field ``field_name`` with spec ``onchange`` on record ``self``. Value assignments are applied on ``self``, while domain and warning messages are put in dictionary ``result``. """ onchange = onchange.strip()
defprocess(res): ifnot res: return if res.get('value'): res['value'].pop('id', None) self.update({key: val for key, val in res['value'].items() if key in self._fields}) if res.get('domain'): result.setdefault('domain', {}).update(res['domain']) if res.get('warning'): result['warnings'].add(( res['warning'].get('title') or _("Warning"), res['warning'].get('message') or"", res['warning'].get('type') or"", ))
if onchange in ("1", "true"): for method in self._onchange_methods.get(field_name, ()): method_res = method(self) process(method_res) return
通过注释,可以发现,确实是在处理字段的onchange方法。主要代码就是下面这个:
1 2 3 4 5
if onchange in ("1", "true"): for method in self._onchange_methods.get(field_name, ()): method_res = method(self) process(method_res) return
@property def_onchange_methods(self): """ Return a dictionary mapping field names to onchange methods. """ defis_onchange(func): return callable(func) and hasattr(func, '_onchange')
# collect onchange methods on the model's class cls = type(self) methods = defaultdict(list) for attr, func in getmembers(cls, is_onchange): for name in func._onchange: if name notin cls._fields: _logger.warning("@onchange%r parameters must be field names", func._onchange) methods[name].append(func)
# add onchange methods to implement "change_default" on fields defonchange_default(field, self): value = field.convert_to_write(self[field.name], self) condition = "%s=%s" % (field.name, value) defaults = self.env['ir.default'].get_model_defaults(self._name, condition) self.update(defaults)
for name, field in cls._fields.items(): if field.change_default: methods[name].append(functools.partial(onchange_default, field))
# optimization: memoize result on cls, it will not be recomputed cls._onchange_methods = methods return methods
for attr, func in getmembers(cls, is_onchange): for name in func._onchange: if name notin cls._fields: _logger.warning("@onchange%r parameters must be field names", func._onchange) methods[name].append(func)