diff --git a/openerp/addons/test_new_api/tests/test_onchange.py b/openerp/addons/test_new_api/tests/test_onchange.py index 08cb0a89bad..1bfe4ad5c45 100644 --- a/openerp/addons/test_new_api/tests/test_onchange.py +++ b/openerp/addons/test_new_api/tests/test_onchange.py @@ -82,41 +82,10 @@ class TestOnChange(common.TransactionCase): field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('name'), '1') self.assertEqual(field_onchange.get('messages'), '1') - - # FIXME: commented out because currently not supported by the client - # # modify messages - # values = { - # 'name': "Foo", - # 'categories': [], - # 'moderator': False, - # 'participants': [], - # 'messages': [ - # (0, 0, { - # 'name': "[%s] %s" % ('', USER.name), - # 'body': BODY, - # 'author': USER.id, - # 'size': len(BODY), - # }), - # (4, message.id), - # ], - # } - # self.env.invalidate_all() - # result = self.Discussion.onchange(values, 'messages', field_onchange) - # self.assertIn('messages', result['value']) - # self.assertItemsEqual(result['value']['messages'], [ - # (0, 0, { - # 'name': "[%s] %s" % ("Foo", USER.name), - # 'body': BODY, - # 'author': USER.id, - # 'size': len(BODY), - # }), - # (1, message.id, { - # 'name': "[%s] %s" % ("Foo", USER.name), - # 'body': BODY, - # 'author': USER.id, - # 'size': len(BODY), - # }), - # ]) + self.assertItemsEqual( + [name for name in field_onchange if name.startswith('messages.')], + ['messages.author', 'messages.body', 'messages.name', 'messages.size'], + ) # modify discussion name values = { @@ -125,28 +94,32 @@ class TestOnChange(common.TransactionCase): 'moderator': False, 'participants': [], 'messages': [ + (4, message.id), (0, 0, { 'name': "[%s] %s" % ('', USER.name), 'body': BODY, 'author': USER.id, 'size': len(BODY), }), - (4, message.id), ], } self.env.invalidate_all() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) - self.assertItemsEqual(result['value']['messages'], [ - (0, 0, { + self.assertEqual(result['value']['messages'], [ + { + 'id': message.id, + 'name': "[%s] %s" % ("Foo", USER.name), + 'body': message.body, + 'author': message.author.name_get()[0], + 'size': message.size, + }, + { 'name': "[%s] %s" % ("Foo", USER.name), 'body': BODY, - 'author': USER.id, + 'author': USER.name_get()[0], 'size': len(BODY), - }), - (1, message.id, { - 'name': "[%s] %s" % ("Foo", USER.name), - }), + }, ]) def test_onchange_specific(self): @@ -156,25 +129,28 @@ class TestOnChange(common.TransactionCase): field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('moderator'), '1') + self.assertFalse(any(name.startswith('participants.') for name in field_onchange)) # first remove demo user from participants discussion.participants -= demo self.assertNotIn(demo, discussion.participants) # check that demo_user is added to participants when set as moderator - participants = [(4, usr.id) for usr in discussion.participants] values = { 'name': discussion.name, 'moderator': demo.id, 'categories': [(4, cat.id) for cat in discussion.categories], 'messages': [(4, msg.id) for msg in discussion.messages], - 'participants': participants, + 'participants': [(4, usr.id) for usr in discussion.participants], } self.env.invalidate_all() result = discussion.onchange(values, 'moderator', field_onchange) self.assertIn('participants', result['value']) - self.assertItemsEqual(result['value']['participants'], participants + [(4, demo.id)]) + self.assertEqual( + result['value']['participants'], + [{'id': usr.id} for usr in discussion.participants + demo], + ) def test_onchange_one2many_value(self): """ test the value of the one2many field inside the onchange """ diff --git a/openerp/fields.py b/openerp/fields.py index 2707daa2ce5..5f09c1044a0 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -761,21 +761,20 @@ class Field(object): """ return False if value is None else value - def convert_to_write(self, value, target=None, fnames=None): + def convert_to_write(self, value): """ convert ``value`` from the cache to a valid value for method :meth:`BaseModel.write`. - - :param target: optional, the record to be modified with this value - :param fnames: for relational fields only, an optional collection of - field names to convert """ return self.convert_to_read(value) - def convert_to_onchange(self, value): - """ convert ``value`` from the cache to a valid value for an onchange - method v7. + def convert_to_onchange(self, value, fnames=None): + """ convert ``value`` from the cache to a value as returned by method + :meth:`BaseModel.onchange`. + + :param fnames: an optional collection of field names to convert + (for relational fields only) """ - return self.convert_to_write(value) + return self.convert_to_read(value) def convert_to_export(self, value, env): """ convert ``value`` from the cache to a valid value for export. The @@ -1115,18 +1114,13 @@ class Monetary(Field): assert self.currency_field in model._fields, \ "Field %s with unknown currency_field %r" % (self, self.currency_field) - def convert_to_write(self, value, target=None, fnames=None): - if target is not None: - currency = target[self.currency_field] - # FIXME @rco-odoo: currency may not be already initialized if it is - # a function or related field! - if currency: - return currency.round(float(value or 0.0)) - return float(value or 0.0) - return value - def convert_to_cache(self, value, record, validate=True): - return self.convert_to_write(value, record) + currency = record[self.currency_field] + # FIXME @rco-odoo: currency may not be already initialized if it is a + # function or related field! + if currency: + return currency.round(float(value or 0.0)) + return float(value or 0.0) class _String(Field): @@ -1649,10 +1643,7 @@ class Many2one(_Relational): else: return value.id - def convert_to_write(self, value, target=None, fnames=None): - return value.id - - def convert_to_onchange(self, value): + def convert_to_write(self, value): return value.id def convert_to_export(self, value, env): @@ -1729,37 +1720,31 @@ class _RelationalMulti(_Relational): def convert_to_read(self, value, use_name_get=True): return value.ids - def convert_to_write(self, value, target=None, fnames=None): - # remove/delete former records - if target is None: - set_ids = [] - result = [(6, 0, set_ids)] - add_existing = lambda id: set_ids.append(id) - else: - tag = 2 if self.type == 'one2many' else 3 - result = [(tag, record.id) for record in target[self.name] - value] - add_existing = lambda id: result.append((4, id)) - - if fnames is None: - # take all fields in cache, except the inverses of self - fnames = set(value._fields) - set(MAGIC_COLUMNS) - model = value.env[self.model_name] - for invf in model._field_inverses[self]: - fnames.discard(invf.name) - - # add new and existing records + def convert_to_write(self, value): + # make result with new and existing records + result = [(5,)] for record in value: if not record.id: - values = {k: v for k, v in record._cache.iteritems() if k in fnames} + values = dict(record._cache) values = record._convert_to_write(values) result.append((0, 0, values)) elif record._is_dirty(): - values = {k: record._cache[k] for k in record._get_dirty() if k in fnames} + values = {k: record._cache[k] for k in record._get_dirty()} values = record._convert_to_write(values) result.append((1, record.id, values)) else: - add_existing(record.id) + result.append((4, record.id)) + return result + def convert_to_onchange(self, value, fnames=None): + # return the recordset value as a list of dicts + fields = [(name, value._fields[name]) for name in (fnames or [])] + result = [] + for record in value: + vals = {name: field.convert_to_onchange(record[name]) for name, field in fields} + if record.id: + vals['id'] = record.id + result.append(vals) return result def convert_to_export(self, value, env): diff --git a/openerp/models.py b/openerp/models.py index 14b55577ffb..dd90b1820fb 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -5785,13 +5785,10 @@ class BaseModel(object): def __getattr__(self, name): field = self._record._fields[name] value = self._record[name] - return field.convert_to_onchange(value) + return field.convert_to_write(value) record = self[self._context['field_parent']] global_vars['parent'] = RawRecord(record) - field_vars = { - key: self._fields[key].convert_to_onchange(val) - for key, val in self._cache.iteritems() - } + field_vars = self._convert_to_write(self._cache) params = eval("[%s]" % params, global_vars, field_vars) # call onchange method with context when possible @@ -5833,7 +5830,7 @@ class BaseModel(object): if not all(name in self._fields for name in names): return {} - # determine subfields for field.convert_to_write() below + # determine subfields for field.convert_to_onchange() below secondary = [] subfields = defaultdict(set) for dotname in field_onchange: @@ -5869,7 +5866,8 @@ class BaseModel(object): continue record[name] = value - result = {'value': {}} + result = {} + dirty = set() # process names in order (or the keys of values if no name given) while todo: @@ -5891,32 +5889,23 @@ class BaseModel(object): for name, oldval in values.iteritems(): field = self._fields[name] newval = record[name] - if field.type in ('one2many', 'many2many'): - if newval != oldval or newval._is_dirty(): - # put new value in result - result['value'][name] = field.convert_to_write( - newval, record._origin, subfields.get(name), - ) - todo.append(name) - else: - # keep result: newval may have been dirty before - pass - else: - if newval != oldval: - # put new value in result - result['value'][name] = field.convert_to_write( - newval, record._origin, subfields.get(name), - ) - todo.append(name) - else: - # clean up result to not return another value - result['value'].pop(name, None) + if newval != oldval or ( + field.type in ('one2many', 'many2many') and newval._is_dirty() + ): + todo.append(name) + dirty.add(name) # At the moment, the client does not support updates on a *2many field # while this one is modified by the user. - if field_name and not isinstance(field_name, list) and \ + if isinstance(field_name, basestring) and \ self._fields[field_name].type in ('one2many', 'many2many'): - result['value'].pop(field_name, None) + dirty.discard(field_name) + + # collect values from dirty fields + result['value'] = { + name: self._fields[name].convert_to_onchange(record[name], subfields.get(name)) + for name in dirty + } return result