1476 lines
59 KiB
Python
1476 lines
59 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
|
|
from unittest.mock import patch
|
|
|
|
from odoo import Command
|
|
from odoo.exceptions import AccessError
|
|
from odoo.tests.common import Form, TransactionCase, users
|
|
from odoo.tools import mute_logger
|
|
|
|
|
|
class PropertiesCase(TransactionCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.user = cls.env.user
|
|
cls.partner = cls.env['test_new_api.partner'].create({'name': 'Test Partner Properties'})
|
|
cls.partner_2 = cls.env['test_new_api.partner'].create({'name': 'Test Partner Properties 2'})
|
|
|
|
cls.test_user = cls.env['res.users'].create({
|
|
'name': 'Test',
|
|
'login': 'test',
|
|
'company_id': cls.env.company.id,
|
|
})
|
|
|
|
attributes_definition_1 = [{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Color Code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'string': 'Partner',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
}]
|
|
|
|
attributes_definition_2 = [{
|
|
'name': 'state',
|
|
'type': 'selection',
|
|
'string': 'Status',
|
|
'selection': [('draft', 'Draft'), ('progress', 'In Progress'), ('done', 'Done')],
|
|
'default': 'draft',
|
|
}]
|
|
|
|
cls.discussion_1 = cls.env['test_new_api.discussion'].create({
|
|
'name': 'Test Discussion',
|
|
'attributes_definition': attributes_definition_1,
|
|
'participants': [Command.link(cls.user.id)],
|
|
})
|
|
cls.discussion_2 = cls.env['test_new_api.discussion'].create({
|
|
'name': 'Test Discussion',
|
|
'attributes_definition': attributes_definition_2,
|
|
'participants': [Command.link(cls.user.id)],
|
|
})
|
|
|
|
cls.message_1 = cls.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'discussion': cls.discussion_1.id,
|
|
'author': cls.user.id,
|
|
'attributes': {
|
|
'discussion_color_code': 'Test',
|
|
'moderator_partner_id': cls.partner.id,
|
|
},
|
|
})
|
|
|
|
cls.message_2 = cls.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'discussion': cls.discussion_1.id,
|
|
'author': cls.user.id,
|
|
})
|
|
cls.message_3 = cls.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'discussion': cls.discussion_2.id,
|
|
'author': cls.user.id,
|
|
})
|
|
|
|
def test_properties_field(self):
|
|
self.assertTrue(isinstance(self.message_1.attributes, list))
|
|
# testing assigned value
|
|
self.assertEqual(self.message_1.attributes, [{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Color Code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
'value': 'Test',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'string': 'Partner',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': self.partner.id,
|
|
}])
|
|
|
|
self.assertEqual(self.message_2.attributes[0]['value'], 'blue')
|
|
self.assertFalse(self.message_2.attributes[1]['value'])
|
|
# testing default value
|
|
self.assertEqual(
|
|
self.message_3.attributes[0]['value'], 'draft',
|
|
msg='Should have taken the default value')
|
|
|
|
self.message_1.attributes = [
|
|
{'name': 'discussion_color_code', 'value': 'red'},
|
|
{'name': 'moderator_partner_id', 'value': self.partner_2.id},
|
|
]
|
|
self.assertEqual(self.message_1.attributes[0]['value'], 'red')
|
|
|
|
self.env.invalidate_all()
|
|
|
|
self.assertEqual(self.message_1.attributes[0]['value'], 'red')
|
|
self.assertEqual(self.message_1.attributes[1]['value'], self.partner_2.id)
|
|
|
|
# check that the value has been updated in the database
|
|
database_values = self._get_sql_properties(self.message_1)
|
|
self.assertTrue(isinstance(database_values, dict))
|
|
self.assertEqual(
|
|
database_values.get('discussion_color_code'), 'red',
|
|
msg='Value must be updated in the database')
|
|
|
|
# if we write False on the field, it should still
|
|
# return the properties definition for the web client
|
|
self.message_3.attributes = False
|
|
self.env.invalidate_all()
|
|
|
|
expected = self.discussion_2.attributes_definition
|
|
for property_definition in expected:
|
|
property_definition['value'] = False
|
|
|
|
self.assertEqual(self.message_3.read(['attributes'])[0]['attributes'], expected)
|
|
self.assertEqual(self.message_3.attributes, expected)
|
|
|
|
def test_properties_field_parameters_cleanup(self):
|
|
# check that the keys not valid for the given type are removed
|
|
self.message_1.attributes = [{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Color Code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
'value': 'Test',
|
|
'definition_changed': True,
|
|
'selection': [['a', 'A']], # selection key is not valid for char type
|
|
}]
|
|
values = self._get_sql_definition(self.message_1.discussion)
|
|
self.assertEqual(values, [{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Color Code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
}])
|
|
|
|
@mute_logger('odoo.fields')
|
|
def test_properties_field_write_batch(self):
|
|
"""Test the behavior of the write called in batch.
|
|
|
|
Simulate a write operation done by the web client.
|
|
"""
|
|
# mix both properties
|
|
properties_values = (self.message_1 | self.message_3).read(['attributes'])
|
|
properties_values = properties_values[0]['attributes'] + properties_values[1]['attributes']
|
|
|
|
for properties in properties_values:
|
|
if properties['name'] == 'discussion_color_code':
|
|
properties['value'] = 'orange'
|
|
elif properties['name'] == 'state':
|
|
properties['value'] = 'done'
|
|
elif properties['name'] == 'moderator_partner_id':
|
|
properties['value'] = self.partner_2.id
|
|
properties['definition_changed'] = True
|
|
|
|
(self.message_1 | self.message_3).write({'attributes': properties_values})
|
|
|
|
sql_values_1 = self._get_sql_properties(self.message_1)
|
|
sql_values_3 = self._get_sql_properties(self.message_3)
|
|
|
|
# definition of both child has been changed
|
|
self.assertEqual(sql_values_1, {'discussion_color_code': 'orange', 'moderator_partner_id': self.partner_2.id, 'state': 'done'})
|
|
self.assertEqual(sql_values_3, {'discussion_color_code': 'orange', 'moderator_partner_id': self.partner_2.id, 'state': 'done'})
|
|
|
|
@mute_logger('odoo.models.unlink', 'odoo.fields')
|
|
def test_properties_field_read_batch(self):
|
|
values = self.message_1.read(['attributes'])[0]['attributes']
|
|
self.assertEqual(len(values), 2)
|
|
self.assertEqual(values[0]['type'], 'char')
|
|
self.assertEqual(values[1]['type'], 'many2one')
|
|
|
|
message_2_values = self.message_1.attributes
|
|
message_2_values[1]['value'] = [self.partner_2.id, "Bob"]
|
|
self.message_2.attributes = message_2_values
|
|
|
|
expected_queries = [
|
|
# read the properties field value
|
|
'SELECT "test_new_api_message"."id" AS "id", "test_new_api_message"."attributes" AS "attributes" FROM "test_new_api_message" WHERE "test_new_api_message".id IN %s',
|
|
'SELECT "test_new_api_message"."id" AS "id", "test_new_api_message"."discussion" AS "discussion", "test_new_api_message"."body" AS "body", "test_new_api_message"."author" AS "author", "test_new_api_message"."name" AS "name", "test_new_api_message"."important" AS "important", "test_new_api_message"."label"->>\'en_US\' AS "label", "test_new_api_message"."priority" AS "priority", "test_new_api_message"."active" AS "active", "test_new_api_message"."create_uid" AS "create_uid", "test_new_api_message"."create_date" AS "create_date", "test_new_api_message"."write_uid" AS "write_uid", "test_new_api_message"."write_date" AS "write_date" FROM "test_new_api_message" WHERE "test_new_api_message".id IN %s',
|
|
# read the definition on the definition record
|
|
'SELECT "test_new_api_discussion"."id" AS "id", "test_new_api_discussion"."name" AS "name", "test_new_api_discussion"."moderator" AS "moderator", "test_new_api_discussion"."message_concat" AS "message_concat", "test_new_api_discussion"."history" AS "history", "test_new_api_discussion"."attributes_definition" AS "attributes_definition", "test_new_api_discussion"."create_uid" AS "create_uid", "test_new_api_discussion"."create_date" AS "create_date", "test_new_api_discussion"."write_uid" AS "write_uid", "test_new_api_discussion"."write_date" AS "write_date" FROM "test_new_api_discussion" WHERE "test_new_api_discussion".id IN %s',
|
|
# check the many2one existence
|
|
'SELECT "test_new_api_partner".id FROM "test_new_api_partner" WHERE "test_new_api_partner".id IN %s',
|
|
'SELECT "test_new_api_partner"."id" AS "id", "test_new_api_partner"."name" AS "name", "test_new_api_partner"."create_uid" AS "create_uid", "test_new_api_partner"."create_date" AS "create_date", "test_new_api_partner"."write_uid" AS "write_uid", "test_new_api_partner"."write_date" AS "write_date" FROM "test_new_api_partner" WHERE "test_new_api_partner".id IN %s',
|
|
]
|
|
|
|
self.env.invalidate_all()
|
|
with self.assertQueryCount(5), self.assertQueries(expected_queries):
|
|
self.message_1.read(['attributes'])
|
|
|
|
# read in batch a lot of records
|
|
discussions = [self.discussion_1, self.discussion_2]
|
|
partners = self.env['test_new_api.partner'].create([{'name': f'Test {i}'} for i in range(50)])
|
|
messages = self.env['test_new_api.message'].create([{
|
|
'name': f'Test Message {i}',
|
|
'discussion': discussions[i % 2].id,
|
|
'author': self.user.id,
|
|
'attributes': [{
|
|
'name': 'partner_id',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': partner.id,
|
|
'definition_changed': True,
|
|
}]
|
|
} for i, partner in enumerate(partners)])
|
|
|
|
self.env.invalidate_all()
|
|
|
|
with self.assertQueryCount(5), self.assertQueries(expected_queries):
|
|
values = messages.read(['attributes'])
|
|
|
|
# remove some partners in the list
|
|
partners[:20].unlink()
|
|
self.env.invalidate_all()
|
|
# 5 queries instead of 25 queries, thanks to the cache values that has been
|
|
# cleaned (the properties field can trust the cached value, the deleted ids
|
|
# are not in the cache even if they still exists in the database)
|
|
with self.assertQueryCount(5):
|
|
values = messages.read(['attributes'])
|
|
|
|
@mute_logger('odoo.fields')
|
|
def test_properties_field_delete(self):
|
|
"""Test to delete a property using the flag "definition_deleted"."""
|
|
self.message_1.attributes = [{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Test color code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
'value': 'purple',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'string': 'Partner',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': [self.partner.id, 'Bob'],
|
|
'definition_deleted': True,
|
|
}]
|
|
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertEqual(
|
|
sql_definition, [{
|
|
'name': 'discussion_color_code',
|
|
'type': 'char',
|
|
'string': 'Test color code',
|
|
'default': 'blue',
|
|
}])
|
|
|
|
self.assertEqual(len(self.message_1.attributes), 1)
|
|
self.assertEqual(self.message_1.attributes[0]['value'], 'purple')
|
|
|
|
@mute_logger('odoo.fields')
|
|
def test_properties_field_create_batch(self):
|
|
# first create to cache the access rights
|
|
self.env['test_new_api.message'].create({'name': 'test'})
|
|
|
|
with self.assertQueryCount(2):
|
|
messages = self.env['test_new_api.message'].create([{
|
|
'name': 'Test Message',
|
|
'discussion': False,
|
|
'author': self.user.id,
|
|
}, {
|
|
'name': 'Test Message',
|
|
'discussion': False,
|
|
'author': self.user.id,
|
|
}])
|
|
self.env.invalidate_all()
|
|
|
|
with self.assertQueryCount(8):
|
|
messages = self.env['test_new_api.message'].create([{
|
|
'name': 'Test Message',
|
|
'discussion': self.discussion_1.id,
|
|
'author': self.user.id,
|
|
'attributes': [{
|
|
# no name, should be automatically generated
|
|
'string': 'Discussion Color code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
'value': 'purple',
|
|
'definition_changed': True,
|
|
}, {
|
|
# the name is already set and shouldn't be re-generated
|
|
'name': 'moderator_partner_id',
|
|
'string': 'Partner',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': self.partner.id,
|
|
'definition_changed': True,
|
|
}],
|
|
}, {
|
|
'name': 'Test Message',
|
|
'discussion': self.discussion_2.id,
|
|
'author': self.user.id,
|
|
'attributes': [{
|
|
'type': 'selection',
|
|
'string': 'Status',
|
|
'selection': [
|
|
('draft', 'Draft'),
|
|
('progress', 'In Progress'),
|
|
('done', 'Done'),
|
|
],
|
|
'default': 'draft',
|
|
'definition_changed': True,
|
|
}],
|
|
}])
|
|
self.env.invalidate_all()
|
|
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertEqual(len(sql_definition), 2)
|
|
|
|
# check the generated name
|
|
property_color_name = sql_definition[0]['name']
|
|
self.assertTrue(property_color_name, msg="Property name must have been generated")
|
|
|
|
self.assertEqual(sql_definition, [
|
|
{
|
|
'name': property_color_name,
|
|
'default': 'blue',
|
|
'string': 'Discussion Color code',
|
|
'type': 'char',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'string': 'Partner',
|
|
}
|
|
])
|
|
|
|
self.assertEqual(
|
|
self.discussion_1.attributes_definition[0]['string'], 'Discussion Color code',
|
|
msg='Should have updated the definition record')
|
|
|
|
self.assertEqual(len(messages), 2)
|
|
|
|
sql_properties_1 = self._get_sql_properties(messages[0])
|
|
self.assertEqual(
|
|
sql_properties_1,
|
|
{'moderator_partner_id': self.partner.id,
|
|
property_color_name: 'purple'})
|
|
sql_properties_2 = self._get_sql_properties(messages[1])
|
|
status_name = self.discussion_2.attributes_definition[0]['name']
|
|
self.assertEqual(
|
|
sql_properties_2,
|
|
{status_name: 'draft'})
|
|
|
|
properties_values_1 = messages[0].attributes
|
|
properties_values_2 = messages[1].attributes
|
|
|
|
self.assertEqual(len(properties_values_1), 2, msg='Discussion 1 has 2 properties')
|
|
self.assertEqual(len(properties_values_2), 1, msg='Discussion 2 has 1 property')
|
|
|
|
self.assertEqual(properties_values_1[0]['value'], 'purple')
|
|
self.assertEqual(properties_values_1[1]['value'], self.partner.id)
|
|
self.assertEqual(properties_values_2[0]['value'], 'draft',
|
|
msg='Should have taken the default value')
|
|
|
|
def test_properties_field_default(self):
|
|
message = self.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'discussion': self.discussion_2.id,
|
|
'author': self.user.id,
|
|
})
|
|
self.assertEqual(
|
|
message.attributes[0]['value'],
|
|
'draft',
|
|
msg='Should have taken the default value')
|
|
|
|
message.attributes = [{'name': 'state', 'value': None}]
|
|
self.assertFalse(
|
|
message.attributes[0]['value'],
|
|
msg='Writing None should not reset to the default value')
|
|
|
|
# test the case where the definition record come from a default as well
|
|
def default_discussion(_record):
|
|
return self.discussion_2.id
|
|
|
|
with patch.object(self.env['test_new_api.message']._fields['discussion'], 'default', default_discussion):
|
|
message = self.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'author': self.user.id,
|
|
})
|
|
self.assertEqual(message.discussion, self.discussion_2)
|
|
self.assertEqual(
|
|
message.attributes[0]['value'],
|
|
'draft',
|
|
msg='Should have taken the default value')
|
|
|
|
# the definition record come from a default value
|
|
self.discussion_2.attributes_definition = [{
|
|
'name': 'test',
|
|
'type': 'char',
|
|
'default': 'default char',
|
|
}]
|
|
message = self.env['test_new_api.message'] \
|
|
.with_context(default_discussion=self.discussion_2) \
|
|
.create({'name': 'Test Message', 'author': self.user.id})
|
|
self.assertEqual(message.discussion, self.discussion_2)
|
|
self.assertEqual(message.attributes, [{
|
|
'name': 'test',
|
|
'type': 'char',
|
|
'default': 'default char',
|
|
'value': 'default char',
|
|
}])
|
|
|
|
# test a default many2one
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'my_many2one',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
'type': 'many2one',
|
|
# send the value like the web client does
|
|
'default': [self.partner.id, 'Bob'],
|
|
},
|
|
]
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertEqual(sql_definition[0]['default'], self.partner.id)
|
|
|
|
read_values = self.discussion_1.read(['attributes_definition'])[0]['attributes_definition']
|
|
self.assertEqual(
|
|
read_values[0]['default'],
|
|
(self.partner.id, self.partner.display_name),
|
|
msg='When reading many2one default, it should return the display name',
|
|
)
|
|
|
|
# read the default many2one and deactivate the name_get
|
|
read_values = self.discussion_1.read(['attributes_definition'], load=None)[0]['attributes_definition']
|
|
self.assertEqual(
|
|
read_values[0]['default'],
|
|
self.partner.id,
|
|
msg='If the name_get is deactivate, it should not return the display name',
|
|
)
|
|
|
|
message = self.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'author': self.user.id,
|
|
'discussion': self.discussion_1.id,
|
|
})
|
|
|
|
properties = message.read(['attributes'])[0]['attributes']
|
|
self.assertEqual(properties[0]['value'], (self.partner.id, self.partner.display_name))
|
|
|
|
self.assertEqual(message.attributes[0]['value'], self.partner.id)
|
|
|
|
# give a default value and a value for a many2one
|
|
# the default value must be ignored
|
|
property_definition = self.discussion_1.read(['attributes_definition'])[0]['attributes_definition']
|
|
property_definition[0]['value'] = (self.partner_2.id, 'Alice')
|
|
message = self.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'author': self.user.id,
|
|
'discussion': self.discussion_1.id,
|
|
'attributes': property_definition,
|
|
})
|
|
self.assertEqual(
|
|
message.attributes[0]['value'],
|
|
self.partner_2.id,
|
|
msg='Should not take the default value',
|
|
)
|
|
|
|
def test_properties_field_read(self):
|
|
"""Test the behavior of the read method.
|
|
|
|
In comparison with a simple "record.properties", the read method should not
|
|
record a recordset for the many2one, but a tuple with the record id and
|
|
the record name_get.
|
|
"""
|
|
properties_values = (self.message_1 | self.message_3).read(['attributes'])
|
|
|
|
self.assertEqual(len(properties_values), 2)
|
|
|
|
properties_message_1 = properties_values[0]['attributes']
|
|
properties_message_3 = properties_values[1]['attributes']
|
|
|
|
self.assertTrue(isinstance(properties_message_1, list))
|
|
self.assertTrue(isinstance(properties_message_3, list))
|
|
|
|
self.assertEqual(len(properties_message_1), 2, msg="Message 1 has 2 properties")
|
|
self.assertEqual(len(properties_message_3), 1, msg="Message 3 has 1 property")
|
|
|
|
self.assertEqual(
|
|
properties_message_1[0]['name'], 'discussion_color_code',
|
|
msg='First message 1 property should be "discussion_color_code"')
|
|
self.assertEqual(
|
|
properties_message_1[1]['name'], 'moderator_partner_id',
|
|
msg='Second message 1 property should be "moderator_partner_id"')
|
|
self.assertEqual(
|
|
properties_message_3[0]['name'], 'state',
|
|
msg='First message 3 property should be "state"')
|
|
|
|
many2one_property = properties_message_1[1]
|
|
self.assertEqual(
|
|
many2one_property['string'], 'Partner',
|
|
msg='Definition must be present when reading child')
|
|
self.assertEqual(
|
|
many2one_property['type'], 'many2one',
|
|
msg='Definition must be present when reading child')
|
|
self.assertEqual(
|
|
many2one_property['comodel'], 'test_new_api.partner',
|
|
msg='Definition must be present when reading child')
|
|
self.assertEqual(many2one_property['value'], (self.partner.id, self.partner.display_name))
|
|
|
|
# disable the name_get
|
|
properties_values = (self.message_1 | self.message_3).read(['attributes'], load=None)
|
|
many2one_property = properties_values[0]['attributes'][1]
|
|
|
|
self.assertEqual(
|
|
many2one_property['value'], self.partner.id,
|
|
msg='If name_get is disable, should only return the record id')
|
|
|
|
def test_properties_field_many2one_basic(self):
|
|
"""Test the basic (read, write...) of the many2one property."""
|
|
self.message_2.attributes = [
|
|
{
|
|
"name": "discussion_color_code",
|
|
"type": "char",
|
|
"string": "Color Code",
|
|
"default": "blue",
|
|
"value": False,
|
|
}, {
|
|
"name": "moderator_partner_id",
|
|
"type": "many2one",
|
|
"string": "Partner",
|
|
"comodel": "test_new_api.partner",
|
|
"value": self.partner_2.id,
|
|
},
|
|
]
|
|
|
|
self.assertFalse(self.message_2.attributes[0]['value'])
|
|
self.assertEqual(self.message_2.attributes[1]['value'], self.partner_2.id)
|
|
sql_values = self._get_sql_properties(self.message_2)
|
|
self.assertEqual(
|
|
sql_values,
|
|
{'moderator_partner_id': self.partner_2.id,
|
|
'discussion_color_code': False})
|
|
|
|
# read the many2one
|
|
properties = self.message_2.read(['attributes'])[0]['attributes']
|
|
self.assertEqual(properties[1]['value'], (self.partner_2.id, self.partner_2.display_name))
|
|
self.assertEqual(properties[1]['comodel'], 'test_new_api.partner')
|
|
|
|
@mute_logger('odoo.models.unlink', 'odoo.fields')
|
|
def test_properties_field_many2one_unlink(self):
|
|
"""Test the case where we unlink the many2one record."""
|
|
self.message_2.attributes = [{
|
|
'name': 'moderator_partner_id',
|
|
'value': self.partner.id,
|
|
}]
|
|
|
|
# remove the partner on message 2
|
|
self.partner.unlink()
|
|
with self.assertQueryCount(4):
|
|
# 1 query to read the field
|
|
# 1 query to read the definition
|
|
# 2 queries to check if the many2one still exists / name_get
|
|
self.assertFalse(self.message_2.attributes[0]['value'])
|
|
|
|
# remove the partner, and use the read method
|
|
self.message_2.attributes = [{
|
|
'name': 'moderator_partner_id',
|
|
'value': self.partner_2.id,
|
|
}]
|
|
self.partner_2.unlink()
|
|
|
|
with self.assertQueryCount(4):
|
|
value = self.message_2.read(['attributes'])
|
|
value = value[0]['attributes']
|
|
self.assertFalse(value[1]['value'])
|
|
self.assertEqual(value[1]['comodel'], 'test_new_api.partner')
|
|
|
|
# many2one properties in a default value
|
|
partner = self.env['res.partner'].create({'name': 'test unlink'})
|
|
self.message_2.attributes = [{
|
|
'name': 'moderator_partner_id',
|
|
'type': 'many2one',
|
|
'comodel': 'res.partner',
|
|
'default': [partner.id, 'Bob'],
|
|
'definition_changed': True,
|
|
}]
|
|
self.assertEqual(
|
|
self.message_2.read(['attributes'])[0]['attributes'],
|
|
[{
|
|
'name': 'moderator_partner_id',
|
|
'type': 'many2one',
|
|
'comodel': 'res.partner',
|
|
'default': (partner.id, partner.display_name),
|
|
'value': False,
|
|
}],
|
|
)
|
|
partner.unlink()
|
|
self.assertEqual(
|
|
self.message_2.read(['attributes'])[0]['attributes'],
|
|
[{
|
|
'name': 'moderator_partner_id',
|
|
'type': 'many2one',
|
|
'comodel': 'res.partner',
|
|
'default': False,
|
|
'value': False,
|
|
}],
|
|
)
|
|
|
|
def test_properties_field_many2one_model_removed(self):
|
|
"""Test the case where we uninstall a module, and the model does not exist anymore."""
|
|
# simulate a module uninstall, the model is not available now
|
|
# when reading the model / many2one, it should return False
|
|
self.message_1.attributes = [{
|
|
'name': 'message',
|
|
'value': self.message_3.id,
|
|
}]
|
|
|
|
self.env.flush_all()
|
|
self.env.cr.execute(
|
|
"""
|
|
UPDATE test_new_api_discussion
|
|
SET attributes_definition = '[{"name": "message", "comodel": "wrong_model", "type": "many2one"}]'
|
|
WHERE id = %s
|
|
""", (self.discussion_1.id, ),
|
|
)
|
|
self.env.invalidate_all()
|
|
|
|
values = self.discussion_1.read(['attributes_definition'])[0]
|
|
self.assertFalse(values['attributes_definition'][0]['comodel'])
|
|
|
|
attributes_definition = self.discussion_1.attributes_definition
|
|
self.assertEqual(
|
|
attributes_definition,
|
|
[{'name': 'message', 'comodel': False, 'type': 'many2one'}],
|
|
msg='The model does not exist anymore, it should return false',
|
|
)
|
|
|
|
# read the many2one on the child, should return False as well
|
|
self.assertFalse(self.message_1.attributes[0]['value'])
|
|
|
|
values = self.message_1.read(['attributes'])[0]['attributes']
|
|
self.assertEqual(values[0]['type'], 'many2one', msg='Property type should be preserved')
|
|
self.assertFalse(values[0]['value'])
|
|
self.assertFalse(values[0]['comodel'])
|
|
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertEqual(
|
|
sql_definition,
|
|
[{'name': 'message', 'comodel': 'wrong_model', 'type': 'many2one'}],
|
|
msg='Do not clean the definition until we write on the field'
|
|
)
|
|
|
|
# write on the properties definition must clean the wrong model name
|
|
self.discussion_1.attributes_definition = self.discussion_1.attributes_definition
|
|
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertEqual(
|
|
sql_definition,
|
|
[{'name': 'message', 'comodel': False, 'type': 'many2one'}],
|
|
msg='Should have cleaned the model key',
|
|
)
|
|
|
|
def test_properties_field_domain(self):
|
|
self.discussion_1.attributes_definition = [{
|
|
'name': 'message',
|
|
'comodel': 'test_new_api.message',
|
|
'type': 'many2one',
|
|
'domain': "[('name', 'ilike', 'message')]",
|
|
}]
|
|
|
|
domain = self.message_1.attributes[0]['domain']
|
|
self.assertEqual(domain, "[('name', 'ilike', 'message')]")
|
|
|
|
# set a wrong domain, it can happen if we uninstall a module
|
|
# and if a field defined in this module was used in the domain
|
|
self.env.flush_all()
|
|
new_properties = json.dumps([{
|
|
'name': 'message',
|
|
'comodel': 'test_new_api.message',
|
|
'type': 'many2one',
|
|
'domain': "[('wrong_field', 'ilike', 'test')]",
|
|
}])
|
|
self.env.cr.execute(
|
|
"""
|
|
UPDATE test_new_api_discussion
|
|
SET attributes_definition = %s
|
|
WHERE id = %s
|
|
""", (new_properties, self.discussion_1.id, ),
|
|
)
|
|
self.env.flush_all()
|
|
self.env.invalidate_all()
|
|
|
|
definition = self.discussion_1.read(['attributes_definition'])[0]['attributes_definition']
|
|
self.assertNotIn('domain', definition)
|
|
|
|
properties = self.message_1.read(['attributes'])[0]['attributes']
|
|
self.assertNotIn('domain', properties)
|
|
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertIn(
|
|
'domain',
|
|
sql_definition[0],
|
|
msg='The domain should remain in database until we write on the properties definition',
|
|
)
|
|
|
|
def test_properties_field_integer_float_boolean(self):
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'int_value',
|
|
'string': 'Int Value',
|
|
'type': 'integer',
|
|
}, {
|
|
'name': 'float_value',
|
|
'string': 'Float Value',
|
|
'type': 'float',
|
|
}, {
|
|
'name': 'boolean_value',
|
|
'string': 'Boolean Value',
|
|
'type': 'boolean',
|
|
},
|
|
]
|
|
|
|
self.message_1.attributes = [{
|
|
'name': 'int_value',
|
|
'value': 55555555555,
|
|
}, {
|
|
'name': 'float_value',
|
|
'value': 1.337,
|
|
}, {
|
|
'name': 'boolean_value',
|
|
'value': 77777, # should be converted into True
|
|
}]
|
|
|
|
self.env.invalidate_all()
|
|
|
|
self.assertEqual(len(self.message_1.attributes), 3)
|
|
self.assertEqual(self.message_1.attributes[0]['value'], 55555555555)
|
|
self.assertEqual(self.message_1.attributes[1]['value'], 1.337)
|
|
self.assertEqual(self.message_1.attributes[2]['value'], True)
|
|
|
|
self.message_1.attributes = [{'name': 'boolean_value', 'value': 0}]
|
|
self.assertEqual(
|
|
self.message_1.attributes[2]['value'], False,
|
|
msg='Boolean value must have been converted to False')
|
|
|
|
# When the user sets the value 0 for the property fields of type integer
|
|
# and float, the system should store the value 0 and shouldn't transform
|
|
# 0 to False (-> unset value).
|
|
|
|
self.message_1.attributes = {'int_value': 0, 'float_value': 0}
|
|
self.assertEqual(len(self.message_1.attributes), 3)
|
|
self.assertEqual(self.message_1.attributes[0]['value'], 0)
|
|
self.assertEqual(self.message_1.attributes[1]['value'], 0)
|
|
self.assertEqual(self.message_1.attributes[2]['value'], False)
|
|
self.assertTrue(isinstance(self.message_1.attributes[0]['value'], int))
|
|
self.assertTrue(isinstance(self.message_1.attributes[1]['value'], int))
|
|
self.assertTrue(isinstance(self.message_1.attributes[2]['value'], bool))
|
|
self.assertEqual(self._get_sql_properties(self.message_1), {'int_value': 0, 'float_value': 0, 'boolean_value': False})
|
|
|
|
def test_properties_field_integer_float_falsy_value_edge_cases(self):
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'int_value',
|
|
'string': 'Int Value',
|
|
'type': 'integer',
|
|
'default': 42
|
|
}, {
|
|
'name': 'float_value',
|
|
'string': 'Float Value',
|
|
'type': 'float',
|
|
'default': 0.42
|
|
}
|
|
]
|
|
message_1 = self.env['test_new_api.message'].create({
|
|
'discussion': self.discussion_1.id,
|
|
'author': self.user.id,
|
|
'attributes': {'int_value': 0, 'float_value': 0}
|
|
})
|
|
|
|
# When the user sets the value 0 for the property fields of type integer
|
|
# and float, the system shouldn't consider 0 as a falsy value and fallback
|
|
# to the default value.
|
|
|
|
self.assertEqual(len(message_1.attributes), 2)
|
|
self.assertEqual(message_1.attributes[0]['value'], 0)
|
|
self.assertEqual(message_1.attributes[1]['value'], 0)
|
|
self.assertTrue(isinstance(message_1.attributes[0]['value'], int))
|
|
self.assertTrue(isinstance(message_1.attributes[1]['value'], int))
|
|
self.assertEqual(self._get_sql_properties(message_1), {'int_value': 0, 'float_value': 0})
|
|
|
|
def test_properties_field_selection(self):
|
|
self.message_3.attributes = [{'name': 'state', 'value': 'done'}]
|
|
self.env.invalidate_all()
|
|
self.assertEqual(self.message_3.attributes[0]['value'], 'done')
|
|
|
|
# the option might have been removed on the definition, write False
|
|
self.message_3.attributes = [{'name': 'state', 'value': 'unknown_selection'}]
|
|
self.env.invalidate_all()
|
|
self.assertFalse(self.message_3.attributes[0]['value'])
|
|
|
|
with self.assertRaises(ValueError):
|
|
# check that 2 options can not have the same id
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'option',
|
|
'type': 'selection',
|
|
'selection': [['a', 'A'], ['b', 'B'], ['a', 'C']],
|
|
}
|
|
]
|
|
|
|
def test_properties_field_tags(self):
|
|
"""Test the behavior of the tag property.
|
|
|
|
The tags properties is basically the same as the selection property,
|
|
but you can select multiple values. It should work like the selection
|
|
(if we remove a value on the definition record, it should remove the value on each
|
|
child the next time we read, etc).
|
|
|
|
Each tags has a color index defined on the definition record.
|
|
"""
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'my_tags',
|
|
'string': 'My Tags',
|
|
'type': 'tags',
|
|
'tags': [
|
|
('be', 'BE', 1),
|
|
('fr', 'FR', 2),
|
|
('de', 'DE', 3),
|
|
('it', 'IT', 1),
|
|
],
|
|
'default': ['be', 'de'],
|
|
},
|
|
]
|
|
message = self.env['test_new_api.message'].create(
|
|
{'discussion': self.discussion_1.id, 'author': self.user.id})
|
|
|
|
self.assertEqual(message.attributes[0]['value'], ['be', 'de'])
|
|
self.assertEqual(self._get_sql_properties(message), {'my_tags': ['be', 'de']})
|
|
|
|
self.env.invalidate_all()
|
|
|
|
# remove the DE tags on the definition
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'my_tags',
|
|
'string': 'My Tags',
|
|
'type': 'tags',
|
|
'tags': [
|
|
('be', 'BE', 1),
|
|
('fr', 'FR', 2),
|
|
('it', 'IT', 1),
|
|
],
|
|
'default': ['be', 'de'],
|
|
},
|
|
]
|
|
|
|
# the value must remain in the database until the next write on the child
|
|
self.assertEqual(self._get_sql_properties(message), {'my_tags': ['be', 'de']})
|
|
|
|
self.assertEqual(
|
|
message.attributes[0]['value'],
|
|
['be'],
|
|
msg='The tag has been removed on the definition, should be removed when reading the child')
|
|
self.assertEqual(
|
|
message.attributes[0]['tags'],
|
|
[['be', 'BE', 1], ['fr', 'FR', 2], ['it', 'IT', 1]])
|
|
|
|
# next write on the child must update the value
|
|
message.attributes = message.attributes
|
|
|
|
self.assertEqual(self._get_sql_properties(message), {'my_tags': ['be']})
|
|
|
|
with self.assertRaises(ValueError):
|
|
# it should detect that the tag is duplicated
|
|
self.discussion_1.attributes_definition = [
|
|
{
|
|
'name': 'my_tags',
|
|
'type': 'tags',
|
|
'tags': [
|
|
('be', 'BE', 1),
|
|
('be', 'FR', 2),
|
|
],
|
|
},
|
|
]
|
|
|
|
@mute_logger('odoo.models.unlink', 'odoo.fields')
|
|
def test_properties_field_many2many_basic(self):
|
|
"""Test the basic operation on a many2many properties (read, write...).
|
|
|
|
Check also that if we remove some record,
|
|
those are filtered when we read the child.
|
|
"""
|
|
partners = self.env['test_new_api.partner'].create([
|
|
{'name': f'Partner {i}'}
|
|
for i in range(20)
|
|
])
|
|
|
|
self.discussion_1.attributes_definition = [{
|
|
'name': 'moderator_partner_ids',
|
|
'string': 'Partners',
|
|
'type': 'many2many',
|
|
'comodel': 'test_new_api.partner',
|
|
}]
|
|
|
|
with self.assertQueryCount(2):
|
|
self.message_1.attributes = [
|
|
{
|
|
"name": "moderator_partner_ids",
|
|
"string": "Partners",
|
|
"type": "many2many",
|
|
"comodel": "test_new_api.partner",
|
|
"value": partners[:10].name_get(),
|
|
}
|
|
]
|
|
self.assertEqual(self.message_1.attributes[0]['value'], partners[:10].ids)
|
|
|
|
partners[:5].unlink()
|
|
with self.assertQueryCount(5):
|
|
self.assertEqual(self.message_1.attributes[0]['value'], partners[5:10].ids)
|
|
|
|
partners[5].unlink()
|
|
with self.assertQueryCount(5):
|
|
properties = self.message_1.read(['attributes'])[0]['attributes']
|
|
self.assertEqual(properties[0]['value'], partners[6:10].name_get())
|
|
|
|
# need to wait next write to clean data in database
|
|
# a single read won't clean the removed many2many
|
|
attributes = self.message_1.read(['attributes'])[0]['attributes']
|
|
self.message_1.invalidate_recordset()
|
|
self.message_1.attributes = attributes
|
|
|
|
sql_values = self._get_sql_properties(self.message_1)
|
|
self.assertEqual(sql_values, {'moderator_partner_ids': partners[6:10].ids})
|
|
|
|
# read and disable name_get
|
|
properties = self.message_1.read(['attributes'], load=None)[0]['attributes']
|
|
self.assertEqual(
|
|
properties[0]['value'],
|
|
partners[6:10].ids,
|
|
msg='Should not return the partners name',
|
|
)
|
|
|
|
# Check that duplicated ids are removed
|
|
self.env.flush_all()
|
|
moderator_partner_ids = partners[6:10].ids
|
|
moderator_partner_ids += moderator_partner_ids[2:]
|
|
new_value = json.dumps({"moderator_partner_ids": moderator_partner_ids})
|
|
self.env.cr.execute(
|
|
"""
|
|
UPDATE test_new_api_message
|
|
SET attributes = %s
|
|
WHERE id = %s
|
|
""", (new_value, self.message_1.id, ),
|
|
)
|
|
self.env.invalidate_all()
|
|
|
|
properties = self.message_1.read(['attributes'], load=None)[0]['attributes']
|
|
self.assertEqual(
|
|
properties[0]['value'],
|
|
partners[6:10].ids,
|
|
msg='Should removed duplicated ids',
|
|
)
|
|
|
|
# write a list with many2many values
|
|
self.message_1.attributes = [{
|
|
'name': 'partner_ids',
|
|
'string': 'Partners',
|
|
'type': 'many2many',
|
|
'comodel': 'test_new_api.partner',
|
|
'default': [(partners[8].id, 'Alice')],
|
|
'value': [(partners[9].id, 'Bob')],
|
|
'definition_changed': True,
|
|
}]
|
|
sql_properties = self._get_sql_properties(self.message_1)
|
|
self.assertEqual(sql_properties, {'partner_ids': [partners[9].id]})
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
self.assertEqual(sql_definition, [{
|
|
'name': 'partner_ids',
|
|
'string': 'Partners',
|
|
'type': 'many2many',
|
|
'comodel': 'test_new_api.partner',
|
|
'default': [partners[8].id],
|
|
}])
|
|
|
|
properties = self.message_1.read(['attributes'])[0]['attributes']
|
|
self.assertEqual(
|
|
properties,
|
|
[{
|
|
'name': 'partner_ids',
|
|
'string': 'Partners',
|
|
'type': 'many2many',
|
|
'comodel': 'test_new_api.partner',
|
|
'default': [(partners[8].id, partners[8].display_name)],
|
|
'value': [(partners[9].id, partners[9].display_name)],
|
|
}])
|
|
|
|
@users('test')
|
|
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.fields')
|
|
def test_properties_field_many2many_filtering(self):
|
|
# a user read a properties with a many2many and he doesn't have access to all records
|
|
tags = self.env['test_new_api.multi.tag'].create(
|
|
[{'name': f'Test Tag {i}'} for i in range(10)])
|
|
|
|
message = self.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'discussion': self.discussion_1.id,
|
|
'author': self.user.id,
|
|
'attributes': [{
|
|
'name': 'My Tags',
|
|
'type': 'many2many',
|
|
'comodel': 'test_new_api.multi.tag',
|
|
'value': tags.ids,
|
|
'definition_changed': True,
|
|
}],
|
|
})
|
|
|
|
self.env['ir.rule'].sudo().create({
|
|
'name': 'test_rule_tags',
|
|
'model_id': self.env['ir.model']._get('test_new_api.multi.tag').id,
|
|
'domain_force': [('name', 'not in', tags[5:].mapped('name'))],
|
|
'perm_read': True,
|
|
'perm_create': True,
|
|
'perm_write': True,
|
|
})
|
|
|
|
self.env.invalidate_all()
|
|
|
|
values = message.read(['attributes'])[0]['attributes'][0]['value']
|
|
self.assertEqual(values, [(tag.id, None if i >= 5 else tag.name) for i, tag in enumerate(tags.sudo())])
|
|
|
|
def test_properties_field_performance(self):
|
|
self.env.invalidate_all()
|
|
with self.assertQueryCount(5):
|
|
# read to put the partner name in cache
|
|
self.message_1.read(['attributes'])
|
|
|
|
with self.assertQueryCount(0, msg='Must read value from cache'):
|
|
self.message_1.attributes
|
|
|
|
expected = ['UPDATE "test_new_api_message" SET "attributes" = %s, "write_date" = %s, "write_uid" = %s WHERE id IN %s']
|
|
with self.assertQueryCount(1), self.assertQueries(expected):
|
|
self.message_1.attributes = [
|
|
{
|
|
"name": "discussion_color_code",
|
|
"type": "char",
|
|
"string": "Color Code",
|
|
"default": "blue",
|
|
"value": "red"
|
|
},
|
|
{
|
|
"name": "moderator_partner_id",
|
|
"type": "many2one",
|
|
"string": "Partner",
|
|
"comodel": "test_new_api.partner",
|
|
"value": None
|
|
},
|
|
]
|
|
self.message_1.flush_recordset()
|
|
|
|
def test_properties_field_change_definition(self):
|
|
"""Test the behavior of the field when changing the definition."""
|
|
|
|
attributes_definition = self.discussion_1.attributes_definition
|
|
self.message_1.attributes = [
|
|
{
|
|
"name": "discussion_color_code",
|
|
"value": None,
|
|
},
|
|
{
|
|
"name": "moderator_partner_id",
|
|
"value": None,
|
|
}
|
|
]
|
|
self.env.invalidate_all()
|
|
self.assertFalse(self.message_1.attributes[0]['value'])
|
|
|
|
# add a property on the definition record
|
|
attributes_definition += [{'name': 'state', 'string': 'State', 'type': 'char'}]
|
|
self.discussion_1.attributes_definition = attributes_definition
|
|
self.message_1.attributes = [{'name': 'state', 'value': 'ready'}]
|
|
|
|
self.env.invalidate_all()
|
|
|
|
self.assertEqual(self.message_1.attributes[2]['value'], 'ready')
|
|
|
|
# remove a property from the definition
|
|
# the properties on the child should remain, until we write on it
|
|
# when reading, the removed property must be filtered
|
|
self.discussion_1.attributes_definition = attributes_definition[:-1] # remove the state field
|
|
|
|
self.assertFalse(self.message_1.attributes[0]['value'])
|
|
|
|
value = self._get_sql_properties(self.message_1)
|
|
self.assertEqual(value.get('state'), 'ready', msg='The field should be in database')
|
|
|
|
self.message_1.attributes = [{'name': 'name', 'value': 'Test name'}]
|
|
value = self._get_sql_properties(self.message_1)
|
|
self.assertFalse(
|
|
value.get('state'),
|
|
msg='After updating an other property, the value must be cleaned')
|
|
|
|
# check that we can only set a allowed list of properties type
|
|
with self.assertRaises(ValueError):
|
|
self.discussion_1.attributes_definition = [{'name': 'state', 'type': 'wrong_type'}]
|
|
|
|
# check the property ID unicity
|
|
with self.assertRaises(ValueError):
|
|
self.discussion_1.attributes_definition = [
|
|
{'name': 'state', 'type': 'char'},
|
|
{'name': 'state', 'type': 'datetime'},
|
|
]
|
|
|
|
@mute_logger('odoo.fields')
|
|
def test_properties_field_onchange(self):
|
|
"""If we change the definition record, the onchange of the properties field must be triggered."""
|
|
message_form = Form(self.env['test_new_api.message'])
|
|
|
|
with self.assertQueryCount(8):
|
|
message_form.discussion = self.discussion_1
|
|
message_form.author = self.user
|
|
|
|
self.assertEqual(
|
|
message_form.attributes,
|
|
[{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Color Code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
'value': 'blue',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'string': 'Partner',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': False,
|
|
}],
|
|
msg='Should take the new definition when changing the definition record',
|
|
)
|
|
|
|
# change the discussion field
|
|
message_form.discussion = self.discussion_2
|
|
|
|
properties = message_form.attributes
|
|
|
|
self.assertEqual(len(properties), 1)
|
|
self.assertEqual(
|
|
properties[0]['name'],
|
|
'state',
|
|
msg='Should take the values of the new definition record',
|
|
)
|
|
|
|
with self.assertQueryCount(6):
|
|
message = message_form.save()
|
|
|
|
self.assertEqual(
|
|
message.attributes[0]['value'],
|
|
'draft',
|
|
msg='Should take the default value',
|
|
)
|
|
|
|
# check cached value
|
|
cached_value = self.env.cache.get(message, message._fields['attributes'])
|
|
self.assertEqual(cached_value, {'state': 'draft'})
|
|
|
|
# change the definition record, change the definition and add default values
|
|
self.assertEqual(message.discussion, self.discussion_2)
|
|
|
|
with self.assertQueryCount(4):
|
|
message.discussion = self.discussion_1
|
|
self.assertEqual(
|
|
self.discussion_1.attributes_definition,
|
|
[{
|
|
'name': 'discussion_color_code',
|
|
'type': 'char',
|
|
'string': 'Color Code',
|
|
'default': 'blue',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'type': 'many2one',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
}],
|
|
)
|
|
self.assertEqual(
|
|
message.attributes,
|
|
[{
|
|
'name': 'discussion_color_code',
|
|
'type': 'char',
|
|
'string': 'Color Code',
|
|
'default': 'blue',
|
|
'value': 'blue',
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'type': 'many2one',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': False,
|
|
}],
|
|
)
|
|
|
|
self.discussion_1.attributes_definition = False
|
|
self.discussion_2.attributes_definition = [{
|
|
'name': 'test',
|
|
'type': 'char',
|
|
'default': 'Default',
|
|
}]
|
|
|
|
# change the message discussion to remove the properties
|
|
# discussion 1 -> discussion 2
|
|
message.discussion = self.discussion_2
|
|
message.attributes = [{'name': 'test', 'value': 'Test'}]
|
|
onchange_values = message.onchange(
|
|
values={
|
|
'discussion': self.discussion_1.id,
|
|
'attributes': [{
|
|
'name': 'test',
|
|
'type': 'char',
|
|
'default': 'Default',
|
|
'value': 'Test',
|
|
}],
|
|
},
|
|
field_name=['discussion'],
|
|
field_onchange={'attributes': '1'},
|
|
)
|
|
self.assertTrue(
|
|
'attributes' in onchange_values['value'],
|
|
msg='Should have detected the definition record change')
|
|
self.assertEqual(
|
|
onchange_values['value']['attributes'], [],
|
|
msg='Should have reset the properties definition')
|
|
|
|
# change the message discussion to add new properties
|
|
# discussion 2 -> discussion 1
|
|
message.discussion = self.discussion_1
|
|
onchange_values = message.onchange(
|
|
values={
|
|
'discussion': self.discussion_2.id,
|
|
'attributes': [],
|
|
},
|
|
field_name=['discussion'],
|
|
field_onchange={'attributes': '1'},
|
|
)
|
|
self.assertTrue(
|
|
'attributes' in onchange_values['value'],
|
|
msg='Should have detected the definition record change')
|
|
self.assertEqual(
|
|
onchange_values['value']['attributes'],
|
|
[{'name': 'test', 'type': 'char', 'default': 'Default', 'value': 'Default'}],
|
|
msg='Should have reset the properties definition to the discussion 1 definition')
|
|
|
|
# change the definition record and the definition at the same time
|
|
message_form = Form(message)
|
|
message_form.discussion = self.discussion_2
|
|
message_form.attributes = [{
|
|
'name': 'new_property',
|
|
'type': 'char',
|
|
'value': 'test value',
|
|
'definition_changed': True,
|
|
}]
|
|
message = message_form.save()
|
|
self.assertEqual(
|
|
self.discussion_2.attributes_definition,
|
|
[{'name': 'new_property', 'type': 'char'}])
|
|
self.assertEqual(
|
|
message.attributes,
|
|
[{'name': 'new_property', 'type': 'char', 'value': 'test value'}])
|
|
|
|
# re-write the same parent again and check that value are not reset
|
|
message.discussion = message.discussion
|
|
self.assertEqual(
|
|
message.attributes,
|
|
[{'name': 'new_property', 'type': 'char', 'value': 'test value'}])
|
|
|
|
# trigger a other onchange after setting the properties
|
|
# and check that it does not impact the properties
|
|
message.discussion.attributes_definition = []
|
|
message_form = Form(message)
|
|
message.attributes = [{
|
|
'name': 'new_property',
|
|
'type': 'char',
|
|
'value': 'test value',
|
|
'definition_changed': True,
|
|
}]
|
|
message_form.body = "a" * 42
|
|
message = message_form.save()
|
|
self.assertEqual(
|
|
message.attributes,
|
|
[{'name': 'new_property', 'type': 'char', 'value': 'test value'}])
|
|
|
|
@mute_logger('odoo.fields')
|
|
def test_properties_field_definition_update(self):
|
|
"""Test the definition update from the child."""
|
|
self.discussion_1.attributes_definition = []
|
|
|
|
self.message_1.attributes = [
|
|
{
|
|
'name': 'my_many2one',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
'type': 'many2one',
|
|
# send the value like the web client does
|
|
'default': [self.partner.id, 'Bob'],
|
|
'value': [self.partner_2.id, "Test"],
|
|
}, {
|
|
'name': 'my_many2many',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
'type': 'many2many',
|
|
# send the value like the web client does
|
|
'default': [[self.partner.id, 'Bob'], [self.partner_2.id, 'Test']],
|
|
'value': [[self.partner_2.id, "Test"]],
|
|
'definition_changed': True,
|
|
},
|
|
]
|
|
self.env.invalidate_all()
|
|
|
|
sql_definition = self._get_sql_definition(self.discussion_1)
|
|
expected_definition = [
|
|
{
|
|
'name': 'my_many2one',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
'type': 'many2one',
|
|
'default': self.partner.id,
|
|
}, {
|
|
'name': 'my_many2many',
|
|
'string': 'Partner',
|
|
'comodel': 'test_new_api.partner',
|
|
'type': 'many2many',
|
|
'default': [self.partner.id, self.partner_2.id],
|
|
},
|
|
]
|
|
self.assertEqual(sql_definition, expected_definition)
|
|
|
|
sql_properties = self._get_sql_properties(self.message_1)
|
|
expected_properties = {
|
|
'my_many2one': self.partner_2.id,
|
|
'my_many2many': [self.partner_2.id],
|
|
}
|
|
self.assertEqual(expected_properties, sql_properties)
|
|
|
|
@mute_logger('odoo.fields')
|
|
@users('test')
|
|
def test_properties_field_security(self):
|
|
"""Check the access right related to the Properties fields."""
|
|
def _mocked_check_access_rights(records, operation, raise_exception=True):
|
|
if records.env.su: # called with SUDO
|
|
return True
|
|
if raise_exception:
|
|
raise AccessError('')
|
|
return False
|
|
|
|
message = self.message_1.with_user(self.test_user)
|
|
|
|
# a user read a properties with a many2one to a record he doesn't have access to
|
|
tag = self.env['test_new_api.multi.tag'].create({'name': 'Test Tag'})
|
|
|
|
message.attributes = [{
|
|
'name': 'test',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.multi.tag',
|
|
'value': [tag.id, 'Tag'],
|
|
'definition_changed': True,
|
|
}]
|
|
values = message.read(['attributes'])[0]['attributes'][0]
|
|
self.assertEqual(values['value'], (tag.id, 'Test Tag'))
|
|
self.env.invalidate_all()
|
|
with patch('odoo.addons.test_new_api.models.test_new_api.MultiTag.check_access_rights', _mocked_check_access_rights):
|
|
values = message.read(['attributes'])[0]['attributes'][0]
|
|
self.assertEqual(values['value'], (tag.id, None))
|
|
|
|
# a user read a properties with a many2one to a record
|
|
# but doesn't have access to its parent
|
|
self.env.invalidate_all()
|
|
with patch('odoo.addons.test_new_api.models.test_new_api.Discussion.check_access_rights', _mocked_check_access_rights):
|
|
values = message.read(['attributes'])[0]['attributes'][0]
|
|
self.assertEqual(values['value'], (tag.id, 'Test Tag'))
|
|
|
|
@users('test')
|
|
def test_properties_field_no_parent_access(self):
|
|
"""We can read the child, but not the definition record.
|
|
|
|
Check that the user does not get an `AccessError` when creating a new
|
|
record having a property field whose property definition is stored on
|
|
a record the user does not have access to. The newly created record
|
|
should have the right schema and should be populated with the default
|
|
values stored on the property definition.
|
|
"""
|
|
def _mocked_check_access_rights(records, operation, raise_exception=True):
|
|
if records.env.su:
|
|
return True
|
|
if raise_exception:
|
|
raise AccessError('')
|
|
return False
|
|
|
|
self.env.invalidate_all()
|
|
with patch('odoo.addons.test_new_api.models.test_new_api.Discussion.check_access_rights', _mocked_check_access_rights):
|
|
message = self.env['test_new_api.message'].create({
|
|
'name': 'Test Message',
|
|
'discussion': self.discussion_1.id,
|
|
'author': self.user.id,
|
|
'attributes': {
|
|
'moderator_partner_id': self.partner.id,
|
|
}
|
|
})
|
|
|
|
self.assertEqual(message.attributes, [{
|
|
'name': 'discussion_color_code',
|
|
'string': 'Color Code',
|
|
'type': 'char',
|
|
'default': 'blue',
|
|
'value': 'blue'
|
|
}, {
|
|
'name': 'moderator_partner_id',
|
|
'string': 'Partner',
|
|
'type': 'many2one',
|
|
'comodel': 'test_new_api.partner',
|
|
'value': self.partner.id
|
|
}])
|
|
|
|
def _get_sql_properties(self, message):
|
|
self.env.flush_all()
|
|
|
|
self.env.cr.execute(
|
|
"""
|
|
SELECT attributes
|
|
FROM test_new_api_message
|
|
WHERE id = %s
|
|
""", (message.id, ),
|
|
)
|
|
value = self.env.cr.fetchone()
|
|
self.assertTrue(value and value[0])
|
|
return value[0]
|
|
|
|
def _get_sql_definition(self, discussion):
|
|
self.env.flush_all()
|
|
|
|
self.env.cr.execute(
|
|
"""
|
|
SELECT attributes_definition
|
|
FROM test_new_api_discussion
|
|
WHERE id = %s
|
|
""", (discussion.id, ),
|
|
)
|
|
value = self.env.cr.fetchone()
|
|
self.assertTrue(value and value[0])
|
|
return value[0]
|
|
|
|
def test_properties_inherits(self):
|
|
email = self.env['test_new_api.emailmessage'].create({
|
|
'discussion': self.discussion_1.id,
|
|
'attributes': [{
|
|
'name': 'discussion_color_code',
|
|
'type': 'char',
|
|
'string': 'Color Code',
|
|
'default': 'blue',
|
|
'value': 'red',
|
|
}],
|
|
})
|
|
|
|
values = email.read(['attributes'])
|
|
self.assertEqual(values[0]['attributes'][0]['value'], 'red')
|
|
values = email.message.read(['attributes'])
|
|
self.assertEqual(values[0]['attributes'][0]['value'], 'red')
|