[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>
This commit is contained in:
committed by
Raphael Collet
co-authored by
Raphael Collet
parent
8821f0e917
commit
e85faf3986
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
@@ -167,6 +175,34 @@ class Property(models.Model):
|
||||
return fields.Date.to_string(fields.Datetime.from_string(self.value_datetime))
|
||||
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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user