diff --git a/openerp/addons/base/module/module_view.xml b/openerp/addons/base/module/module_view.xml index 729d3ef7b4c..c3b3684f07c 100644 --- a/openerp/addons/base/module/module_view.xml +++ b/openerp/addons/base/module/module_view.xml @@ -39,7 +39,7 @@ + 'ilike', self)]" string="Module"/> diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 8bd5c3a0179..a1c06a52494 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -341,14 +341,12 @@ class res_users(osv.osv): def copy(self, cr, uid, id, default=None, context=None): user2copy = self.read(cr, uid, [id], ['login','name'])[0] - if default is None: - default = {} - copy_pattern = _("%s (copy)") - copydef = dict(login=(copy_pattern % user2copy['login']), - name=(copy_pattern % user2copy['name']), - ) - copydef.update(default) - return super(res_users, self).copy(cr, uid, id, copydef, context) + default = dict(default or {}) + if ('name' not in default) and ('partner_id' not in default): + default['name'] = _("%s (copy)") % user2copy['name'] + if 'login' not in default: + default['login'] = _("%s (copy)") % user2copy['login'] + return super(res_users, self).copy(cr, uid, id, default, context) def context_get(self, cr, uid, context=None): user = self.browse(cr, SUPERUSER_ID, uid, context) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index ec995c6a8cc..c713c0378ea 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4746,56 +4746,45 @@ class BaseModel(object): else: raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name)) - # TODO it seems fields_get can be replaced by _all_columns (no need for translation) - fields = self.fields_get(cr, uid, context=context) - for f in fields: - ftype = fields[f]['type'] - - if self._log_access and f in LOG_ACCESS_COLUMNS: - del data[f] + # build a black list of fields that should not be copied + blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right']) + def blacklist_given_fields(obj): + # blacklist the fields that are given by inheritance + for other, field_to_other in obj._inherits.items(): + blacklist.add(field_to_other) + if field_to_other in default: + # all the fields of 'other' are given by the record: default[field_to_other], + # except the ones redefined in self + blacklist.update(set(self.pool.get(other)._all_columns) - set(self._columns)) + else: + blacklist_given_fields(self.pool.get(other)) + blacklist_given_fields(self) + res = dict(default) + for f, colinfo in self._all_columns.items(): + field = colinfo.column if f in default: - data[f] = default[f] - elif 'function' in fields[f]: - del data[f] - elif ftype == 'many2one': - try: - data[f] = data[f] and data[f][0] - except: - pass - elif ftype == 'one2many': - res = [] - rel = self.pool.get(fields[f]['relation']) - if data[f]: - # duplicate following the order of the ids - # because we'll rely on it later for copying - # translations in copy_translation()! - data[f].sort() - for rel_id in data[f]: - # the lines are first duplicated using the wrong (old) - # parent but then are reassigned to the correct one thanks - # to the (0, 0, ...) - d = rel.copy_data(cr, uid, rel_id, context=context) - if d: - res.append((0, 0, d)) - data[f] = res - elif ftype == 'many2many': - data[f] = [(6, 0, data[f])] + pass + elif f in blacklist: + pass + elif isinstance(field, fields.function): + pass + elif field._type == 'many2one': + res[f] = data[f] and data[f][0] + elif field._type == 'one2many': + other = self.pool.get(field._obj) + # duplicate following the order of the ids because we'll rely on + # it later for copying translations in copy_translation()! + lines = [other.copy_data(cr, uid, line_id, context=context) for line_id in sorted(data[f])] + # the lines are duplicated using the wrong (old) parent, but then + # are reassigned to the correct one thanks to the (0, 0, ...) + res[f] = [(0, 0, line) for line in lines if line] + elif field._type == 'many2many': + res[f] = [(6, 0, data[f])] + else: + res[f] = data[f] - del data['id'] - - # make sure we don't break the current parent_store structure and - # force a clean recompute! - for parent_column in ['parent_left', 'parent_right']: - data.pop(parent_column, None) - # Remove _inherits field's from data recursively, missing parents will - # be created by create() (so that copy() copy everything). - def remove_ids(inherits_dict): - for parent_table in inherits_dict: - del data[inherits_dict[parent_table]] - remove_ids(self.pool.get(parent_table)._inherits) - remove_ids(self._inherits) - return data + return res def copy_translations(self, cr, uid, old_id, new_id, context=None): if context is None: diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index bce5bc3903a..ef8c0cf19aa 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -6,6 +6,85 @@ import common UID = common.ADMIN_USER_ID DB = common.DB +class TestInherits(common.TransactionCase): + """ test the behavior of the orm for models that use _inherits; + specifically: res.users, that inherits from res.partner + """ + + def setUp(self): + super(TestInherits, self).setUp() + self.partner = self.registry('res.partner') + self.user = self.registry('res.users') + + def test_create(self): + """ creating a user should automatically create a new partner """ + partners_before = self.partner.search(self.cr, UID, []) + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + foo = self.user.browse(self.cr, UID, foo_id) + + self.assertNotIn(foo.partner_id.id, partners_before) + + def test_create_with_ancestor(self): + """ creating a user with a specific 'partner_id' should not create a new partner """ + par_id = self.partner.create(self.cr, UID, {'name': 'Foo'}) + partners_before = self.partner.search(self.cr, UID, []) + foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'}) + partners_after = self.partner.search(self.cr, UID, []) + + self.assertEqual(set(partners_before), set(partners_after)) + + foo = self.user.browse(self.cr, UID, foo_id) + self.assertEqual(foo.name, 'Foo') + self.assertEqual(foo.partner_id.id, par_id) + + def test_read(self): + """ inherited fields should be read without any indirection """ + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + foo_values, = self.user.read(self.cr, UID, [foo_id]) + partner_id = foo_values['partner_id'][0] + partner_values, = self.partner.read(self.cr, UID, [partner_id]) + self.assertEqual(foo_values['name'], partner_values['name']) + + foo = self.user.browse(self.cr, UID, foo_id) + self.assertEqual(foo.name, foo.partner_id.name) + + def test_copy(self): + """ copying a user should automatically copy its partner, too """ + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + foo_before, = self.user.read(self.cr, UID, [foo_id]) + bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'}) + foo_after, = self.user.read(self.cr, UID, [foo_id]) + + self.assertEqual(foo_before, foo_after) + + foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id]) + self.assertEqual(bar.login, 'bar') + self.assertNotEqual(foo.id, bar.id) + self.assertNotEqual(foo.partner_id.id, bar.partner_id.id) + + def test_copy_with_ancestor(self): + """ copying a user with 'parent_id' in defaults should not duplicate the partner """ + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + par_id = self.partner.create(self.cr, UID, {'name': 'Bar'}) + + foo_before, = self.user.read(self.cr, UID, [foo_id]) + partners_before = self.partner.search(self.cr, UID, []) + bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'}) + foo_after, = self.user.read(self.cr, UID, [foo_id]) + partners_after = self.partner.search(self.cr, UID, []) + + self.assertEqual(foo_before, foo_after) + self.assertEqual(set(partners_before), set(partners_after)) + + foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id]) + self.assertNotEqual(foo.id, bar.id) + self.assertEqual(bar.partner_id.id, par_id) + self.assertEqual(bar.login, 'bar', "login is given from copy parameters") + self.assertEqual(bar.password, foo.password, "password is given from original record") + self.assertEqual(bar.name, 'Bar', "name is given from specific partner") + + + CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) DELETE = lambda id: (2, id, False) @@ -19,6 +98,7 @@ def sorted_by_id(list_of_dicts): return sorted(list_of_dicts, key=lambda d: d.get('id')) class TestO2MSerialization(common.TransactionCase): + """ test the orm method 'write' on one2many fields """ def setUp(self): super(TestO2MSerialization, self).setUp()