diff --git a/CHANGES.rst b/CHANGES.rst index ab0c3c8..fcc4c41 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,16 @@ Changelog --------- +2.x.x (unreleased) +~~~~~~~~~~~~~~~~~~ + +* Support :method:`Model.create` with a list of values. + With Odoo >= 12.0 + +* Support :method:`RecordList.copy`. + With Odoo >= 18.0 + + 2.2.0 (2025-09-16) ~~~~~~~~~~~~~~~~~~ diff --git a/odooly.py b/odooly.py index 8de4bfe..fd07a86 100644 --- a/odooly.py +++ b/odooly.py @@ -75,10 +75,10 @@ # Published object methods _methods = { + 'common': ['about', 'login', 'authenticate', 'version'], 'db': ['create_database', 'duplicate_database', 'db_exist', 'drop', 'dump', 'restore', 'rename', 'list', 'list_lang', 'list_countries', 'change_admin_password', 'server_version', 'migrate_databases'], - 'common': ['about', 'login', 'authenticate', 'version'], 'object': ['execute', 'execute_kw'], } # New 6.1: (db) create_database db_exist, @@ -1214,19 +1214,23 @@ def get(self, domain, *args, **kwargs): return Record(self, ids[0]) if ids else None def create(self, values): - """Create a :class:`Record`. + """Create one or many :class:`Record`(s). The argument `values` is a dictionary of values which are used to create the record. Relationship fields `one2many` and `many2many` accept either a list of ids or a RecordList or the extended Odoo syntax. Relationship fields `many2one` and `reference` accept either a Record or the Odoo syntax. + Since Odoo 12.0, it can create multiple records. - The newly created :class:`Record` is returned. + The newly created :class:`Record` is returned (or :class:`RecordList`). """ - values = self._unbrowse_values(values) - new_id = self._execute('create', values) - return Record(self, new_id) + if hasattr(values, "items"): + values = self._unbrowse_values(values) + else: # Odoo >= 12.0 + values = [self._unbrowse_values(vals) for vals in values] + new_ids = self._execute('create', values) + return Record(self, new_ids) def read(self, *params, **kwargs): """Wrapper for ``client.execute(model, 'read', [...], ('a', 'b'))``. @@ -1673,6 +1677,17 @@ def read(self, fields=None): return records return values + def copy(self, default=None): + """Copy records and return :class:`RecordList`. Odoo >= 18.0 + + The optional argument `default` is a mapping which overrides some + values of the new records. + """ + if default: + default = self._model._unbrowse_values(default) + new_ids = self._execute('copy', self.ids, default) + return RecordList(self._model, new_ids) + @property def _external_id(self): """Retrieve the External IDs of the :class:`RecordList`. @@ -1769,6 +1784,8 @@ def copy(self, default=None): if default: default = self._model._unbrowse_values(default) new_id = self._execute('copy', self.id, default) + if isinstance(new_id, list): + [new_id] = new_id or [False] return Record(self._model, new_id) def _send(self, signal): diff --git a/tests/test_model.py b/tests/test_model.py index edb6102..3a9f3c7 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -78,7 +78,11 @@ def __getitem__(self, key): ids[ids.index(8888)] = b'\xdan\xeecode'.decode('latin-1') return [(res_id, b'name_%s'.decode() % res_id) for res_id in ids] if method in ('create', 'copy'): - return 1999 + single_id = ( + (method == 'copy' and isinstance(args[0], int)) or + (method == 'create' and hasattr(args[0], 'items')) + ) + return 1999 if single_id else list(range(1001, 1001 + len(args[0]))) return [sentinel.OTHER] def setUp(self): @@ -464,6 +468,7 @@ def test_create(self): FooBar = self.env['foo.bar'] record42 = FooBar.browse(42) + record100 = FooBar.browse(100) recordlist42 = FooBar.browse([4, 2]) FooBar.create({'spam': 42}) @@ -471,6 +476,9 @@ def test_create(self): FooBar.create({'spam': recordlist42}) FooBar._execute('create', {'spam': 42}) FooBar.create({}) + # Odoo >= 12.0 + FooBar.create([{'spam': record42}]) + FooBar.create([{'spam': record100}, {'spam': record42}]) self.assertCalls( OBJ('foo.bar', 'fields_get'), OBJ('foo.bar', 'create', {'spam': 42}), @@ -478,6 +486,8 @@ def test_create(self): OBJ('foo.bar', 'create', {'spam': [4, 2]}), OBJ('foo.bar', 'create', {'spam': 42}), OBJ('foo.bar', 'create', {}), + OBJ('foo.bar', 'create', [{'spam': 42}]), + OBJ('foo.bar', 'create', [{'spam': 100}, {'spam': 42}]), ) self.assertOutput('') @@ -724,6 +734,8 @@ def test_copy(self): rec.copy({'spam': rec}) rec.copy({'spam': records}) rec.copy({}) + # Odoo >= 18.0 + records.copy({'spam': rec}) self.assertCalls( OBJ('foo.bar', 'copy', 42, None), OBJ('foo.bar', 'fields_get'), @@ -731,6 +743,7 @@ def test_copy(self): OBJ('foo.bar', 'copy', 42, {'spam': 42}), OBJ('foo.bar', 'copy', 42, {'spam': [13, 17]}), OBJ('foo.bar', 'copy', 42, {}), + OBJ('foo.bar', 'copy', [13, 17], {'spam': 42}), ) self.assertOutput('')