diff options
| author | Pierre Masereel <pim@odoo.com> | 2019-11-15 15:38:48 +0000 |
|---|---|---|
| committer | Raphael Collet <rco@odoo.com> | 2019-11-22 12:34:43 +0000 |
| commit | e85faf398659a5beb0b1570a06af64dcf78dc1c8 (patch) | |
| tree | 2726de4e47cb5f61bfd748416f85f3fadfb6bf7d | |
| parent | 8821f0e9176e3b976828f088ce5b7f5c6517b0c7 (diff) | |
[IMP] base: add unique constraint on ir.property
There is no unique constraint on the properties to avoid having two
properties for the same field, company and res_id.
Having two properties for the same field, company and res_id, leads to
inconsistencies through the code, as reading a company-dependent field
nondeterministically returns one of the available property value.
So now, before creating a property, we have to check that there is not
already a value for the field, company and res_id and write or create
depending on if it is already exists.
The properties of specific records already satisfy the constraint thanks
to the implementation of company-dependent fields that use the method
`set_multi`. We added a method `set_default` to set generic properties,
and its implementation does the right thing. It also simplifies the
code to set such properties, by the way.
closes odoo/odoo#40473
Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
Co-authored-by: Raphael Collet <rco@odoo.com>
| -rw-r--r-- | addons/account/tests/common.py | 32 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_frontend.py | 12 | ||||
| -rw-r--r-- | addons/pos_restaurant/tests/test_frontend.py | 13 | ||||
| -rw-r--r-- | addons/product/models/res_company.py | 28 | ||||
| -rw-r--r-- | addons/stock_account/__init__.py | 65 | ||||
| -rw-r--r-- | addons/stock_account/tests/common.py | 23 | ||||
| -rw-r--r-- | addons/test_website_slides_full/tests/test_ui_wslides.py | 17 | ||||
| -rw-r--r-- | odoo/addons/base/models/ir_property.py | 38 | ||||
| -rw-r--r-- | odoo/addons/test_main_flows/tests/test_flow.py | 33 | ||||
| -rw-r--r-- | odoo/addons/test_new_api/tests/test_new_fields.py | 11 |
10 files changed, 114 insertions, 158 deletions
diff --git a/addons/account/tests/common.py b/addons/account/tests/common.py index c2ea7307021..b3362bf9c43 100644 --- a/addons/account/tests/common.py +++ b/addons/account/tests/common.py @@ -206,32 +206,12 @@ class AccountTestCommon(SavepointCase): }) # Properties: Product income and expense accounts, default parameters - cls.env['ir.property'].create([{ - 'name': 'property_account_receivable_id', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_account_receivable_id')], limit=1).id, - 'value': 'account.account,%s' % (cls.a_recv.id), - 'company_id': cls.company.id, - }, { - 'name': 'property_account_payable_id', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_account_payable_id')], limit=1).id, - 'value': 'account.account,%s' % (cls.a_pay.id), - 'company_id': cls.company.id, - }, { - 'name': 'property_account_position_id', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_account_position_id')], limit=1).id, - 'value': False, - 'company_id': cls.company.id, - }, { - 'name': 'property_account_expense_categ_id', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_account_expense_categ_id')], limit=1).id, - 'value': 'account.account,%s' % (cls.a_expense.id), - 'company_id': cls.company.id, - }, { - 'name': 'property_account_income_categ_id', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_account_income_categ_id')], limit=1).id, - 'value': 'account.account,%s' % (cls.a_sale.id), - 'company_id': cls.company.id, - }]) + Property = cls.env['ir.property'] + Property.set_default('property_account_receivable_id', 'res.partner', cls.a_recv, cls.company) + Property.set_default('property_account_payable_id', 'res.partner', cls.a_pay, cls.company) + Property.set_default('property_account_position_id', 'res.partner', False, cls.company) + Property.set_default('property_account_expense_categ_id', 'product.category', cls.a_expense, cls.company) + Property.set_default('property_account_income_categ_id', 'product.category', cls.a_sale, cls.company) # Bank Accounts cls.bank_account = cls.env['res.partner.bank'].create({ diff --git a/addons/point_of_sale/tests/test_frontend.py b/addons/point_of_sale/tests/test_frontend.py index 1b323572ff4..02447e04ab4 100644 --- a/addons/point_of_sale/tests/test_frontend.py +++ b/addons/point_of_sale/tests/test_frontend.py @@ -29,11 +29,13 @@ class TestPointOfSaleHttpCommon(odoo.tests.HttpCase): 'user_type_id': env.ref('account.data_account_type_receivable').id, 'reconcile': True}) self.env.company.account_default_pos_receivable_account_id = account_receivable - field = env['ir.model.fields']._get('res.partner', 'property_account_receivable_id') - env['ir.property'].create({'name': 'property_account_receivable_id', - 'company_id': main_company.id, - 'fields_id': field.id, - 'value': 'account.account,' + str(account_receivable.id)}) + + self.env['ir.property'].set_default( + 'property_account_receivable_id', + 'res.partner', + account_receivable, + main_company, + ) cash_journal = journal_obj.create({ 'name': 'Cash Test', diff --git a/addons/pos_restaurant/tests/test_frontend.py b/addons/pos_restaurant/tests/test_frontend.py index b4d5a8b3ed8..03120b757c6 100644 --- a/addons/pos_restaurant/tests/test_frontend.py +++ b/addons/pos_restaurant/tests/test_frontend.py @@ -56,11 +56,14 @@ class TestFrontend(odoo.tests.HttpCase): 'name': 'Account Receivable - Test', 'user_type_id': env.ref('account.data_account_type_receivable').id, 'reconcile': True}) - field = env['ir.model.fields']._get('res.partner', 'property_account_receivable_id') - env['ir.property'].create({'name': 'property_account_receivable_id', - 'company_id': main_company.id, - 'fields_id': field.id, - 'value': 'account.account,' + str(account_receivable.id)}) + + self.env['ir.property'].set_default( + 'property_account_receivable_id', + 'res.partner', + account_receivable, + main_company, + ) + test_sale_journal = env['account.journal'].create({ 'name': 'Sales Journal - Test', 'code': 'TSJ', diff --git a/addons/product/models/res_company.py b/addons/product/models/res_company.py index 5de5704aed4..94e053eda23 100644 --- a/addons/product/models/res_company.py +++ b/addons/product/models/res_company.py @@ -18,13 +18,12 @@ class ResCompany(models.Model): 'name': _("Default %(currency)s pricelist") % params, 'currency_id': new_company.currency_id.id, }) - field = self.env['ir.model.fields']._get('res.partner', 'property_product_pricelist') - self.env['ir.property'].sudo().create({ - 'name': 'property_product_pricelist', - 'value_reference': 'product.pricelist,%s' % pricelist.id, - 'fields_id': field.id, - 'company_id': new_company.id, - }) + self.env['ir.property'].sudo().set_default( + 'property_product_pricelist', + 'res.partner', + pricelist, + new_company, + ) return new_company def write(self, values): @@ -38,7 +37,7 @@ class ResCompany(models.Model): nb_companies = self.search_count([]) for company in self: existing_pricelist = ProductPricelist.search( - [('company_id', 'in', (False, company.id)), + [('company_id', 'in', (False, company.id)), ('currency_id', '=', currency_id)]) if existing_pricelist: continue @@ -55,11 +54,10 @@ class ResCompany(models.Model): 'name': _("Default %(currency)s pricelist") % params, 'currency_id': currency_id, }) - field = self.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_product_pricelist')]) - self.env['ir.property'].sudo().create({ - 'name': 'property_product_pricelist', - 'company_id': company.id, - 'value_reference': 'product.pricelist,%s' % pricelist.id, - 'fields_id': field.id - }) + self.env['ir.property'].sudo().set_default( + 'property_product_pricelist', + 'res.partner', + pricelist, + company, + ) return super(ResCompany, self).write(values) diff --git a/addons/stock_account/__init__.py b/addons/stock_account/__init__.py index 74f33e2e2b7..e0fb76fd2d7 100644 --- a/addons/stock_account/__init__.py +++ b/addons/stock_account/__init__.py @@ -34,16 +34,12 @@ def _configure_journals(cr, registry): 'company_id': company_id.id, 'show_on_dashboard': False }).id - vals = { - 'name': 'property_stock_journal', - 'fields_id': env['ir.model.fields'].search([ - ('name', '=', 'property_stock_journal'), - ('model', '=', 'product.category'), - ('relation', '=', 'account.journal')], limit=1).id, - 'company_id': company_id.id, - 'value': 'account.journal,' + str(journal_id) - } - env['ir.property'].create(vals) + env['ir.property'].set_default( + 'property_stock_journal', + 'product.category', + journal_id, + company_id, + ) # Property Stock Accounts todo_list = [ @@ -52,43 +48,12 @@ def _configure_journals(cr, registry): 'property_stock_valuation_account_id', ] - for record in todo_list: - account = getattr(company_id, record) - value = account and 'account.account,' + str(account.id) or False - if value: - field_id = env['ir.model.fields'].search([ - ('name', '=', record), - ('model', '=', 'product.category'), - ('relation', '=', 'account.account') - ], limit=1).id - vals = { - 'name': record, - 'company_id': company_id.id, - 'fields_id': field_id, - 'value': value, - } - properties = env['ir.property'].search([ - ('name', '=', record), - ('company_id', '=', company_id.id), - ]) - if properties: - properties.write(vals) - else: - # create the property - env['ir.property'].create(vals) - - stock_account = env.ref('account.demo_stock_account', False) - if stock_account: - account_id = env['account.account'].search([('tag_ids', '=', stock_account.id)], limit=1).id - fields_id = env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_stock_valuation_account_id')], limit=1).id - if not account_id: - account_id = env['account.account'].search([('user_type_id', '=', env.ref('account.data_account_type_current_assets').id)], limit=1).id - if account_id: - xml_id = 'stock_account.property_stock_valuation_account_id' - vals = { - 'name': 'property_stock_valuation_account_id', - 'fields_id': fields_id, - 'value': 'account.account,'+str(account_id), - 'company_id': env.ref('base.main_company').id, - } - env['ir.property']._load_records([dict(xml_id=xml_id, values=vals, noupdate=True)]) + for name in todo_list: + account = getattr(company_id, name) + if account: + env['ir.property'].set_default( + name, + 'product.category', + account, + company_id, + ) diff --git a/addons/stock_account/tests/common.py b/addons/stock_account/tests/common.py index ae153a77cb1..68d79a1b8d9 100644 --- a/addons/stock_account/tests/common.py +++ b/addons/stock_account/tests/common.py @@ -11,14 +11,15 @@ class StockAccountTestCommon(AccountTestCommon): super(StockAccountTestCommon, cls).setUpClass() # Properties: Stock valuation account and journal - cls.env['ir.property'].create([{ - 'name': 'property_stock_valuation_account_id', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_stock_valuation_account_id')], limit=1).id, - 'value': 'account.account,%s' % (cls.stk.id), - 'company_id': cls.env.company.id, - }, { - 'name': 'property_stock_valuation_journal', - 'fields_id': cls.env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_stock_journal')], limit=1).id, - 'value': 'account.journal,%s' % (cls.miscellaneous_journal.id), - 'company_id': cls.env.company.id, - }]) + cls.env['ir.property'].set_default( + 'property_stock_valuation_account_id', + 'product.category', + cls.stk, + cls.env.company, + ) + cls.env['ir.property'].set_default( + 'property_stock_journal', + 'product.category', + cls.miscellaneous_journal, + cls.env.company, + ) diff --git a/addons/test_website_slides_full/tests/test_ui_wslides.py b/addons/test_website_slides_full/tests/test_ui_wslides.py index 39ff909e2b5..d0785e059dd 100644 --- a/addons/test_website_slides_full/tests/test_ui_wslides.py +++ b/addons/test_website_slides_full/tests/test_ui_wslides.py @@ -24,7 +24,7 @@ class TestUi(TestUICommon): 'email': 'admin@yourcompany.example.com', }) - # Specify Accounting Data + # Specify Accounting Data cash_journal = self.env['account.journal'].create({'name': 'Cash - Test', 'type': 'cash', 'code': 'CASH - Test'}) self.env['payment.acquirer'].search([('journal_id', '=', False)]).journal_id = cash_journal a_recv = self.env['account.account'].create({ @@ -39,17 +39,10 @@ class TestUi(TestUICommon): 'user_type_id': self.env.ref('account.data_account_type_payable').id, 'reconcile': True, }) - 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, - }]) + + Property = self.env['ir.property'] + Property.set_default('property_account_receivable_id', 'res.partner', a_recv, self.env.company) + Property.set_default('property_account_payable_id', 'res.partner', a_pay, self.env.company) product_course_channel_6 = self.env['product.product'].create({ 'name': 'DIY Furniture Course', diff --git a/odoo/addons/base/models/ir_property.py b/odoo/addons/base/models/ir_property.py index a8bab99215e..a303e6ab4f6 100644 --- a/odoo/addons/base/models/ir_property.py +++ b/odoo/addons/base/models/ir_property.py @@ -38,7 +38,7 @@ class Property(models.Model): name = fields.Char(index=True) res_id = fields.Char(string='Resource', index=True, help="If not set, acts as a default value for new resources",) company_id = fields.Many2one('res.company', string='Company', index=True) - fields_id = fields.Many2one('ir.model.fields', string='Field', ondelete='cascade', required=True, index=True) + fields_id = fields.Many2one('ir.model.fields', string='Field', ondelete='cascade', required=True) value_float = fields.Float() value_integer = fields.Integer() value_text = fields.Text() # will contain (char, text) @@ -60,6 +60,14 @@ class Property(models.Model): default='many2one', index=True) + def init(self): + # Ensure there is at most one active variant for each combination. + query = """ + CREATE UNIQUE INDEX IF NOT EXISTS ir_property_unique_index + ON %s (fields_id, COALESCE(company_id, 0), COALESCE(res_id, '')) + """ + self.env.cr.execute(query % self._table) + def _update_values(self, values): if 'value' not in values: return values @@ -168,6 +176,34 @@ class Property(models.Model): return False @api.model + def set_default(self, name, model, value, company=False): + """ Set the given field's generic value for the given company. + + :param name: the field's name + :param model: the field's model name + :param value: the field's value + :param company: the company (record or id) + """ + field_id = self.env['ir.model.fields']._get(model, name).id + company_id = int(company) if company else False + prop = self.search([ + ('fields_id', '=', field_id), + ('company_id', '=', company_id), + ('res_id', '=', False), + ]) + if prop: + prop.write({'value': value}) + else: + self.create({ + 'fields_id': field_id, + 'company_id': company_id, + 'res_id': False, + 'name': name, + 'value': value, + 'type': self.env[model]._fields[name].type, + }) + + @api.model def get(self, name, model, res_id=False): if not res_id: t, v = self._get_default_property(name, model) diff --git a/odoo/addons/test_main_flows/tests/test_flow.py b/odoo/addons/test_main_flows/tests/test_flow.py index fe86f8187c6..2f359d4cccc 100644 --- a/odoo/addons/test_main_flows/tests/test_flow.py +++ b/odoo/addons/test_main_flows/tests/test_flow.py @@ -36,32 +36,13 @@ class TestUi(odoo.tests.HttpCase): '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, - }]) + Property = self.env['ir.property'] + Property.set_default('property_account_receivable_id', 'res.partner', a_recv, self.env.company) + Property.set_default('property_account_payable_id', 'res.partner', a_pay, self.env.company) + Property.set_default('property_account_position_id', 'res.partner', False, self.env.company) + Property.set_default('property_account_expense_categ_id', 'product.category', a_expense, self.env.company) + Property.set_default('property_account_income_categ_id', 'product.category', a_sale, self.env.company) + self.expenses_journal = self.env['account.journal'].create({ 'name': 'Vendor Bills - Test', 'code': 'TEXJ', 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 b68ae8220fb..5600affc125 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -951,12 +951,9 @@ class TestFields(TransactionCaseWithUserDemo): tag2 = self.env['test_new_api.multi.tag'].create({'name': 'Quuz'}) # create default values for the company-dependent fields - field_foo = self.env['ir.model.fields']._get('test_new_api.company', 'foo') - self.env['ir.property'].create({'name': 'foo', 'fields_id': field_foo.id, - 'value': 'default', 'type': 'char'}) - field_tag_id = self.env['ir.model.fields']._get('test_new_api.company', 'tag_id') - self.env['ir.property'].create({'name': 'foo', 'fields_id': field_tag_id.id, - 'value': tag0, 'type': 'many2one'}) + self.env['ir.property'].set_default('foo', 'test_new_api.company', 'default') + self.env['ir.property'].set_default('foo', 'test_new_api.company', 'default1', company1) + self.env['ir.property'].set_default('tag_id', 'test_new_api.company', tag0) # assumption: users don't have access to 'ir.property' accesses = self.env['ir.model.access'].search([('model_id.model', '=', 'ir.property')]) @@ -970,7 +967,7 @@ class TestFields(TransactionCaseWithUserDemo): 'tag_id': tag1.id, }) self.assertEqual(record.with_user(user0).foo, 'main') - self.assertEqual(record.with_user(user1).foo, 'default') + self.assertEqual(record.with_user(user1).foo, 'default1') self.assertEqual(record.with_user(user2).foo, 'default') self.assertEqual(str(record.with_user(user0).date), '1932-11-09') self.assertEqual(record.with_user(user1).date, False) |
