From 4aa5ba35afa949098d532189beb4c680dcada2fa Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Mon, 4 Nov 2019 16:22:50 +0100 Subject: [IMP] base/test_*: Adapt tests to work with/without demo data --- odoo/addons/base/tests/test_form_create.py | 1 - .../tests/test_access_monetary_related.py | 7 +- .../test_access_rights/tests/test_feedback.py | 10 +- odoo/addons/test_convert/tests/test_env.py | 24 +- odoo/addons/test_impex/tests/contacts.json | 2 +- odoo/addons/test_impex/tests/test_load.py | 9 +- odoo/addons/test_inherit/tests/test_inherit.py | 5 +- .../static/tests/tours/main_flow.js | 10 +- odoo/addons/test_main_flows/tests/test_flow.py | 79 +++ odoo/addons/test_new_api/__init__.py | 2 + odoo/addons/test_new_api/__manifest__.py | 10 +- odoo/addons/test_new_api/assets.xml | 10 - .../addons/test_new_api/data/test_new_api_data.xml | 54 ++ odoo/addons/test_new_api/demo_data.xml | 55 -- odoo/addons/test_new_api/ir.model.access.csv | 47 -- odoo/addons/test_new_api/models.py | 766 --------------------- odoo/addons/test_new_api/models/__init__.py | 4 + odoo/addons/test_new_api/models/test_new_api.py | 766 +++++++++++++++++++++ .../test_new_api/security/ir.model.access.csv | 47 ++ odoo/addons/test_new_api/tests/test_new_fields.py | 53 +- odoo/addons/test_new_api/tests/test_onchange.py | 17 +- odoo/addons/test_new_api/tests/test_ui.py | 6 +- odoo/addons/test_new_api/views.xml | 300 -------- .../test_new_api/views/test_new_api_templates.xml | 10 + .../test_new_api/views/test_new_api_views.xml | 300 ++++++++ odoo/addons/test_performance/__manifest__.py | 3 - odoo/addons/test_performance/demo/demo.xml | 30 - .../test_performance/tests/test_performance.py | 40 +- 28 files changed, 1402 insertions(+), 1265 deletions(-) delete mode 100644 odoo/addons/test_new_api/assets.xml create mode 100644 odoo/addons/test_new_api/data/test_new_api_data.xml delete mode 100644 odoo/addons/test_new_api/demo_data.xml delete mode 100644 odoo/addons/test_new_api/ir.model.access.csv delete mode 100644 odoo/addons/test_new_api/models.py create mode 100644 odoo/addons/test_new_api/models/__init__.py create mode 100644 odoo/addons/test_new_api/models/test_new_api.py create mode 100644 odoo/addons/test_new_api/security/ir.model.access.csv delete mode 100644 odoo/addons/test_new_api/views.xml create mode 100644 odoo/addons/test_new_api/views/test_new_api_templates.xml create mode 100644 odoo/addons/test_new_api/views/test_new_api_views.xml delete mode 100644 odoo/addons/test_performance/demo/demo.xml diff --git a/odoo/addons/base/tests/test_form_create.py b/odoo/addons/base/tests/test_form_create.py index b79980017c4..9a787f46d2f 100644 --- a/odoo/addons/base/tests/test_form_create.py +++ b/odoo/addons/base/tests/test_form_create.py @@ -30,7 +30,6 @@ class TestFormCreate(TransactionCase): }) partner_form.property_account_payable_id = property_account_payable_id partner_form.property_account_receivable_id = property_account_receivable_id - # [('internal_type', '=', 'payable'), ('deprecated', '=', False)] partner_form.save() def test_create_res_users(self): diff --git a/odoo/addons/test_access_rights/tests/test_access_monetary_related.py b/odoo/addons/test_access_rights/tests/test_access_monetary_related.py index 2abe53294ed..0bff8048f8c 100644 --- a/odoo/addons/test_access_rights/tests/test_access_monetary_related.py +++ b/odoo/addons/test_access_rights/tests/test_access_monetary_related.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import odoo.tests +from odoo.addons.base.tests.common import TransactionCaseWithUserDemo -class TestMonetaryAccess(odoo.tests.TransactionCase): + +class TestMonetaryAccess(TransactionCaseWithUserDemo): def test_monetary_access_create(self): """Monetary fields that depend on compute/related currency @@ -13,7 +14,7 @@ class TestMonetaryAccess(odoo.tests.TransactionCase): as well as trigger spurious ACL errors. """ user_admin = self.env.ref("base.user_admin") - user_demo = self.env.ref("base.user_demo").with_user(user_admin) + user_demo = self.user_demo.with_user(user_admin) # this would raise without the fix introduced in this commit new_user = user_demo.copy({'monetary': 1/3}) diff --git a/odoo/addons/test_access_rights/tests/test_feedback.py b/odoo/addons/test_access_rights/tests/test_feedback.py index 09fd42589a8..40515026f3c 100644 --- a/odoo/addons/test_access_rights/tests/test_feedback.py +++ b/odoo/addons/test_access_rights/tests/test_feedback.py @@ -23,7 +23,15 @@ class TestSudo(Feedback): def test_sudo(self): record = self.env['test_access_right.some_obj'].create({'val': 5}) user1 = self.user - user2 = self.env.ref('base.user_demo') + partner_demo = self.env['res.partner'].create({ + 'name': 'Marc Demo', + }) + user2 = self.env['res.users'].create({ + 'login': 'demo2', + 'password': 'demo2', + 'partner_id': partner_demo.id, + 'groups_id': [(6, 0, [self.env.ref('base.group_user').id, self.env.ref('base.group_partner_manager').id])], + }) # with_user(user) record1 = record.with_user(user1) diff --git a/odoo/addons/test_convert/tests/test_env.py b/odoo/addons/test_convert/tests/test_env.py index fc5d9a094d3..1e549ce3db3 100644 --- a/odoo/addons/test_convert/tests/test_env.py +++ b/odoo/addons/test_convert/tests/test_env.py @@ -39,14 +39,14 @@ class TestEnv(common.TransactionCase): model="test_convert.usered", id="test_convert.testing" ), - uid="base.user_demo" + uid="base.default_user" ) ) r = self.env.ref('test_convert.testing') self.assertEqual(r.name, 'a') - self.assertEqual(r.create_uid, self.env.ref('base.user_demo')) - self.assertEqual(r.user_id, self.env.ref('base.user_demo')) + self.assertEqual(r.create_uid, self.env.ref('base.default_user')) + self.assertEqual(r.user_id, self.env.ref('base.default_user')) def test_uid_data_function(self): self.importer( @@ -56,14 +56,14 @@ class TestEnv(common.TransactionCase): name="create", eval="[[{'name': 'b'}]]", ), - uid="base.user_demo" + uid="base.default_user" ) ) r = self.env['test_convert.usered'].search([]) self.assertEqual(r.name, 'b') - self.assertEqual(r.create_uid, self.env.ref('base.user_demo')) - self.assertEqual(r.user_id, self.env.ref('base.user_demo')) + self.assertEqual(r.create_uid, self.env.ref('base.default_user')) + self.assertEqual(r.user_id, self.env.ref('base.default_user')) def test_uid_record(self): self.importer( @@ -72,7 +72,7 @@ class TestEnv(common.TransactionCase): field('c', name="name"), model="test_convert.usered", id="test_convert.testing", - uid="base.user_demo" + uid="base.default_user" ), uid="base.user_root" ) @@ -80,8 +80,8 @@ class TestEnv(common.TransactionCase): r = self.env.ref('test_convert.testing') self.assertEqual(r.name, 'c') - self.assertEqual(r.create_uid, self.env.ref('base.user_demo')) - self.assertEqual(r.user_id, self.env.ref('base.user_demo')) + self.assertEqual(r.create_uid, self.env.ref('base.default_user')) + self.assertEqual(r.user_id, self.env.ref('base.default_user')) def test_uid_function(self): @@ -90,7 +90,7 @@ class TestEnv(common.TransactionCase): function( model="test_convert.usered", name="create", - uid="base.user_demo", + uid="base.default_user", eval="[[{'name': 'd'}]]" ), uid="base.user_root" @@ -98,8 +98,8 @@ class TestEnv(common.TransactionCase): ) r = self.env['test_convert.usered'].search([]) self.assertEqual(r.name, 'd') - self.assertEqual(r.create_uid, self.env.ref('base.user_demo')) - self.assertEqual(r.user_id, self.env.ref('base.user_demo')) + self.assertEqual(r.create_uid, self.env.ref('base.default_user')) + self.assertEqual(r.user_id, self.env.ref('base.default_user')) def test_context_data_function(self): self.env.user.tz = 'UTC' diff --git a/odoo/addons/test_impex/tests/contacts.json b/odoo/addons/test_impex/tests/contacts.json index a024d2b7ee4..8bf490bb4c0 100644 --- a/odoo/addons/test_impex/tests/contacts.json +++ b/odoo/addons/test_impex/tests/contacts.json @@ -1 +1 @@ -[["Wood y Wood Pecker", "", "Snow Street, 25", "Kainuu", "Finland", "Vendor", "1", ""], ["Roger Pecker", "Contact", "Snow Street, 27", "Kainuu", "Finland", "Vendor", "0", "Wood y Wood Pecker"], ["Sharon Pecker", "Delivery Address", "Snow Street, 28", "Kainuu", "Finland", "Vendor", "0", "Wood y Wood Pecker"], ["Thomas Pecker", "Contact", "Snow Street, 27", "Kainuu", "Finland", "Vendor", "0", "Wood y Wood Pecker"], ["Norseman Roundabout", "", "Atonium Street, 45a", "Brussels", "Belgium", "Vendor", "1", ""], ["Yvan Holiday", "Invoice Address", "Atonium Street, 45b", "Brussels", "Belgium", "Vendor", "0", "Norseman Roundabout"], ["Jack Unsworth", "Contact", "Atonium Street, 45a", "Brussels", "Belgium", "Vendor", "0", "Norseman Roundabout"]] +[["Wood y Wood Pecker", "", "Snow Street, 25", "Kainuu", "Finland", "Sellers", "1", ""], ["Roger Pecker", "Contact", "Snow Street, 27", "Kainuu", "Finland", "Sellers", "0", "Wood y Wood Pecker"], ["Sharon Pecker", "Delivery Address", "Snow Street, 28", "Kainuu", "Finland", "Sellers", "0", "Wood y Wood Pecker"], ["Thomas Pecker", "Contact", "Snow Street, 27", "Kainuu", "Finland", "Sellers", "0", "Wood y Wood Pecker"], ["Norseman Roundabout", "", "Atonium Street, 45a", "Brussels", "Belgium", "Sellers", "1", ""], ["Yvan Holiday", "Invoice Address", "Atonium Street, 45b", "Brussels", "Belgium", "Sellers", "0", "Norseman Roundabout"], ["Jack Unsworth", "Contact", "Atonium Street, 45a", "Brussels", "Belgium", "Sellers", "0", "Norseman Roundabout"]] diff --git a/odoo/addons/test_impex/tests/test_load.py b/odoo/addons/test_impex/tests/test_load.py index edd346c6220..d2bd80f7e56 100644 --- a/odoo/addons/test_impex/tests/test_load.py +++ b/odoo/addons/test_impex/tests/test_load.py @@ -6,6 +6,7 @@ import pkgutil import re from odoo import fields +from odoo.addons.base.tests.common import SavepointCaseWithUserDemo from odoo.tests import common from odoo.tools.misc import mute_logger @@ -1007,7 +1008,13 @@ class test_o2m_multiple(ImporterCase): self.assertEqual(set(values(b.child2)), set([21, 22, 23])) -class test_realworld(common.TransactionCase): +class test_realworld(SavepointCaseWithUserDemo): + + @classmethod + def setUpClass(cls): + super(test_realworld, cls).setUpClass() + cls._load_partners_set() + def test_bigfile(self): data = json.loads(pkgutil.get_data(self.__module__, 'contacts_big.json').decode('utf-8')) result = self.env['res.partner'].load(['name', 'mobile', 'email', 'image_1920'], data) diff --git a/odoo/addons/test_inherit/tests/test_inherit.py b/odoo/addons/test_inherit/tests/test_inherit.py index d47f9b0dc93..8e5502d5671 100644 --- a/odoo/addons/test_inherit/tests/test_inherit.py +++ b/odoo/addons/test_inherit/tests/test_inherit.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.addons.base.tests.common import TransactionCaseWithUserDemo from odoo.tests import common class test_inherits(common.TransactionCase): @@ -68,10 +69,12 @@ class test_inherits(common.TransactionCase): self.assertEqual(mother._fields['state'].selection, [('a', 'A'), ('d', 'D'), ('b', 'B'), ('c', 'C')]) +class test_inherits_demo(TransactionCaseWithUserDemo): + def test_50_search_one2many(self): """ check search on one2many field based on inherited many2one field. """ # create a daughter record attached to partner Demo - partner_demo = self.env.ref('base.partner_demo') + partner_demo = self.partner_demo daughter = self.env['test.inherit.daughter'].create({'partner_id': partner_demo.id}) self.assertEqual(daughter.partner_id, partner_demo) self.assertIn(daughter, partner_demo.daughter_ids) diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index 4b49c9e7e08..272e98c5f54 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -665,12 +665,6 @@ tour.register('main_flow_tour', { trigger: ".o_statusbar_buttons > button:enabled:contains('Register Payment')", content: _t("Register Payment"), position: "bottom", -}, { - trigger: "select.o_field_widget[name=journal_id]", - extra_trigger: ".modal-dialog", - content: _t("Select Journal"), - position: "bottom", - run: 'text(Bank (USD))', }, { trigger: ".modal-footer .btn-primary", content: _t("Validate"), @@ -732,7 +726,7 @@ tour.register('main_flow_tour', { trigger: ".o_selected_row .o_field_widget[name=amount] input", content: _t("Write the amount received."), position: "bottom", - run: "text 12.65", + run: "text 11.00", }, { edition: "enterprise", trigger: ".o_selected_row .o_field_widget[name=partner_id] input", @@ -762,7 +756,7 @@ tour.register('main_flow_tour', { position: "bottom", }, { edition: "enterprise", - trigger: "button.o_reconcile", + trigger: "button.o_reconcile, button.o_validate", content: _t('

Click on Reconcile

'), position: "right", }, { diff --git a/odoo/addons/test_main_flows/tests/test_flow.py b/odoo/addons/test_main_flows/tests/test_flow.py index c412b031339..fe86f8187c6 100644 --- a/odoo/addons/test_main_flows/tests/test_flow.py +++ b/odoo/addons/test_main_flows/tests/test_flow.py @@ -7,4 +7,83 @@ import odoo.tests class TestUi(odoo.tests.HttpCase): def test_01_main_flow_tour(self): + # Define minimal accounting data to run without CoA + a_expense = self.env['account.account'].create({ + 'code': 'X2120', + 'name': 'Expenses - (test)', + 'user_type_id': self.env.ref('account.data_account_type_expenses').id, + }) + a_recv = self.env['account.account'].create({ + 'code': 'X1012', + 'name': 'Debtors - (test)', + 'reconcile': True, + 'user_type_id': self.env.ref('account.data_account_type_receivable').id, + }) + a_pay = self.env['account.account'].create({ + 'code': 'X1111', + 'name': 'Creditors - (test)', + 'user_type_id': self.env.ref('account.data_account_type_payable').id, + 'reconcile': True, + }) + a_sale = self.env['account.account'].create({ + 'code': 'X2020', + 'name': 'Product Sales - (test)', + 'user_type_id': self.env.ref('account.data_account_type_revenue').id, + }) + bnk = self.env['account.account'].create({ + 'code': 'X1014', + 'name': 'Bank Current Account - (test)', + 'user_type_id': self.env.ref('account.data_account_type_liquidity').id, + }) + + self.env['ir.property'].create([{ + 'name': 'property_account_receivable_id', + 'fields_id': self.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_account_receivable_id')], limit=1).id, + 'value': 'account.account,%s' % (a_recv.id), + 'company_id': self.env.company.id, + }, { + 'name': 'property_account_payable_id', + 'fields_id': self.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_account_payable_id')], limit=1).id, + 'value': 'account.account,%s' % (a_pay.id), + 'company_id': self.env.company.id, + }, { + 'name': 'property_account_position_id', + 'fields_id': self.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_account_position_id')], limit=1).id, + 'value': False, + 'company_id': self.env.company.id, + }, { + 'name': 'property_account_expense_categ_id', + 'fields_id': self.env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_account_expense_categ_id')], limit=1).id, + 'value': 'account.account,%s' % (a_expense.id), + 'company_id': self.env.company.id, + }, { + 'name': 'property_account_income_categ_id', + 'fields_id': self.env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_account_income_categ_id')], limit=1).id, + 'value': 'account.account,%s' % (a_sale.id), + 'company_id': self.env.company.id, + }]) + self.expenses_journal = self.env['account.journal'].create({ + 'name': 'Vendor Bills - Test', + 'code': 'TEXJ', + 'type': 'purchase', + 'default_credit_account_id': a_expense.id, + 'default_debit_account_id': a_expense.id, + 'refund_sequence': True, + }) + self.bank_journal = self.env['account.journal'].create({ + 'name': 'Bank - Test', + 'code': 'TBNK', + 'type': 'bank', + 'default_credit_account_id': bnk.id, + 'default_debit_account_id': bnk.id, + }) + self.sales_journal = self.env['account.journal'].create({ + 'name': 'Customer Invoices - Test', + 'code': 'TINV', + 'type': 'sale', + 'default_credit_account_id': a_sale.id, + 'default_debit_account_id': a_sale.id, + 'refund_sequence': True, + }) + self.start_tour("/web", 'main_flow_tour', login="admin", timeout=180) diff --git a/odoo/addons/test_new_api/__init__.py b/odoo/addons/test_new_api/__init__.py index a0fdc10fe11..dc5e6b693d1 100644 --- a/odoo/addons/test_new_api/__init__.py +++ b/odoo/addons/test_new_api/__init__.py @@ -1,2 +1,4 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + from . import models diff --git a/odoo/addons/test_new_api/__manifest__.py b/odoo/addons/test_new_api/__manifest__.py index 409fb15571a..f7ba4e9cdd8 100644 --- a/odoo/addons/test_new_api/__manifest__.py +++ b/odoo/addons/test_new_api/__manifest__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + { 'name': 'Test API', 'version': '1.0', @@ -8,11 +10,11 @@ 'installable': True, 'auto_install': False, 'data': [ - 'ir.model.access.csv', - 'views.xml', - 'assets.xml', + 'security/ir.model.access.csv', + 'views/test_new_api_views.xml', + 'views/test_new_api_templates.xml', + 'data/test_new_api_data.xml', ], 'demo': [ - 'demo_data.xml', ], } diff --git a/odoo/addons/test_new_api/assets.xml b/odoo/addons/test_new_api/assets.xml deleted file mode 100644 index 8bd75bceab5..00000000000 --- a/odoo/addons/test_new_api/assets.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/odoo/addons/test_new_api/data/test_new_api_data.xml b/odoo/addons/test_new_api/data/test_new_api_data.xml new file mode 100644 index 00000000000..678dabbece7 --- /dev/null +++ b/odoo/addons/test_new_api/data/test_new_api_data.xml @@ -0,0 +1,54 @@ + + + + Chat + + + Foolish + + + + + Stuff + + + + + + Hey dude! + hello + + + + What's up? + + + + This is a much longer message + + + + + first@example.localhost + + + + + + + + + + + + + + + + + New API Precision + + + + + diff --git a/odoo/addons/test_new_api/demo_data.xml b/odoo/addons/test_new_api/demo_data.xml deleted file mode 100644 index ec6a5c8bced..00000000000 --- a/odoo/addons/test_new_api/demo_data.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - Chat - - - Foolish - - - - - Stuff - - - - - - Hey dude! - hello - - - - - What's up? - - - - This is a much longer message - - - - - first@example.localhost - - - - - - - - - - - - - - - - - New API Precision - - - - - diff --git a/odoo/addons/test_new_api/ir.model.access.csv b/odoo/addons/test_new_api/ir.model.access.csv deleted file mode 100644 index b676e85dd0d..00000000000 --- a/odoo/addons/test_new_api/ir.model.access.csv +++ /dev/null @@ -1,47 +0,0 @@ -"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1 -access_decimal_precision_test_all,decimal.precision.test,model_decimal_precision_test,,1,1,1,1 -access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1 -access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1 -access_emailmessage,test_new_api_emailmessage,test_new_api.model_test_new_api_emailmessage,,1,1,1,1 -access_multi,test_new_api_multi,test_new_api.model_test_new_api_multi,,1,1,1,1 -access_multi_line,test_new_api_multi_line,test_new_api.model_test_new_api_multi_line,,1,1,1,1 -access_multi_line2,test_new_api_multi_line2,test_new_api.model_test_new_api_multi_line2,,1,1,1,1 -access_multi_tag,test_new_api_multi_tag,test_new_api.model_test_new_api_multi_tag,,1,1,1,1 -access_creativework_edition,test_new_api_creativework_edition,model_test_new_api_creativework_edition,,1,1,1,1 -access_creativework_book,test_new_api_creativework_book,model_test_new_api_creativework_book,,1,1,1,1 -access_creativework_movie,test_new_api_creativework_movie,model_test_new_api_creativework_movie,,1,1,1,1 -access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1 -access_domain_bool,access_domain_bool,model_domain_bool,,1,1,1,1 -access_test_new_api_foo,access_test_new_api_foo,model_test_new_api_foo,,1,1,1,1 -access_test_new_api_bar,access_test_new_api_bar,model_test_new_api_bar,,1,1,1,1 -access_test_new_api_related,access_test_new_api_related,model_test_new_api_related,,1,1,1,1 -access_test_new_api_company,access_test_new_api_company,model_test_new_api_company,,1,1,1,1 -access_test_new_api_company_attr,access_test_new_api_company_attr,model_test_new_api_company_attr,,1,1,1,1 -access_test_new_api_compute_inverse,access_test_new_api_compute_inverse,model_test_new_api_compute_inverse,,1,1,1,1 -access_test_new_api_compute_protected,access_test_new_api_compute_protected,model_test_new_api_compute_protected,,1,1,1,1 -access_test_new_api_multi_compute_inverse,access_test_new_api_multi_compute_inverse,model_test_new_api_multi_compute_inverse,,1,1,1,1 -access_test_new_api_recursive,access_test_new_api_recursive,model_test_new_api_recursive,,1,1,1,1 -access_test_new_api_cascade,access_test_new_api_cascade,model_test_new_api_cascade,,1,1,1,1 -access_test_new_api_compute_onchange,access_test_new_api_compute_onchange,model_test_new_api_compute_onchange,,1,1,1,1 -access_test_new_api_binary_svg,access_test_new_api_binary_svg,model_test_new_api_binary_svg,,1,1,1,1 -access_test_new_api_monetary_base,access_test_new_api_monetary_base,model_test_new_api_monetary_base,,1,1,1,1 -access_test_new_api_monetary_related,access_test_new_api_monetary_related,model_test_new_api_monetary_related,,1,1,1,1 -access_test_new_api_monetary_custom,access_test_new_api_monetary_custom,model_test_new_api_monetary_custom,,1,1,1,1 -access_test_new_api_monetary_inherits,access_test_new_api_monetary_inherits,model_test_new_api_monetary_inherits,,1,1,1,1 -access_test_new_api_field_with_caps,access_test_new_api_field_with_caps,model_test_new_api_field_with_caps,,1,1,1,1 -access_test_new_api_req_m2o,access_test_new_api_req_m2o,model_test_new_api_req_m2o,,1,1,1,1 -access_test_new_api_selection,access_test_new_api_selection,model_test_new_api_selection,,1,1,1,1 -access_test_new_api_attachment,access_test_new_api_attachment,model_test_new_api_attachment,,1,1,1,1 -access_test_new_api_attachment_host,access_test_new_api_attachment_host,model_test_new_api_attachment_host,,1,1,1,1 -access_test_new_api_model_image,access_test_new_api_model_image,model_test_new_api_model_image,,1,1,1,1 -access_test_new_api_model_a,access_test_new_api_model_a,model_test_new_api_model_a,,1,1,1,1 -access_test_new_api_model_b,access_test_new_api_model_b,model_test_new_api_model_b,,1,1,1,1 -access_test_new_api_model_parent,access_test_new_api_model_parent,model_test_new_api_model_parent,,1,1,1,1 -access_test_new_api_model_child,access_test_new_api_model_child,model_test_new_api_model_child,,1,1,1,1 -access_test_new_api_model_child_nocheck,access_test_new_api_model_child_nocheck,model_test_new_api_model_child_nocheck,,1,1,1,1 -access_test_new_api_display,access_test_new_api_display,model_test_new_api_display,,1,1,1,1 -access_test_new_api_model_active_field,access_test_new_api_model_active_field,model_test_new_api_model_active_field,,1,1,1,1 -access_test_new_api_model_many2one_reference,access_test_new_api_model_many2one_reference,model_test_new_api_model_many2one_reference,,1,1,1,1 -access_test_new_api_inverse_m2o_ref,access_test_new_api_inverse_m2o_ref,model_test_new_api_inverse_m2o_ref,,1,1,1,1 -access_test_new_api_model_binary,access_test_new_api_model_binary,model_test_new_api_model_binary,,1,1,1,1 diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py deleted file mode 100644 index 3f45466efa3..00000000000 --- a/odoo/addons/test_new_api/models.py +++ /dev/null @@ -1,766 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import datetime - -from odoo import models, fields, api, _ -from odoo.exceptions import AccessError, ValidationError - - -class Category(models.Model): - _name = 'test_new_api.category' - _description = 'Test New API Category' - _order = 'name' - _parent_store = True - _parent_name = 'parent' - - name = fields.Char(required=True) - color = fields.Integer('Color Index') - parent = fields.Many2one('test_new_api.category', ondelete='cascade') - parent_path = fields.Char(index=True) - root_categ = fields.Many2one(_name, compute='_compute_root_categ') - display_name = fields.Char(compute='_compute_display_name', inverse='_inverse_display_name') - dummy = fields.Char(store=False) - discussions = fields.Many2many('test_new_api.discussion', 'test_new_api_discussion_category', - 'category', 'discussion') - - _sql_constraints = [ - ('positive_color', 'CHECK(color >= 0)', 'The color code must be positive !') - ] - - @api.depends('name', 'parent.display_name') # this definition is recursive - def _compute_display_name(self): - for cat in self: - if cat.parent: - cat.display_name = cat.parent.display_name + ' / ' + cat.name - else: - cat.display_name = cat.name - - @api.depends('parent') - def _compute_root_categ(self): - for cat in self: - current = cat - while current.parent: - current = current.parent - cat.root_categ = current - - def _inverse_display_name(self): - for cat in self: - names = cat.display_name.split('/') - # determine sequence of categories - categories = [] - for name in names[:-1]: - category = self.search([('name', 'ilike', name.strip())]) - categories.append(category[0]) - categories.append(cat) - # assign parents following sequence - for parent, child in zip(categories, categories[1:]): - if parent and child: - child.parent = parent - # assign name of last category, and reassign display_name (to normalize it) - cat.name = names[-1].strip() - - def _read(self, fields): - # DLE P45: `test_31_prefetch`, - # with self.assertRaises(AccessError): - # cat1.name - if self.search_count([('id', 'in', self._ids), ('name', '=', 'NOACCESS')]): - raise AccessError('Sorry') - return super(Category, self)._read(fields) - - -class Discussion(models.Model): - _name = 'test_new_api.discussion' - _description = 'Test New API Discussion' - - name = fields.Char(string='Title', required=True, - help="General description of what this discussion is about.") - moderator = fields.Many2one('res.users') - categories = fields.Many2many('test_new_api.category', - 'test_new_api_discussion_category', 'discussion', 'category') - participants = fields.Many2many('res.users', context={'active_test': False}) - messages = fields.One2many('test_new_api.message', 'discussion', copy=True) - message_concat = fields.Text(string='Message concatenate') - important_messages = fields.One2many('test_new_api.message', 'discussion', - domain=[('important', '=', True)]) - very_important_messages = fields.One2many( - 'test_new_api.message', 'discussion', - domain=lambda self: self._domain_very_important()) - emails = fields.One2many('test_new_api.emailmessage', 'discussion') - important_emails = fields.One2many('test_new_api.emailmessage', 'discussion', - domain=[('important', '=', True)]) - - def _domain_very_important(self): - """Ensure computed O2M domains work as expected.""" - return [("important", "=", True)] - - @api.onchange('name') - def _onchange_name(self): - # test onchange modifying one2many field values - if self.env.context.get('generate_dummy_message') and self.name == '{generate_dummy_message}': - # update body of existings messages and emails - for message in self.messages: - message.body = 'not last dummy message' - for message in self.important_messages: - message.body = 'not last dummy message' - # add new dummy message - message_vals = self.messages._add_missing_default_values({'body': 'dummy message', 'important': True}) - self.messages |= self.messages.new(message_vals) - self.important_messages |= self.messages.new(message_vals) - - @api.onchange('moderator') - def _onchange_moderator(self): - self.participants |= self.moderator - - @api.onchange('messages') - def _onchange_messages(self): - self.message_concat = "\n".join(["%s:%s" % (m.name, m.body) for m in self.messages]) - - -class Message(models.Model): - _name = 'test_new_api.message' - _description = 'Test New API Message' - - discussion = fields.Many2one('test_new_api.discussion', ondelete='cascade') - body = fields.Text() - author = fields.Many2one('res.users', default=lambda self: self.env.user) - name = fields.Char(string='Title', compute='_compute_name', store=True) - display_name = fields.Char(string='Abstract', compute='_compute_display_name') - size = fields.Integer(compute='_compute_size', search='_search_size') - double_size = fields.Integer(compute='_compute_double_size') - discussion_name = fields.Char(related='discussion.name', string="Discussion Name", readonly=False) - author_partner = fields.Many2one( - 'res.partner', compute='_compute_author_partner', - search='_search_author_partner') - important = fields.Boolean() - label = fields.Char(translate=True) - priority = fields.Integer() - - @api.constrains('author', 'discussion') - def _check_author(self): - for message in self.with_context(active_test=False): - if message.discussion and message.author not in message.discussion.participants: - raise ValidationError(_("Author must be among the discussion participants.")) - - @api.depends('author.name', 'discussion.name') - def _compute_name(self): - for message in self: - message.name = self._context.get('compute_name', - "[%s] %s" % (message.discussion.name or '', message.author.name or '')) - - @api.constrains('name') - def _check_name(self): - # dummy constraint to check on computed field - for message in self: - if message.name.startswith("[X]"): - raise ValidationError("No way!") - - @api.depends('author.name', 'discussion.name', 'body') - def _compute_display_name(self): - for message in self: - stuff = "[%s] %s: %s" % (message.author.name, message.discussion.name or '', message.body or '') - message.display_name = stuff[:80] - - @api.depends('body') - def _compute_size(self): - for message in self: - message.size = len(message.body or '') - - def _search_size(self, operator, value): - if operator not in ('=', '!=', '<', '<=', '>', '>=', 'in', 'not in'): - return [] - # retrieve all the messages that match with a specific SQL query - query = """SELECT id FROM "%s" WHERE char_length("body") %s %%s""" % \ - (self._table, operator) - self.env.cr.execute(query, (value,)) - ids = [t[0] for t in self.env.cr.fetchall()] - return [('id', 'in', ids)] - - @api.depends('size') - def _compute_double_size(self): - for message in self: - # This illustrates a subtle situation: message.double_size depends - # on message.size. When the latter is computed, message.size is - # assigned, which would normally invalidate message.double_size. - # However, this may not happen while message.double_size is being - # computed: the last statement below would fail, because - # message.double_size would be undefined. - message.double_size = 0 - size = message.size - message.double_size = message.double_size + size - - @api.depends('author', 'author.partner_id') - def _compute_author_partner(self): - for message in self: - message.author_partner = message.author.partner_id - - @api.model - def _search_author_partner(self, operator, value): - return [('author.partner_id', operator, value)] - - def write(self, vals): - if 'priority' in vals: - vals['priority'] = 5 - return super().write(vals) - - -class EmailMessage(models.Model): - _name = 'test_new_api.emailmessage' - _description = 'Test New API Email Message' - _inherits = {'test_new_api.message': 'message'} - - message = fields.Many2one('test_new_api.message', 'Message', - required=True, ondelete='cascade') - email_to = fields.Char('To') - - -class Multi(models.Model): - """ Model for testing multiple onchange methods in cascade that modify a - one2many field several times. - """ - _name = 'test_new_api.multi' - _description = 'Test New API Multi' - - name = fields.Char(related='partner.name', readonly=True) - partner = fields.Many2one('res.partner') - lines = fields.One2many('test_new_api.multi.line', 'multi') - partners = fields.One2many(related='partner.child_ids') - - @api.onchange('name') - def _onchange_name(self): - for line in self.lines: - line.name = self.name - - @api.onchange('partner') - def _onchange_partner(self): - for line in self.lines: - line.partner = self.partner - - -class MultiLine(models.Model): - _name = 'test_new_api.multi.line' - _description = 'Test New API Multi Line' - - multi = fields.Many2one('test_new_api.multi', ondelete='cascade') - name = fields.Char() - partner = fields.Many2one('res.partner') - tags = fields.Many2many('test_new_api.multi.tag') - - -class MultiLine2(models.Model): - _name = 'test_new_api.multi.line2' - _inherit = 'test_new_api.multi.line' - _description = 'Test New API Multi Line 2' - - -class MultiTag(models.Model): - _name = 'test_new_api.multi.tag' - _description = 'Test New API Multi Tag' - - name = fields.Char() - - -class Edition(models.Model): - _name = 'test_new_api.creativework.edition' - _description = 'Test New API Creative Work Edition' - - name = fields.Char() - res_id = fields.Integer(required=True) - res_model_id = fields.Many2one('ir.model', required=True) - res_model = fields.Char(related='res_model_id.model', store=True, readonly=False) - - -class Book(models.Model): - _name = 'test_new_api.creativework.book' - _description = 'Test New API Creative Work Book' - - name = fields.Char() - editions = fields.One2many( - 'test_new_api.creativework.edition', 'res_id', domain=[('res_model', '=', _name)] - ) - - -class Movie(models.Model): - _name = 'test_new_api.creativework.movie' - _description = 'Test New API Creative Work Movie' - - name = fields.Char() - editions = fields.One2many( - 'test_new_api.creativework.edition', 'res_id', domain=[('res_model', '=', _name)] - ) - - -class MixedModel(models.Model): - _name = 'test_new_api.mixed' - _description = 'Test New API Mixed' - - number = fields.Float(digits=(10, 2), default=3.14) - number2 = fields.Float(digits='New API Precision') - date = fields.Date() - moment = fields.Datetime() - now = fields.Datetime(compute='_compute_now') - lang = fields.Selection(string='Language', selection='_get_lang') - reference = fields.Reference(string='Related Document', - selection='_reference_models') - comment1 = fields.Html(sanitize=False) - comment2 = fields.Html(sanitize_attributes=True, strip_classes=False) - comment3 = fields.Html(sanitize_attributes=True, strip_classes=True) - comment4 = fields.Html(sanitize_attributes=True, strip_style=True) - - currency_id = fields.Many2one('res.currency', default=lambda self: self.env.ref('base.EUR')) - amount = fields.Monetary() - - def _compute_now(self): - # this is a non-stored computed field without dependencies - for message in self: - message.now = fields.Datetime.now() - - @api.model - def _get_lang(self): - return self.env['res.lang'].get_installed() - - @api.model - def _reference_models(self): - models = self.env['ir.model'].sudo().search([('state', '!=', 'manual')]) - return [(model.model, model.name) - for model in models - if not model.model.startswith('ir.')] - - -class BoolModel(models.Model): - _name = 'domain.bool' - _description = 'Boolean Domain' - - bool_true = fields.Boolean('b1', default=True) - bool_false = fields.Boolean('b2', default=False) - bool_undefined = fields.Boolean('b3') - - -class Foo(models.Model): - _name = 'test_new_api.foo' - _description = 'Test New API Foo' - - name = fields.Char() - value1 = fields.Integer(change_default=True) - value2 = fields.Integer() - - -class Bar(models.Model): - _name = 'test_new_api.bar' - _description = 'Test New API Bar' - - name = fields.Char() - foo = fields.Many2one('test_new_api.foo', compute='_compute_foo', search='_search_foo') - value1 = fields.Integer(related='foo.value1', readonly=False) - value2 = fields.Integer(related='foo.value2', readonly=False) - - @api.depends('name') - def _compute_foo(self): - for bar in self: - bar.foo = self.env['test_new_api.foo'].search([('name', '=', bar.name)], limit=1) - - def _search_foo(self, operator, value): - assert operator == 'in' - records = self.env['test_new_api.foo'].browse(value) - return [('name', 'in', records.mapped('name'))] - - -class Related(models.Model): - _name = 'test_new_api.related' - _description = 'Test New API Related' - - name = fields.Char() - # related fields with a single field - related_name = fields.Char(related='name', string='A related on Name', readonly=False) - related_related_name = fields.Char(related='related_name', string='A related on a related on Name', readonly=False) - - message = fields.Many2one('test_new_api.message') - message_name = fields.Text(related="message.body", related_sudo=False, string='Message Body') - message_currency = fields.Many2one(related="message.author", string='Message Author') - -class ComputeProtected(models.Model): - _name = 'test_new_api.compute.protected' - _description = 'Test New API Compute Protected' - - foo = fields.Char(default='') - bar = fields.Char(compute='_compute_bar', store=True) - - @api.depends('foo') - def _compute_bar(self): - for record in self: - record.bar = record.foo - -class ComputeInverse(models.Model): - _name = 'test_new_api.compute.inverse' - _description = 'Test New API Compute Inversse' - - foo = fields.Char() - bar = fields.Char(compute='_compute_bar', inverse='_inverse_bar', store=True) - - @api.depends('foo') - def _compute_bar(self): - self._context.get('log', []).append('compute') - for record in self: - record.bar = record.foo - - def _inverse_bar(self): - self._context.get('log', []).append('inverse') - for record in self: - record.foo = record.bar - - -class MultiComputeInverse(models.Model): - """ Model with the same inverse method for several fields. """ - _name = 'test_new_api.multi_compute_inverse' - _description = 'Test New API Multi Compute Inverse' - - foo = fields.Char(default='', required=True) - bar1 = fields.Char(compute='_compute_bars', inverse='_inverse_bar1', store=True) - bar2 = fields.Char(compute='_compute_bars', inverse='_inverse_bar23', store=True) - bar3 = fields.Char(compute='_compute_bars', inverse='_inverse_bar23', store=True) - - @api.depends('foo') - def _compute_bars(self): - self._context.get('log', []).append('compute') - for record in self: - substrs = record.foo.split('/') + ['', '', ''] - record.bar1, record.bar2, record.bar3 = substrs[:3] - - def _inverse_bar1(self): - self._context.get('log', []).append('inverse1') - for record in self: - record.write({'foo': '/'.join([record.bar1, record.bar2, record.bar3])}) - - def _inverse_bar23(self): - self._context.get('log', []).append('inverse23') - for record in self: - record.write({'foo': '/'.join([record.bar1, record.bar2, record.bar3])}) - - -class CompanyDependent(models.Model): - _name = 'test_new_api.company' - _description = 'Test New API Company' - - foo = fields.Char(company_dependent=True) - date = fields.Date(company_dependent=True) - moment = fields.Datetime(company_dependent=True) - tag_id = fields.Many2one('test_new_api.multi.tag', company_dependent=True) - - -class CompanyDependentAttribute(models.Model): - _name = 'test_new_api.company.attr' - _description = 'Test New API Company Attribute' - - company = fields.Many2one('test_new_api.company') - quantity = fields.Integer() - bar = fields.Char(compute='_compute_bar', store=True) - - @api.depends('quantity', 'company.foo') - def _compute_bar(self): - for record in self: - record.bar = (record.company.foo or '') * record.quantity - - -class ComputeRecursive(models.Model): - _name = 'test_new_api.recursive' - _description = 'Test New API Recursive' - - name = fields.Char(required=True) - parent = fields.Many2one('test_new_api.recursive', ondelete='cascade') - display_name = fields.Char(compute='_compute_display_name', store=True) - - @api.depends('name', 'parent.display_name') - def _compute_display_name(self): - for rec in self: - if rec.parent: - rec.display_name = rec.parent.display_name + " / " + rec.name - else: - rec.display_name = rec.name - - -class ComputeCascade(models.Model): - _name = 'test_new_api.cascade' - _description = 'Test New API Cascade' - - foo = fields.Char() - bar = fields.Char(compute='_compute_bar') # depends on foo - baz = fields.Char(compute='_compute_baz', store=True) # depends on bar - - @api.depends('foo') - def _compute_bar(self): - for record in self: - record.bar = "[%s]" % (record.foo or "") - - @api.depends('bar') - def _compute_baz(self): - for record in self: - record.baz = "<%s>" % (record.bar or "") - - -class ComputeOnchange(models.Model): - _name = 'test_new_api.compute.onchange' - _description = "Compute method as an onchange" - - active = fields.Boolean() - foo = fields.Char() - bar = fields.Char(compute='_compute_bar', store=True) - baz = fields.Char(compute='_compute_baz', store=True, readonly=False) - - @api.depends('foo') - def _compute_bar(self): - for record in self: - record.bar = record.foo - - @api.depends('active', 'foo') - def _compute_baz(self): - for record in self: - if record.active: - record.baz = record.foo - - -class ModelBinary(models.Model): - _name = 'test_new_api.model_binary' - _description = 'Test Image field' - - binary = fields.Binary() - binary_related_store = fields.Binary("Binary Related Store", related='binary', store=True, readonly=False) - binary_related_no_store = fields.Binary("Binary Related No Store", related='binary', store=False, readonly=False) - binary_computed = fields.Binary(compute='_compute_binary') - - @api.depends('binary') - def _compute_binary(self): - # arbitrary value: 'bin_size' must have no effect - for record in self: - record.binary_computed = [(record.id, bool(record.binary))] - - -class ModelImage(models.Model): - _name = 'test_new_api.model_image' - _description = 'Test Image field' - - name = fields.Char(required=True) - - image = fields.Image() - image_512 = fields.Image("Image 512", related='image', max_width=512, max_height=512, store=True, readonly=False) - image_256 = fields.Image("Image 256", related='image', max_width=256, max_height=256, store=False, readonly=False) - image_128 = fields.Image("Image 128", max_width=128, max_height=128) - - -class BinarySvg(models.Model): - _name = 'test_new_api.binary_svg' - _description = 'Test SVG upload' - - name = fields.Char(required=True) - image_attachment = fields.Binary(attachment=True) - image_wo_attachment = fields.Binary(attachment=False) - - -class MonetaryBase(models.Model): - _name = 'test_new_api.monetary_base' - _description = 'Monetary Base' - - base_currency_id = fields.Many2one('res.currency') - amount = fields.Monetary(currency_field='base_currency_id') - - -class MonetaryRelated(models.Model): - _name = 'test_new_api.monetary_related' - _description = 'Monetary Related' - - monetary_id = fields.Many2one('test_new_api.monetary_base') - currency_id = fields.Many2one('res.currency', related='monetary_id.base_currency_id') - amount = fields.Monetary(related='monetary_id.amount') - - -class MonetaryCustom(models.Model): - _name = 'test_new_api.monetary_custom' - _description = 'Monetary Related Custom' - - monetary_id = fields.Many2one('test_new_api.monetary_base') - x_currency_id = fields.Many2one('res.currency', related='monetary_id.base_currency_id') - x_amount = fields.Monetary(related='monetary_id.amount') - - -class MonetaryInherits(models.Model): - _name = 'test_new_api.monetary_inherits' - _description = 'Monetary Inherits' - _inherits = {'test_new_api.monetary_base': 'monetary_id'} - - monetary_id = fields.Many2one('test_new_api.monetary_base', required=True, ondelete='cascade') - currency_id = fields.Many2one('res.currency') - - -class FieldWithCaps(models.Model): - _name = 'test_new_api.field_with_caps' - _description = 'Model with field defined with capital letters' - - pArTneR_321_id = fields.Many2one('res.partner') - - -class Selection(models.Model): - _name = 'test_new_api.selection' - _description = "Selection" - - state = fields.Selection([('foo', 'Foo'), ('bar', 'Bar')]) - - -class RequiredM2O(models.Model): - _name = 'test_new_api.req_m2o' - _description = 'Required Many2one' - - foo = fields.Many2one('res.currency', required=True, ondelete='cascade') - bar = fields.Many2one('res.country', required=True) - - -class RequiredM2OTransient(models.TransientModel): - _name = 'test_new_api.req_m2o_transient' - _description = 'Transient Model with Required Many2one' - - foo = fields.Many2one('res.currency', required=True, ondelete='restrict') - bar = fields.Many2one('res.country', required=True) - - -class Attachment(models.Model): - _name = 'test_new_api.attachment' - _description = 'Attachment' - - res_model = fields.Char(required=True) - res_id = fields.Integer(required=True) - name = fields.Char(compute='_compute_name', compute_sudo=True, store=True) - - @api.depends('res_model', 'res_id') - def _compute_name(self): - for rec in self: - rec.name = self.env[rec.res_model].browse(rec.res_id).display_name - - # DLE P55: `test_cache_invalidation` - def modified(self, fnames, create=False): - if not self: - return - comodel = self.env[self.res_model] - if 'res_id' in fnames and 'attachment_ids' in comodel: - field = comodel._fields['attachment_ids'] - record = comodel.browse(self.res_id) - self.env.cache.invalidate([(field, record._ids)]) - record.modified(['attachment_ids']) - return super(Attachment, self).modified(fnames, create) - - -class AttachmentHost(models.Model): - _name = 'test_new_api.attachment.host' - _description = 'Attachment Host' - - attachment_ids = fields.One2many( - 'test_new_api.attachment', 'res_id', auto_join=True, - domain=lambda self: [('res_model', '=', self._name)], - ) - -class DecimalPrecisionTestModel(models.Model): - _name = 'decimal.precision.test' - _description = 'Decimal Precision Test' - - float = fields.Float() - float_2 = fields.Float(digits=(16, 2)) - float_4 = fields.Float(digits=(16, 4)) - - -class ModelA(models.Model): - _name = 'test_new_api.model_a' - _description = 'Model A' - - name = fields.Char() - a_restricted_b_ids = fields.Many2many('test_new_api.model_b', relation='rel_model_a_model_b_1') - b_restricted_b_ids = fields.Many2many('test_new_api.model_b', relation='rel_model_a_model_b_2', ondelete='restrict') - - -class ModelB(models.Model): - _name = 'test_new_api.model_b' - _description = 'Model B' - - name = fields.Char() - a_restricted_a_ids = fields.Many2many('test_new_api.model_a', relation='rel_model_a_model_b_1', ondelete='restrict') - b_restricted_a_ids = fields.Many2many('test_new_api.model_a', relation='rel_model_a_model_b_2') - - -class ModelParent(models.Model): - _name = 'test_new_api.model_parent' - _description = 'Model Multicompany parent' - - name = fields.Char() - company_id = fields.Many2one('res.company', required=True) - - -class ModelChild(models.Model): - _name = 'test_new_api.model_child' - _description = 'Model Multicompany child' - _check_company_auto = True - - name = fields.Char() - company_id = fields.Many2one('res.company', required=True) - parent_id = fields.Many2one('test_new_api.model_parent', check_company=True) - - -class ModelChildNoCheck(models.Model): - _name = 'test_new_api.model_child_nocheck' - _description = 'Model Multicompany child' - _check_company_auto = True - - name = fields.Char() - company_id = fields.Many2one('res.company', required=True) - parent_id = fields.Many2one('test_new_api.model_parent', check_company=False) - - -# model with explicit and stored field 'display_name' -class Display(models.Model): - _name = 'test_new_api.display' - _description = 'Model that overrides display_name' - - display_name = fields.Char(compute='_compute_display_name', store=True) - - def _compute_display_name(self): - for record in self: - record.display_name = 'My id is %s' % (record.id) - - -# abstract model with automatic and non-stored field 'display_name' -class Mixin(models.AbstractModel): - _name = 'test_new_api.mixin' - _description = 'Dummy mixin model' - - -# in this model extension, the field 'display_name' should not be inherited from -# 'test_new_api.mixin' -class ExtendedDisplay(models.Model): - _name = 'test_new_api.display' - _inherit = ['test_new_api.mixin', 'test_new_api.display'] - - -class ModelActiveField(models.Model): - _name = 'test_new_api.model_active_field' - _description = 'A model with active field' - - active = fields.Boolean(default=True) - parent_id = fields.Many2one('test_new_api.model_active_field') - children_ids = fields.One2many('test_new_api.model_active_field', 'parent_id') - parent_active = fields.Boolean(string='Active Parent', related='parent_id.active', store=True) - - -class ModelMany2oneReference(models.Model): - _name = 'test_new_api.model_many2one_reference' - _description = 'dummy m2oref model' - - res_model = fields.Char('Resource Model') - res_id = fields.Many2oneReference('Resource ID', model_field='res_model') - - -class InverseM2oRef(models.Model): - _name = 'test_new_api.inverse_m2o_ref' - _description = 'dummy m2oref inverse model' - - model_ids = fields.One2many('test_new_api.model_many2one_reference', 'res_id', string="Models") - model_ids_count = fields.Integer("Count", compute='_compute_model_ids_count') - - @api.depends('model_ids') - def _compute_model_ids_count(self): - for rec in self: - rec.model_ids_count = len(rec.model_ids) diff --git a/odoo/addons/test_new_api/models/__init__.py b/odoo/addons/test_new_api/models/__init__.py new file mode 100644 index 00000000000..8c9bb6618d1 --- /dev/null +++ b/odoo/addons/test_new_api/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_new_api diff --git a/odoo/addons/test_new_api/models/test_new_api.py b/odoo/addons/test_new_api/models/test_new_api.py new file mode 100644 index 00000000000..3f45466efa3 --- /dev/null +++ b/odoo/addons/test_new_api/models/test_new_api.py @@ -0,0 +1,766 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime + +from odoo import models, fields, api, _ +from odoo.exceptions import AccessError, ValidationError + + +class Category(models.Model): + _name = 'test_new_api.category' + _description = 'Test New API Category' + _order = 'name' + _parent_store = True + _parent_name = 'parent' + + name = fields.Char(required=True) + color = fields.Integer('Color Index') + parent = fields.Many2one('test_new_api.category', ondelete='cascade') + parent_path = fields.Char(index=True) + root_categ = fields.Many2one(_name, compute='_compute_root_categ') + display_name = fields.Char(compute='_compute_display_name', inverse='_inverse_display_name') + dummy = fields.Char(store=False) + discussions = fields.Many2many('test_new_api.discussion', 'test_new_api_discussion_category', + 'category', 'discussion') + + _sql_constraints = [ + ('positive_color', 'CHECK(color >= 0)', 'The color code must be positive !') + ] + + @api.depends('name', 'parent.display_name') # this definition is recursive + def _compute_display_name(self): + for cat in self: + if cat.parent: + cat.display_name = cat.parent.display_name + ' / ' + cat.name + else: + cat.display_name = cat.name + + @api.depends('parent') + def _compute_root_categ(self): + for cat in self: + current = cat + while current.parent: + current = current.parent + cat.root_categ = current + + def _inverse_display_name(self): + for cat in self: + names = cat.display_name.split('/') + # determine sequence of categories + categories = [] + for name in names[:-1]: + category = self.search([('name', 'ilike', name.strip())]) + categories.append(category[0]) + categories.append(cat) + # assign parents following sequence + for parent, child in zip(categories, categories[1:]): + if parent and child: + child.parent = parent + # assign name of last category, and reassign display_name (to normalize it) + cat.name = names[-1].strip() + + def _read(self, fields): + # DLE P45: `test_31_prefetch`, + # with self.assertRaises(AccessError): + # cat1.name + if self.search_count([('id', 'in', self._ids), ('name', '=', 'NOACCESS')]): + raise AccessError('Sorry') + return super(Category, self)._read(fields) + + +class Discussion(models.Model): + _name = 'test_new_api.discussion' + _description = 'Test New API Discussion' + + name = fields.Char(string='Title', required=True, + help="General description of what this discussion is about.") + moderator = fields.Many2one('res.users') + categories = fields.Many2many('test_new_api.category', + 'test_new_api_discussion_category', 'discussion', 'category') + participants = fields.Many2many('res.users', context={'active_test': False}) + messages = fields.One2many('test_new_api.message', 'discussion', copy=True) + message_concat = fields.Text(string='Message concatenate') + important_messages = fields.One2many('test_new_api.message', 'discussion', + domain=[('important', '=', True)]) + very_important_messages = fields.One2many( + 'test_new_api.message', 'discussion', + domain=lambda self: self._domain_very_important()) + emails = fields.One2many('test_new_api.emailmessage', 'discussion') + important_emails = fields.One2many('test_new_api.emailmessage', 'discussion', + domain=[('important', '=', True)]) + + def _domain_very_important(self): + """Ensure computed O2M domains work as expected.""" + return [("important", "=", True)] + + @api.onchange('name') + def _onchange_name(self): + # test onchange modifying one2many field values + if self.env.context.get('generate_dummy_message') and self.name == '{generate_dummy_message}': + # update body of existings messages and emails + for message in self.messages: + message.body = 'not last dummy message' + for message in self.important_messages: + message.body = 'not last dummy message' + # add new dummy message + message_vals = self.messages._add_missing_default_values({'body': 'dummy message', 'important': True}) + self.messages |= self.messages.new(message_vals) + self.important_messages |= self.messages.new(message_vals) + + @api.onchange('moderator') + def _onchange_moderator(self): + self.participants |= self.moderator + + @api.onchange('messages') + def _onchange_messages(self): + self.message_concat = "\n".join(["%s:%s" % (m.name, m.body) for m in self.messages]) + + +class Message(models.Model): + _name = 'test_new_api.message' + _description = 'Test New API Message' + + discussion = fields.Many2one('test_new_api.discussion', ondelete='cascade') + body = fields.Text() + author = fields.Many2one('res.users', default=lambda self: self.env.user) + name = fields.Char(string='Title', compute='_compute_name', store=True) + display_name = fields.Char(string='Abstract', compute='_compute_display_name') + size = fields.Integer(compute='_compute_size', search='_search_size') + double_size = fields.Integer(compute='_compute_double_size') + discussion_name = fields.Char(related='discussion.name', string="Discussion Name", readonly=False) + author_partner = fields.Many2one( + 'res.partner', compute='_compute_author_partner', + search='_search_author_partner') + important = fields.Boolean() + label = fields.Char(translate=True) + priority = fields.Integer() + + @api.constrains('author', 'discussion') + def _check_author(self): + for message in self.with_context(active_test=False): + if message.discussion and message.author not in message.discussion.participants: + raise ValidationError(_("Author must be among the discussion participants.")) + + @api.depends('author.name', 'discussion.name') + def _compute_name(self): + for message in self: + message.name = self._context.get('compute_name', + "[%s] %s" % (message.discussion.name or '', message.author.name or '')) + + @api.constrains('name') + def _check_name(self): + # dummy constraint to check on computed field + for message in self: + if message.name.startswith("[X]"): + raise ValidationError("No way!") + + @api.depends('author.name', 'discussion.name', 'body') + def _compute_display_name(self): + for message in self: + stuff = "[%s] %s: %s" % (message.author.name, message.discussion.name or '', message.body or '') + message.display_name = stuff[:80] + + @api.depends('body') + def _compute_size(self): + for message in self: + message.size = len(message.body or '') + + def _search_size(self, operator, value): + if operator not in ('=', '!=', '<', '<=', '>', '>=', 'in', 'not in'): + return [] + # retrieve all the messages that match with a specific SQL query + query = """SELECT id FROM "%s" WHERE char_length("body") %s %%s""" % \ + (self._table, operator) + self.env.cr.execute(query, (value,)) + ids = [t[0] for t in self.env.cr.fetchall()] + return [('id', 'in', ids)] + + @api.depends('size') + def _compute_double_size(self): + for message in self: + # This illustrates a subtle situation: message.double_size depends + # on message.size. When the latter is computed, message.size is + # assigned, which would normally invalidate message.double_size. + # However, this may not happen while message.double_size is being + # computed: the last statement below would fail, because + # message.double_size would be undefined. + message.double_size = 0 + size = message.size + message.double_size = message.double_size + size + + @api.depends('author', 'author.partner_id') + def _compute_author_partner(self): + for message in self: + message.author_partner = message.author.partner_id + + @api.model + def _search_author_partner(self, operator, value): + return [('author.partner_id', operator, value)] + + def write(self, vals): + if 'priority' in vals: + vals['priority'] = 5 + return super().write(vals) + + +class EmailMessage(models.Model): + _name = 'test_new_api.emailmessage' + _description = 'Test New API Email Message' + _inherits = {'test_new_api.message': 'message'} + + message = fields.Many2one('test_new_api.message', 'Message', + required=True, ondelete='cascade') + email_to = fields.Char('To') + + +class Multi(models.Model): + """ Model for testing multiple onchange methods in cascade that modify a + one2many field several times. + """ + _name = 'test_new_api.multi' + _description = 'Test New API Multi' + + name = fields.Char(related='partner.name', readonly=True) + partner = fields.Many2one('res.partner') + lines = fields.One2many('test_new_api.multi.line', 'multi') + partners = fields.One2many(related='partner.child_ids') + + @api.onchange('name') + def _onchange_name(self): + for line in self.lines: + line.name = self.name + + @api.onchange('partner') + def _onchange_partner(self): + for line in self.lines: + line.partner = self.partner + + +class MultiLine(models.Model): + _name = 'test_new_api.multi.line' + _description = 'Test New API Multi Line' + + multi = fields.Many2one('test_new_api.multi', ondelete='cascade') + name = fields.Char() + partner = fields.Many2one('res.partner') + tags = fields.Many2many('test_new_api.multi.tag') + + +class MultiLine2(models.Model): + _name = 'test_new_api.multi.line2' + _inherit = 'test_new_api.multi.line' + _description = 'Test New API Multi Line 2' + + +class MultiTag(models.Model): + _name = 'test_new_api.multi.tag' + _description = 'Test New API Multi Tag' + + name = fields.Char() + + +class Edition(models.Model): + _name = 'test_new_api.creativework.edition' + _description = 'Test New API Creative Work Edition' + + name = fields.Char() + res_id = fields.Integer(required=True) + res_model_id = fields.Many2one('ir.model', required=True) + res_model = fields.Char(related='res_model_id.model', store=True, readonly=False) + + +class Book(models.Model): + _name = 'test_new_api.creativework.book' + _description = 'Test New API Creative Work Book' + + name = fields.Char() + editions = fields.One2many( + 'test_new_api.creativework.edition', 'res_id', domain=[('res_model', '=', _name)] + ) + + +class Movie(models.Model): + _name = 'test_new_api.creativework.movie' + _description = 'Test New API Creative Work Movie' + + name = fields.Char() + editions = fields.One2many( + 'test_new_api.creativework.edition', 'res_id', domain=[('res_model', '=', _name)] + ) + + +class MixedModel(models.Model): + _name = 'test_new_api.mixed' + _description = 'Test New API Mixed' + + number = fields.Float(digits=(10, 2), default=3.14) + number2 = fields.Float(digits='New API Precision') + date = fields.Date() + moment = fields.Datetime() + now = fields.Datetime(compute='_compute_now') + lang = fields.Selection(string='Language', selection='_get_lang') + reference = fields.Reference(string='Related Document', + selection='_reference_models') + comment1 = fields.Html(sanitize=False) + comment2 = fields.Html(sanitize_attributes=True, strip_classes=False) + comment3 = fields.Html(sanitize_attributes=True, strip_classes=True) + comment4 = fields.Html(sanitize_attributes=True, strip_style=True) + + currency_id = fields.Many2one('res.currency', default=lambda self: self.env.ref('base.EUR')) + amount = fields.Monetary() + + def _compute_now(self): + # this is a non-stored computed field without dependencies + for message in self: + message.now = fields.Datetime.now() + + @api.model + def _get_lang(self): + return self.env['res.lang'].get_installed() + + @api.model + def _reference_models(self): + models = self.env['ir.model'].sudo().search([('state', '!=', 'manual')]) + return [(model.model, model.name) + for model in models + if not model.model.startswith('ir.')] + + +class BoolModel(models.Model): + _name = 'domain.bool' + _description = 'Boolean Domain' + + bool_true = fields.Boolean('b1', default=True) + bool_false = fields.Boolean('b2', default=False) + bool_undefined = fields.Boolean('b3') + + +class Foo(models.Model): + _name = 'test_new_api.foo' + _description = 'Test New API Foo' + + name = fields.Char() + value1 = fields.Integer(change_default=True) + value2 = fields.Integer() + + +class Bar(models.Model): + _name = 'test_new_api.bar' + _description = 'Test New API Bar' + + name = fields.Char() + foo = fields.Many2one('test_new_api.foo', compute='_compute_foo', search='_search_foo') + value1 = fields.Integer(related='foo.value1', readonly=False) + value2 = fields.Integer(related='foo.value2', readonly=False) + + @api.depends('name') + def _compute_foo(self): + for bar in self: + bar.foo = self.env['test_new_api.foo'].search([('name', '=', bar.name)], limit=1) + + def _search_foo(self, operator, value): + assert operator == 'in' + records = self.env['test_new_api.foo'].browse(value) + return [('name', 'in', records.mapped('name'))] + + +class Related(models.Model): + _name = 'test_new_api.related' + _description = 'Test New API Related' + + name = fields.Char() + # related fields with a single field + related_name = fields.Char(related='name', string='A related on Name', readonly=False) + related_related_name = fields.Char(related='related_name', string='A related on a related on Name', readonly=False) + + message = fields.Many2one('test_new_api.message') + message_name = fields.Text(related="message.body", related_sudo=False, string='Message Body') + message_currency = fields.Many2one(related="message.author", string='Message Author') + +class ComputeProtected(models.Model): + _name = 'test_new_api.compute.protected' + _description = 'Test New API Compute Protected' + + foo = fields.Char(default='') + bar = fields.Char(compute='_compute_bar', store=True) + + @api.depends('foo') + def _compute_bar(self): + for record in self: + record.bar = record.foo + +class ComputeInverse(models.Model): + _name = 'test_new_api.compute.inverse' + _description = 'Test New API Compute Inversse' + + foo = fields.Char() + bar = fields.Char(compute='_compute_bar', inverse='_inverse_bar', store=True) + + @api.depends('foo') + def _compute_bar(self): + self._context.get('log', []).append('compute') + for record in self: + record.bar = record.foo + + def _inverse_bar(self): + self._context.get('log', []).append('inverse') + for record in self: + record.foo = record.bar + + +class MultiComputeInverse(models.Model): + """ Model with the same inverse method for several fields. """ + _name = 'test_new_api.multi_compute_inverse' + _description = 'Test New API Multi Compute Inverse' + + foo = fields.Char(default='', required=True) + bar1 = fields.Char(compute='_compute_bars', inverse='_inverse_bar1', store=True) + bar2 = fields.Char(compute='_compute_bars', inverse='_inverse_bar23', store=True) + bar3 = fields.Char(compute='_compute_bars', inverse='_inverse_bar23', store=True) + + @api.depends('foo') + def _compute_bars(self): + self._context.get('log', []).append('compute') + for record in self: + substrs = record.foo.split('/') + ['', '', ''] + record.bar1, record.bar2, record.bar3 = substrs[:3] + + def _inverse_bar1(self): + self._context.get('log', []).append('inverse1') + for record in self: + record.write({'foo': '/'.join([record.bar1, record.bar2, record.bar3])}) + + def _inverse_bar23(self): + self._context.get('log', []).append('inverse23') + for record in self: + record.write({'foo': '/'.join([record.bar1, record.bar2, record.bar3])}) + + +class CompanyDependent(models.Model): + _name = 'test_new_api.company' + _description = 'Test New API Company' + + foo = fields.Char(company_dependent=True) + date = fields.Date(company_dependent=True) + moment = fields.Datetime(company_dependent=True) + tag_id = fields.Many2one('test_new_api.multi.tag', company_dependent=True) + + +class CompanyDependentAttribute(models.Model): + _name = 'test_new_api.company.attr' + _description = 'Test New API Company Attribute' + + company = fields.Many2one('test_new_api.company') + quantity = fields.Integer() + bar = fields.Char(compute='_compute_bar', store=True) + + @api.depends('quantity', 'company.foo') + def _compute_bar(self): + for record in self: + record.bar = (record.company.foo or '') * record.quantity + + +class ComputeRecursive(models.Model): + _name = 'test_new_api.recursive' + _description = 'Test New API Recursive' + + name = fields.Char(required=True) + parent = fields.Many2one('test_new_api.recursive', ondelete='cascade') + display_name = fields.Char(compute='_compute_display_name', store=True) + + @api.depends('name', 'parent.display_name') + def _compute_display_name(self): + for rec in self: + if rec.parent: + rec.display_name = rec.parent.display_name + " / " + rec.name + else: + rec.display_name = rec.name + + +class ComputeCascade(models.Model): + _name = 'test_new_api.cascade' + _description = 'Test New API Cascade' + + foo = fields.Char() + bar = fields.Char(compute='_compute_bar') # depends on foo + baz = fields.Char(compute='_compute_baz', store=True) # depends on bar + + @api.depends('foo') + def _compute_bar(self): + for record in self: + record.bar = "[%s]" % (record.foo or "") + + @api.depends('bar') + def _compute_baz(self): + for record in self: + record.baz = "<%s>" % (record.bar or "") + + +class ComputeOnchange(models.Model): + _name = 'test_new_api.compute.onchange' + _description = "Compute method as an onchange" + + active = fields.Boolean() + foo = fields.Char() + bar = fields.Char(compute='_compute_bar', store=True) + baz = fields.Char(compute='_compute_baz', store=True, readonly=False) + + @api.depends('foo') + def _compute_bar(self): + for record in self: + record.bar = record.foo + + @api.depends('active', 'foo') + def _compute_baz(self): + for record in self: + if record.active: + record.baz = record.foo + + +class ModelBinary(models.Model): + _name = 'test_new_api.model_binary' + _description = 'Test Image field' + + binary = fields.Binary() + binary_related_store = fields.Binary("Binary Related Store", related='binary', store=True, readonly=False) + binary_related_no_store = fields.Binary("Binary Related No Store", related='binary', store=False, readonly=False) + binary_computed = fields.Binary(compute='_compute_binary') + + @api.depends('binary') + def _compute_binary(self): + # arbitrary value: 'bin_size' must have no effect + for record in self: + record.binary_computed = [(record.id, bool(record.binary))] + + +class ModelImage(models.Model): + _name = 'test_new_api.model_image' + _description = 'Test Image field' + + name = fields.Char(required=True) + + image = fields.Image() + image_512 = fields.Image("Image 512", related='image', max_width=512, max_height=512, store=True, readonly=False) + image_256 = fields.Image("Image 256", related='image', max_width=256, max_height=256, store=False, readonly=False) + image_128 = fields.Image("Image 128", max_width=128, max_height=128) + + +class BinarySvg(models.Model): + _name = 'test_new_api.binary_svg' + _description = 'Test SVG upload' + + name = fields.Char(required=True) + image_attachment = fields.Binary(attachment=True) + image_wo_attachment = fields.Binary(attachment=False) + + +class MonetaryBase(models.Model): + _name = 'test_new_api.monetary_base' + _description = 'Monetary Base' + + base_currency_id = fields.Many2one('res.currency') + amount = fields.Monetary(currency_field='base_currency_id') + + +class MonetaryRelated(models.Model): + _name = 'test_new_api.monetary_related' + _description = 'Monetary Related' + + monetary_id = fields.Many2one('test_new_api.monetary_base') + currency_id = fields.Many2one('res.currency', related='monetary_id.base_currency_id') + amount = fields.Monetary(related='monetary_id.amount') + + +class MonetaryCustom(models.Model): + _name = 'test_new_api.monetary_custom' + _description = 'Monetary Related Custom' + + monetary_id = fields.Many2one('test_new_api.monetary_base') + x_currency_id = fields.Many2one('res.currency', related='monetary_id.base_currency_id') + x_amount = fields.Monetary(related='monetary_id.amount') + + +class MonetaryInherits(models.Model): + _name = 'test_new_api.monetary_inherits' + _description = 'Monetary Inherits' + _inherits = {'test_new_api.monetary_base': 'monetary_id'} + + monetary_id = fields.Many2one('test_new_api.monetary_base', required=True, ondelete='cascade') + currency_id = fields.Many2one('res.currency') + + +class FieldWithCaps(models.Model): + _name = 'test_new_api.field_with_caps' + _description = 'Model with field defined with capital letters' + + pArTneR_321_id = fields.Many2one('res.partner') + + +class Selection(models.Model): + _name = 'test_new_api.selection' + _description = "Selection" + + state = fields.Selection([('foo', 'Foo'), ('bar', 'Bar')]) + + +class RequiredM2O(models.Model): + _name = 'test_new_api.req_m2o' + _description = 'Required Many2one' + + foo = fields.Many2one('res.currency', required=True, ondelete='cascade') + bar = fields.Many2one('res.country', required=True) + + +class RequiredM2OTransient(models.TransientModel): + _name = 'test_new_api.req_m2o_transient' + _description = 'Transient Model with Required Many2one' + + foo = fields.Many2one('res.currency', required=True, ondelete='restrict') + bar = fields.Many2one('res.country', required=True) + + +class Attachment(models.Model): + _name = 'test_new_api.attachment' + _description = 'Attachment' + + res_model = fields.Char(required=True) + res_id = fields.Integer(required=True) + name = fields.Char(compute='_compute_name', compute_sudo=True, store=True) + + @api.depends('res_model', 'res_id') + def _compute_name(self): + for rec in self: + rec.name = self.env[rec.res_model].browse(rec.res_id).display_name + + # DLE P55: `test_cache_invalidation` + def modified(self, fnames, create=False): + if not self: + return + comodel = self.env[self.res_model] + if 'res_id' in fnames and 'attachment_ids' in comodel: + field = comodel._fields['attachment_ids'] + record = comodel.browse(self.res_id) + self.env.cache.invalidate([(field, record._ids)]) + record.modified(['attachment_ids']) + return super(Attachment, self).modified(fnames, create) + + +class AttachmentHost(models.Model): + _name = 'test_new_api.attachment.host' + _description = 'Attachment Host' + + attachment_ids = fields.One2many( + 'test_new_api.attachment', 'res_id', auto_join=True, + domain=lambda self: [('res_model', '=', self._name)], + ) + +class DecimalPrecisionTestModel(models.Model): + _name = 'decimal.precision.test' + _description = 'Decimal Precision Test' + + float = fields.Float() + float_2 = fields.Float(digits=(16, 2)) + float_4 = fields.Float(digits=(16, 4)) + + +class ModelA(models.Model): + _name = 'test_new_api.model_a' + _description = 'Model A' + + name = fields.Char() + a_restricted_b_ids = fields.Many2many('test_new_api.model_b', relation='rel_model_a_model_b_1') + b_restricted_b_ids = fields.Many2many('test_new_api.model_b', relation='rel_model_a_model_b_2', ondelete='restrict') + + +class ModelB(models.Model): + _name = 'test_new_api.model_b' + _description = 'Model B' + + name = fields.Char() + a_restricted_a_ids = fields.Many2many('test_new_api.model_a', relation='rel_model_a_model_b_1', ondelete='restrict') + b_restricted_a_ids = fields.Many2many('test_new_api.model_a', relation='rel_model_a_model_b_2') + + +class ModelParent(models.Model): + _name = 'test_new_api.model_parent' + _description = 'Model Multicompany parent' + + name = fields.Char() + company_id = fields.Many2one('res.company', required=True) + + +class ModelChild(models.Model): + _name = 'test_new_api.model_child' + _description = 'Model Multicompany child' + _check_company_auto = True + + name = fields.Char() + company_id = fields.Many2one('res.company', required=True) + parent_id = fields.Many2one('test_new_api.model_parent', check_company=True) + + +class ModelChildNoCheck(models.Model): + _name = 'test_new_api.model_child_nocheck' + _description = 'Model Multicompany child' + _check_company_auto = True + + name = fields.Char() + company_id = fields.Many2one('res.company', required=True) + parent_id = fields.Many2one('test_new_api.model_parent', check_company=False) + + +# model with explicit and stored field 'display_name' +class Display(models.Model): + _name = 'test_new_api.display' + _description = 'Model that overrides display_name' + + display_name = fields.Char(compute='_compute_display_name', store=True) + + def _compute_display_name(self): + for record in self: + record.display_name = 'My id is %s' % (record.id) + + +# abstract model with automatic and non-stored field 'display_name' +class Mixin(models.AbstractModel): + _name = 'test_new_api.mixin' + _description = 'Dummy mixin model' + + +# in this model extension, the field 'display_name' should not be inherited from +# 'test_new_api.mixin' +class ExtendedDisplay(models.Model): + _name = 'test_new_api.display' + _inherit = ['test_new_api.mixin', 'test_new_api.display'] + + +class ModelActiveField(models.Model): + _name = 'test_new_api.model_active_field' + _description = 'A model with active field' + + active = fields.Boolean(default=True) + parent_id = fields.Many2one('test_new_api.model_active_field') + children_ids = fields.One2many('test_new_api.model_active_field', 'parent_id') + parent_active = fields.Boolean(string='Active Parent', related='parent_id.active', store=True) + + +class ModelMany2oneReference(models.Model): + _name = 'test_new_api.model_many2one_reference' + _description = 'dummy m2oref model' + + res_model = fields.Char('Resource Model') + res_id = fields.Many2oneReference('Resource ID', model_field='res_model') + + +class InverseM2oRef(models.Model): + _name = 'test_new_api.inverse_m2o_ref' + _description = 'dummy m2oref inverse model' + + model_ids = fields.One2many('test_new_api.model_many2one_reference', 'res_id', string="Models") + model_ids_count = fields.Integer("Count", compute='_compute_model_ids_count') + + @api.depends('model_ids') + def _compute_model_ids_count(self): + for rec in self: + rec.model_ids_count = len(rec.model_ids) diff --git a/odoo/addons/test_new_api/security/ir.model.access.csv b/odoo/addons/test_new_api/security/ir.model.access.csv new file mode 100644 index 00000000000..b676e85dd0d --- /dev/null +++ b/odoo/addons/test_new_api/security/ir.model.access.csv @@ -0,0 +1,47 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1 +access_decimal_precision_test_all,decimal.precision.test,model_decimal_precision_test,,1,1,1,1 +access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1 +access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1 +access_emailmessage,test_new_api_emailmessage,test_new_api.model_test_new_api_emailmessage,,1,1,1,1 +access_multi,test_new_api_multi,test_new_api.model_test_new_api_multi,,1,1,1,1 +access_multi_line,test_new_api_multi_line,test_new_api.model_test_new_api_multi_line,,1,1,1,1 +access_multi_line2,test_new_api_multi_line2,test_new_api.model_test_new_api_multi_line2,,1,1,1,1 +access_multi_tag,test_new_api_multi_tag,test_new_api.model_test_new_api_multi_tag,,1,1,1,1 +access_creativework_edition,test_new_api_creativework_edition,model_test_new_api_creativework_edition,,1,1,1,1 +access_creativework_book,test_new_api_creativework_book,model_test_new_api_creativework_book,,1,1,1,1 +access_creativework_movie,test_new_api_creativework_movie,model_test_new_api_creativework_movie,,1,1,1,1 +access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1 +access_domain_bool,access_domain_bool,model_domain_bool,,1,1,1,1 +access_test_new_api_foo,access_test_new_api_foo,model_test_new_api_foo,,1,1,1,1 +access_test_new_api_bar,access_test_new_api_bar,model_test_new_api_bar,,1,1,1,1 +access_test_new_api_related,access_test_new_api_related,model_test_new_api_related,,1,1,1,1 +access_test_new_api_company,access_test_new_api_company,model_test_new_api_company,,1,1,1,1 +access_test_new_api_company_attr,access_test_new_api_company_attr,model_test_new_api_company_attr,,1,1,1,1 +access_test_new_api_compute_inverse,access_test_new_api_compute_inverse,model_test_new_api_compute_inverse,,1,1,1,1 +access_test_new_api_compute_protected,access_test_new_api_compute_protected,model_test_new_api_compute_protected,,1,1,1,1 +access_test_new_api_multi_compute_inverse,access_test_new_api_multi_compute_inverse,model_test_new_api_multi_compute_inverse,,1,1,1,1 +access_test_new_api_recursive,access_test_new_api_recursive,model_test_new_api_recursive,,1,1,1,1 +access_test_new_api_cascade,access_test_new_api_cascade,model_test_new_api_cascade,,1,1,1,1 +access_test_new_api_compute_onchange,access_test_new_api_compute_onchange,model_test_new_api_compute_onchange,,1,1,1,1 +access_test_new_api_binary_svg,access_test_new_api_binary_svg,model_test_new_api_binary_svg,,1,1,1,1 +access_test_new_api_monetary_base,access_test_new_api_monetary_base,model_test_new_api_monetary_base,,1,1,1,1 +access_test_new_api_monetary_related,access_test_new_api_monetary_related,model_test_new_api_monetary_related,,1,1,1,1 +access_test_new_api_monetary_custom,access_test_new_api_monetary_custom,model_test_new_api_monetary_custom,,1,1,1,1 +access_test_new_api_monetary_inherits,access_test_new_api_monetary_inherits,model_test_new_api_monetary_inherits,,1,1,1,1 +access_test_new_api_field_with_caps,access_test_new_api_field_with_caps,model_test_new_api_field_with_caps,,1,1,1,1 +access_test_new_api_req_m2o,access_test_new_api_req_m2o,model_test_new_api_req_m2o,,1,1,1,1 +access_test_new_api_selection,access_test_new_api_selection,model_test_new_api_selection,,1,1,1,1 +access_test_new_api_attachment,access_test_new_api_attachment,model_test_new_api_attachment,,1,1,1,1 +access_test_new_api_attachment_host,access_test_new_api_attachment_host,model_test_new_api_attachment_host,,1,1,1,1 +access_test_new_api_model_image,access_test_new_api_model_image,model_test_new_api_model_image,,1,1,1,1 +access_test_new_api_model_a,access_test_new_api_model_a,model_test_new_api_model_a,,1,1,1,1 +access_test_new_api_model_b,access_test_new_api_model_b,model_test_new_api_model_b,,1,1,1,1 +access_test_new_api_model_parent,access_test_new_api_model_parent,model_test_new_api_model_parent,,1,1,1,1 +access_test_new_api_model_child,access_test_new_api_model_child,model_test_new_api_model_child,,1,1,1,1 +access_test_new_api_model_child_nocheck,access_test_new_api_model_child_nocheck,model_test_new_api_model_child_nocheck,,1,1,1,1 +access_test_new_api_display,access_test_new_api_display,model_test_new_api_display,,1,1,1,1 +access_test_new_api_model_active_field,access_test_new_api_model_active_field,model_test_new_api_model_active_field,,1,1,1,1 +access_test_new_api_model_many2one_reference,access_test_new_api_model_many2one_reference,model_test_new_api_model_many2one_reference,,1,1,1,1 +access_test_new_api_inverse_m2o_ref,access_test_new_api_inverse_m2o_ref,model_test_new_api_inverse_m2o_ref,,1,1,1,1 +access_test_new_api_model_binary,access_test_new_api_model_binary,model_test_new_api_model_binary,,1,1,1,1 diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 7622ec7e5a1..bb7c4ccbc1b 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + # # test cases for new-style fields # @@ -9,6 +12,7 @@ from PIL import Image import psycopg2 from odoo import fields +from odoo.addons.base.tests.common import TransactionCaseWithUserDemo from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tests import common from odoo.tools import mute_logger, float_repr @@ -16,7 +20,16 @@ from odoo.tools.date_utils import add, subtract, start_of, end_of from odoo.tools.image import image_data_uri -class TestFields(common.TransactionCase): +class TestFields(TransactionCaseWithUserDemo): + + def setUp(self): + super(TestFields, self).setUp() + self.env.ref('test_new_api.discussion_0').write({'participants': [(4, self.user_demo.id)]}) + # YTI FIX ME: The cache shouldn't be inconsistent (rco is gonna fix it) + # self.env.ref('test_new_api.discussion_0').participants -> 1 user + # self.env.ref('test_new_api.discussion_0').invalidate_cache() + # self.env.ref('test_new_api.discussion_0').with_context(active_test=False).participants -> 2 users + self.env.ref('test_new_api.message_0_1').write({'author': self.user_demo.id}) def test_00_basics(self): """ test accessing new fields """ @@ -110,6 +123,9 @@ class TestFields(common.TransactionCase): def test_10_computed_custom(self): """ check definition of custom computed fields """ + # Flush demo user before creating a new ir.model.fields to avoid + # a deadlock + self.user_demo.flush() self.env['ir.model.fields'].create({ 'name': 'x_bool_false_computed', 'model_id': self.env.ref('test_new_api.model_test_new_api_message').id, @@ -148,7 +164,7 @@ class TestFields(common.TransactionCase): # create a message, assign body, and check size in several environments message1 = self.env['test_new_api.message'].create({}) - message2 = message1.with_user(self.env.ref('base.user_demo')) + message2 = message1.with_user(self.user_demo) self.assertEqual(message1.size, 0) self.assertEqual(message2.size, 0) @@ -177,6 +193,10 @@ class TestFields(common.TransactionCase): check_stored(discussion1) # switch message from discussion, and check again + + # See YTI FIXME + discussion1.invalidate_cache() + discussion2 = discussion1.copy({'name': 'Another discussion'}) message2 = discussion1.messages[0] message2.discussion = discussion2 @@ -184,7 +204,7 @@ class TestFields(common.TransactionCase): # create a new discussion with messages, and check their name user_root = self.env.ref('base.user_root') - user_demo = self.env.ref('base.user_demo') + user_demo = self.user_demo discussion3 = self.env['test_new_api.discussion'].create({ 'name': 'Stuff', 'participants': [(4, user_root.id), (4, user_demo.id)], @@ -265,7 +285,7 @@ class TestFields(common.TransactionCase): # We need to force the read in order to test the security access User.invalidate_cache() # group users as a recordset, and read them as user demo - users = (user1 + user2 + user3).with_user(self.env.ref('base.user_demo')) + users = (user1 + user2 + user3).with_user(self.user_demo) user1, user2, user3 = users # regression test: a bug invalidated the field's value from cache user1.company_type @@ -480,6 +500,7 @@ class TestFields(common.TransactionCase): def test_15_constraint(self): """ test new-style Python constraints """ discussion = self.env.ref('test_new_api.discussion_0') + discussion.flush() # remove oneself from discussion participants: we can no longer create # messages in discussion @@ -773,7 +794,7 @@ class TestFields(common.TransactionCase): def test_23_relation(self): """ test relation fields """ - demo = self.env.ref('base.user_demo') + demo = self.user_demo message = self.env.ref('test_new_api.message_0_0') # check environment of record and related records @@ -792,6 +813,9 @@ class TestFields(common.TransactionCase): self.assertEqual(demo_message.env, demo_env) self.assertEqual(demo_message.discussion.env, demo_env) + # See YTI FIXME + message.discussion.invalidate_cache() + # assign record's parent to a record in demo_env message.discussion = message.discussion.copy({'name': 'Copy'}) @@ -1249,7 +1273,7 @@ class TestFields(common.TransactionCase): access.write({'perm_read': False}) # create an environment for demo user - env = self.env(user=self.env.ref('base.user_demo')) + env = self.env(user=self.user_demo) self.assertEqual(env.user.login, "demo") # create a new message as demo user @@ -1274,7 +1298,7 @@ class TestFields(common.TransactionCase): access.write({'perm_read': False}) # create an environment for demo user - env = self.env(user=self.env.ref('base.user_demo')) + env = self.env(user=self.user_demo) self.assertEqual(env.user.login, "demo") # create a new discussion and a new message as demo user @@ -1328,6 +1352,9 @@ class TestFields(common.TransactionCase): def test_70_x2many_write(self): discussion = self.env.ref('test_new_api.discussion_0') + # See YTI FIXME + discussion.invalidate_cache() + Message = self.env['test_new_api.message'] # There must be 3 messages, 0 important self.assertEqual(len(discussion.messages), 3) @@ -1353,11 +1380,15 @@ class TestFields(common.TransactionCase): def test_70_relational_inverse(self): """ Check the consistency of relational fields with inverse(s). """ discussion = self.env.ref('test_new_api.discussion_0') - demo_discussion = discussion.with_user(self.env.ref('base.user_demo')) + demo_discussion = discussion.with_user(self.user_demo) # check that the demo user sees the same messages self.assertEqual(demo_discussion.messages, discussion.messages) + # See YTI FIXME + discussion.invalidate_cache() + demo_discussion.invalidate_cache() + # add a message as user demo messages = demo_discussion.messages message = messages.create({'discussion': discussion.id}) @@ -1413,7 +1444,7 @@ class TestFields(common.TransactionCase): # And this gives error with self.assertRaises(UserError): self.env['test_new_api.binary_svg'].with_user( - self.env.ref('base.user_demo'), + self.user_demo, ).create({ 'name': 'Test without attachment', 'image_wo_attachment': SVG, @@ -1434,7 +1465,7 @@ class TestFields(common.TransactionCase): self.assertEqual(attachment.mimetype, 'image/svg+xml') # ...but this should be neutered with demo user record = self.env['test_new_api.binary_svg'].with_user( - self.env.ref('base.user_demo'), + self.user_demo, ).create({ 'name': 'Test without attachment', 'image_attachment': SVG, @@ -1448,7 +1479,7 @@ class TestFields(common.TransactionCase): def test_92_binary_self_avatar_svg(self): from odoo.addons.base.tests.test_mimetypes import SVG - demo_user = self.env.ref('base.user_demo') + demo_user = self.user_demo # User demo changes his own avatar demo_user.with_user(demo_user).image_1920 = SVG # The SVG file should have been neutered diff --git a/odoo/addons/test_new_api/tests/test_onchange.py b/odoo/addons/test_new_api/tests/test_onchange.py index 2359acd78fe..5d544bd2875 100644 --- a/odoo/addons/test_new_api/tests/test_onchange.py +++ b/odoo/addons/test_new_api/tests/test_onchange.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + try: from unittest.mock import patch except ImportError: from mock import patch +from odoo.addons.base.tests.common import SavepointCaseWithUserDemo from odoo.tests import common def strip_prefix(prefix, names): size = len(prefix) return [name[size:] for name in names if name.startswith(prefix)] -class TestOnChange(common.TransactionCase): +class TestOnChange(SavepointCaseWithUserDemo): def setUp(self): super(TestOnChange, self).setUp() @@ -229,7 +232,7 @@ class TestOnChange(common.TransactionCase): def test_onchange_one2many_multi(self): """ test the effect of multiple onchange methods on one2many fields """ - partner1 = self.env.ref('base.res_partner_1') + partner1 = self.env['res.partner'].create({'name': 'A partner'}) multi = self.env['test_new_api.multi'].create({'partner': partner1.id}) line1 = multi.lines.create({'multi': multi.id}) @@ -255,7 +258,7 @@ class TestOnChange(common.TransactionCase): # -> set 'partner' on all lines # -> recompute 'name' # -> set 'name' on all lines - partner2 = self.env.ref('base.res_partner_2') + partner2 = self.env['res.partner'].create({'name': 'A second partner'}) values = { 'name': partner1.name, 'partner': partner2.id, # this one just changed @@ -326,7 +329,7 @@ class TestOnChange(common.TransactionCase): def test_onchange_specific(self): """ test the effect of field-specific onchange method """ discussion = self.env.ref('test_new_api.discussion_0') - demo = self.env.ref('base.user_demo') + demo = self.user_demo field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('moderator'), '1') @@ -381,7 +384,7 @@ class TestOnChange(common.TransactionCase): def test_onchange_one2many_value(self): """ test the value of the one2many field inside the onchange """ discussion = self.env.ref('test_new_api.discussion_0') - demo = self.env.ref('base.user_demo') + demo = self.user_demo field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('messages'), '1') @@ -406,7 +409,7 @@ class TestOnChange(common.TransactionCase): def test_onchange_one2many_with_domain_on_related_field(self): """ test the value of the one2many field when defined with a domain on a related field""" discussion = self.env.ref('test_new_api.discussion_0') - demo = self.env.ref('base.user_demo') + demo = self.user_demo # mimic UI behaviour, so we get subfields # (we need at least subfield: 'important_emails.important') @@ -489,7 +492,7 @@ class TestOnChange(common.TransactionCase): self.assertEqual(result['value'], onchange_result) self.env.cache.invalidate() - Message = self.env(user=self.env.ref('base.user_demo').id)['test_new_api.related'] + Message = self.env(user=self.user_demo.id)['test_new_api.related'] result = Message.onchange(value, ['message', 'message_name', 'message_currency'], field_onchange) self.assertEqual(result['value'], onchange_result) diff --git a/odoo/addons/test_new_api/tests/test_ui.py b/odoo/addons/test_new_api/tests/test_ui.py index 6fa360ac9ae..979bf8064a9 100644 --- a/odoo/addons/test_new_api/tests/test_ui.py +++ b/odoo/addons/test_new_api/tests/test_ui.py @@ -1,9 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + import odoo.tests from odoo.tools import mute_logger +from odoo.addons.base.tests.common import HttpCaseWithUserDemo @odoo.tests.common.tagged('post_install', '-at_install') -class TestUi(odoo.tests.HttpCase): +class TestUi(HttpCaseWithUserDemo): def test_01_admin_widget_x2many(self): # FIXME: breaks if too many children of base.menu_tests diff --git a/odoo/addons/test_new_api/views.xml b/odoo/addons/test_new_api/views.xml deleted file mode 100644 index 830210c4d87..00000000000 --- a/odoo/addons/test_new_api/views.xml +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - - Discussions - test_new_api.discussion - tree,form - - - - - Messages - test_new_api.message - tree,form - - - - - Multi - test_new_api.multi - tree,form - - - - - - - Categories - test_new_api.category - tree,form - - - - - - discussion form view - test_new_api.discussion - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - -
- - - - - - - -
- - - -
- -
-
- - - - discussion form view - test_new_api.discussion - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - Discussions 2 - test_new_api.discussion - - - - - - - discussion form view - test_new_api.discussion - - -
- - - - - - - - - - - - - - - -
-
-
- - - Discussions 3 - test_new_api.discussion - - - - - - - - message tree view - test_new_api.message - - - - - - - - - - - - - message form view - test_new_api.message - -
- - - - - - - - -
-
-
- - - - category tree view - test_new_api.category - - - - - - - - - - category form view - test_new_api.category - -
- - - - - - - - - -
-
-
- - - - multi tree view - test_new_api.multi - - - - - - - - - - multi form view - test_new_api.multi - -
- - - - - -
- -
-
-
-
diff --git a/odoo/addons/test_new_api/views/test_new_api_templates.xml b/odoo/addons/test_new_api/views/test_new_api_templates.xml new file mode 100644 index 00000000000..8bd75bceab5 --- /dev/null +++ b/odoo/addons/test_new_api/views/test_new_api_templates.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/odoo/addons/test_new_api/views/test_new_api_views.xml b/odoo/addons/test_new_api/views/test_new_api_views.xml new file mode 100644 index 00000000000..830210c4d87 --- /dev/null +++ b/odoo/addons/test_new_api/views/test_new_api_views.xml @@ -0,0 +1,300 @@ + + + + + + + + Discussions + test_new_api.discussion + tree,form + + + + + Messages + test_new_api.message + tree,form + + + + + Multi + test_new_api.multi + tree,form + + + + + + + Categories + test_new_api.category + tree,form + + + + + + discussion form view + test_new_api.discussion + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + +
+ + + +
+ +
+
+ + + + discussion form view + test_new_api.discussion + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Discussions 2 + test_new_api.discussion + + + + + + + discussion form view + test_new_api.discussion + + +
+ + + + + + + + + + + + + + + +
+
+
+ + + Discussions 3 + test_new_api.discussion + + + + + + + + message tree view + test_new_api.message + + + + + + + + + + + + + message form view + test_new_api.message + +
+ + + + + + + + +
+
+
+ + + + category tree view + test_new_api.category + + + + + + + + + + category form view + test_new_api.category + +
+ + + + + + + + + +
+
+
+ + + + multi tree view + test_new_api.multi + + + + + + + + + + multi form view + test_new_api.multi + +
+ + + + + +
+ +
+
+
+
diff --git a/odoo/addons/test_performance/__manifest__.py b/odoo/addons/test_performance/__manifest__.py index ec3daa51a3f..e33ff4d4328 100644 --- a/odoo/addons/test_performance/__manifest__.py +++ b/odoo/addons/test_performance/__manifest__.py @@ -7,7 +7,4 @@ 'data': [ 'security/ir.model.access.csv', ], - 'demo': [ - 'demo/demo.xml', - ], } diff --git a/odoo/addons/test_performance/demo/demo.xml b/odoo/addons/test_performance/demo/demo.xml deleted file mode 100644 index 327e9082319..00000000000 --- a/odoo/addons/test_performance/demo/demo.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Object 0 - 0 - - - - Object 1 - 10 - - - - Object 2 - 20 - - - - Object 3 - 30 - - - - Object 4 - 40 - - - - diff --git a/odoo/addons/test_performance/tests/test_performance.py b/odoo/addons/test_performance/tests/test_performance.py index 1db2e33f6d2..eaae5f41f10 100644 --- a/odoo/addons/test_performance/tests/test_performance.py +++ b/odoo/addons/test_performance/tests/test_performance.py @@ -4,11 +4,44 @@ from collections import defaultdict import json +from odoo.addons.base.tests.common import SavepointCaseWithUserDemo from odoo.tests.common import TransactionCase, users, warmup, tagged from odoo.tools import mute_logger -class TestPerformance(TransactionCase): +class TestPerformance(SavepointCaseWithUserDemo): + + @classmethod + def setUpClass(cls): + super(TestPerformance, cls).setUpClass() + cls._load_partners_set() + + partner3 = cls.env['res.partner'].search([('name', '=', 'AnalytIQ')], limit=1) + partner4 = cls.env['res.partner'].search([('name', '=', 'Urban Trends')], limit=1) + partner10 = cls.env['res.partner'].search([('name', '=', 'Ctrl-Alt-Fix')], limit=1) + partner12 = cls.env['res.partner'].search([('name', '=', 'Ignitive Labs')], limit=1) + + cls.env['test_performance.base'].create([{ + 'name': 'Object 0', + 'value': 0, + 'partner_id': partner3.id, + }, { + 'name': 'Object 1', + 'value': 10, + 'partner_id': partner3.id, + }, { + 'name': 'Object 2', + 'value': 20, + 'partner_id': partner4.id, + }, { + 'name': 'Object 3', + 'value': 30, + 'partner_id': partner10.id, + }, { + 'name': 'Object 4', + 'value': 40, + 'partner_id': partner12.id, + }]) @users('__system__', 'demo') @warmup @@ -377,9 +410,10 @@ class TestPerformance(TransactionCase): def expected_read_group(self): groups = defaultdict(list) - for record in self.env['test_performance.base'].search([]): + all_records = self.env['test_performance.base'].search([]) + for record in all_records: groups[record.partner_id.id].append(record.value) - partners = self.env['res.partner'].search([('id', 'in', list(groups))]) + partners = self.env['res.partner'].search([('id', 'in', all_records.mapped('partner_id').ids)]) return [{ '__domain': [('partner_id', '=', partner.id)], 'partner_id': (partner.id, partner.display_name), -- cgit v1.2.3