[IMP] mail, *: make mockserver autofill relational fields' inverses
*: web, website_slides. Specifying both side of a relation when seeding test data is cumbersome. Let startServer method handle it. task-2792108 closes odoo/odoo#86939 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -106,25 +106,33 @@ class IrModel(models.Model):
|
||||
for model_name in model_names_to_fetch:
|
||||
model = self.env[model_name]
|
||||
# get fields, relational fields are kept only if the related model is in model_names_to_fetch
|
||||
fields_by_fname = {
|
||||
fname: field
|
||||
for fname, field in model.fields_get(
|
||||
fields_data_by_fname = {
|
||||
fname: field_data
|
||||
for fname, field_data in model.fields_get(
|
||||
attributes=['name', 'type', 'relation', 'required', 'readonly', 'selection', 'string']
|
||||
).items()
|
||||
if not field.get('relation') or field['relation'] in model_names_to_fetch
|
||||
if not field_data.get('relation') or field_data['relation'] in model_names_to_fetch
|
||||
}
|
||||
# exclude date/datetime and binary default values: dates will always be wrong since they are dynamic
|
||||
# and binary values are not needed for now
|
||||
default_values_by_fname = model.default_get([
|
||||
fname for fname, field in fields_by_fname.items()
|
||||
if field['type'] not in ['binary', 'date', 'datetime']
|
||||
fname for fname, field_data in fields_data_by_fname.items()
|
||||
if field_data['type'] not in ['binary', 'date', 'datetime']
|
||||
])
|
||||
tracked_field_names = model._track_get_fields() if 'mail.thread' in model._inherit else []
|
||||
for fname in tracked_field_names:
|
||||
if fname in fields_by_fname:
|
||||
fields_by_fname[fname]['tracking'] = True
|
||||
for fname, value in default_values_by_fname.items():
|
||||
if fname in fields_by_fname:
|
||||
fields_by_fname[fname]['default'] = value
|
||||
fields_by_model_names[model_name] = fields_by_fname
|
||||
for fname, field_data in fields_data_by_fname.items():
|
||||
if fname in tracked_field_names:
|
||||
field_data['tracking'] = True
|
||||
if fname in default_values_by_fname:
|
||||
field_data['default'] = default_values_by_fname[fname]
|
||||
if fname in model._fields:
|
||||
inverse_fields = [
|
||||
field for field in model.pool.field_inverses[model._fields[fname]]
|
||||
if field.model_name in model_names_to_fetch
|
||||
]
|
||||
if inverse_fields:
|
||||
field_data['inverse_fname_by_model_name'] = {field.model_name: field.name for field in inverse_fields}
|
||||
if field_data['type'] == 'many2one_reference':
|
||||
field_data['model_name_ref_fname'] = model._fields[fname].model_field
|
||||
fields_by_model_names[model_name] = fields_data_by_fname
|
||||
return fields_by_model_names
|
||||
|
||||
@@ -519,13 +519,7 @@ MockServer.include({
|
||||
return res;
|
||||
}
|
||||
if (request_list.includes('activities')) {
|
||||
const activities = this._mockSearchRead('mail.activity', [[
|
||||
'|',
|
||||
['id', 'in', thread.activity_ids || []],
|
||||
'&',
|
||||
['res_id', '=', thread.id],
|
||||
['res_model', '=', thread_model],
|
||||
]], {});
|
||||
const activities = this._mockSearchRead('mail.activity', [[['id', 'in', thread.activity_ids || []]]], {});
|
||||
res['activities'] = this._mockMailActivityActivityFormat(activities.map(activity => activity.id));
|
||||
}
|
||||
if (request_list.includes('attachments')) {
|
||||
@@ -535,13 +529,7 @@ MockServer.include({
|
||||
res['attachments'] = this._mockIrAttachment_attachmentFormat(attachments.map(attachment => attachment.id), true);
|
||||
}
|
||||
if (request_list.includes('followers')) {
|
||||
const followers = this._mockSearchRead('mail.followers', [[
|
||||
'|',
|
||||
['id', 'in', thread.message_follower_ids || []],
|
||||
'&',
|
||||
['res_id', '=', thread.id],
|
||||
['res_model', '=', thread_model],
|
||||
]], {});
|
||||
const followers = this._mockSearchRead('mail.followers', [[['id', 'in', thread.message_follower_ids || []]]], {});
|
||||
// search read returns many2one relations as an array [id, display_name].
|
||||
// But the original route does not. Thus, we need to change it now.
|
||||
followers.forEach(follower => follower.partner_id = follower.partner_id[0]);
|
||||
|
||||
+6
-24
@@ -15,10 +15,7 @@ QUnit.module('activity_mark_done_popover_tests.js', {
|
||||
QUnit.test('activity mark done popover simplest layout', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'not_upload_file',
|
||||
can_write: true,
|
||||
@@ -68,10 +65,7 @@ QUnit.test('activity mark done popover simplest layout', async function (assert)
|
||||
QUnit.test('activity with force next mark done popover simplest layout', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'not_upload_file',
|
||||
can_write: true,
|
||||
@@ -122,10 +116,7 @@ QUnit.test('activity with force next mark done popover simplest layout', async f
|
||||
QUnit.test('activity mark done popover mark done without feedback', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'not_upload_file',
|
||||
can_write: true,
|
||||
@@ -167,10 +158,7 @@ QUnit.test('activity mark done popover mark done without feedback', async functi
|
||||
QUnit.test('activity mark done popover mark done with feedback', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'not_upload_file',
|
||||
can_write: true,
|
||||
@@ -221,10 +209,7 @@ QUnit.test('activity mark done popover mark done and schedule next', async funct
|
||||
assert.step('activity_action');
|
||||
throw new Error("The do-action event should not be triggered when the route doesn't return an action");
|
||||
});
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'not_upload_file',
|
||||
can_write: true,
|
||||
@@ -279,10 +264,7 @@ QUnit.test('[technical] activity mark done & schedule next with new action', asy
|
||||
"The content of the action should be correct"
|
||||
);
|
||||
});
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'not_upload_file',
|
||||
can_write: true,
|
||||
|
||||
@@ -16,10 +16,7 @@ QUnit.module('activity_tests.js', {
|
||||
QUnit.test('activity simplest layout', async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
id: 12,
|
||||
res_id: 100,
|
||||
@@ -95,10 +92,7 @@ QUnit.test('activity simplest layout', async function (assert) {
|
||||
QUnit.test('activity with note layout', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
id: 12,
|
||||
note: 'There is no good or bad note',
|
||||
@@ -133,10 +127,7 @@ QUnit.test('activity info layout when planned after tomorrow', async function (a
|
||||
const today = new Date();
|
||||
const fiveDaysFromNow = new Date();
|
||||
fiveDaysFromNow.setDate(today.getDate() + 5);
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
date_deadline: date_to_str(fiveDaysFromNow),
|
||||
id: 12,
|
||||
@@ -176,10 +167,7 @@ QUnit.test('activity info layout when planned tomorrow', async function (assert)
|
||||
const today = new Date();
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
date_deadline: date_to_str(tomorrow),
|
||||
id: 12,
|
||||
@@ -216,10 +204,7 @@ QUnit.test('activity info layout when planned tomorrow', async function (assert)
|
||||
QUnit.test('activity info layout when planned today', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
date_deadline: date_to_str(new Date()),
|
||||
id: 12,
|
||||
@@ -259,10 +244,7 @@ QUnit.test('activity info layout when planned yesterday', async function (assert
|
||||
const today = new Date();
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
date_deadline: date_to_str(yesterday),
|
||||
id: 12,
|
||||
@@ -302,10 +284,7 @@ QUnit.test('activity info layout when planned before yesterday', async function
|
||||
const today = new Date();
|
||||
const fiveDaysBeforeNow = new Date();
|
||||
fiveDaysBeforeNow.setDate(today.getDate() - 5);
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
date_deadline: date_to_str(fiveDaysBeforeNow),
|
||||
id: 12,
|
||||
@@ -342,10 +321,7 @@ QUnit.test('activity info layout when planned before yesterday', async function
|
||||
QUnit.test('activity with a summary layout', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
id: 12,
|
||||
res_id: 100,
|
||||
@@ -382,10 +358,7 @@ QUnit.test('activity with a summary layout', async function (assert) {
|
||||
QUnit.test('activity without summary layout', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_type_id: 1,
|
||||
id: 12,
|
||||
@@ -430,10 +403,7 @@ QUnit.test('activity details toggle', async function (assert) {
|
||||
const today = new Date();
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
create_date: date_to_str(today),
|
||||
create_uid: 1,
|
||||
@@ -492,10 +462,7 @@ QUnit.test('activity details layout', async function (assert) {
|
||||
id: 10,
|
||||
name: 'Pauvre pomme',
|
||||
});
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_type_id: 1,
|
||||
create_date: date_to_str(today),
|
||||
@@ -576,10 +543,7 @@ QUnit.test('activity details layout', async function (assert) {
|
||||
QUnit.test('activity with mail template layout', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.template'].records.push({
|
||||
id: 1,
|
||||
name: "Dummy mail template",
|
||||
@@ -675,10 +639,7 @@ QUnit.test('activity with mail template: preview mail', async function (assert)
|
||||
);
|
||||
});
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 42,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 42 });
|
||||
this.data['mail.template'].records.push({
|
||||
id: 1,
|
||||
name: "Dummy mail template",
|
||||
@@ -716,10 +677,7 @@ QUnit.test('activity with mail template: preview mail', async function (assert)
|
||||
QUnit.test('activity with mail template: send mail', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 42,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 42 });
|
||||
this.data['mail.template'].records.push({
|
||||
id: 1,
|
||||
name: "Dummy mail template",
|
||||
@@ -770,10 +728,7 @@ QUnit.test('activity with mail template: send mail', async function (assert) {
|
||||
QUnit.test('activity upload document is available', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100, });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'upload_file',
|
||||
activity_type_id: 28,
|
||||
@@ -806,10 +761,7 @@ QUnit.test('activity upload document is available', async function (assert) {
|
||||
QUnit.test('activity click on mark as done', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'default',
|
||||
activity_type_id: 1,
|
||||
@@ -856,10 +808,7 @@ QUnit.test('activity click on mark as done', async function (assert) {
|
||||
QUnit.test('activity mark as done popover should focus feedback input on open [REQUIRE FOCUS]', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'default',
|
||||
activity_type_id: 1,
|
||||
@@ -927,10 +876,7 @@ QUnit.test('activity click on edit', async function (assert) {
|
||||
);
|
||||
});
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 42,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 42 });
|
||||
this.data['mail.template'].records.push({
|
||||
id: 1,
|
||||
name: "Dummy mail template",
|
||||
@@ -969,10 +915,7 @@ QUnit.test('activity click on edit', async function (assert) {
|
||||
QUnit.test('activity edition', async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 42,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 42 });
|
||||
this.data['mail.activity'].records.push({
|
||||
can_write: true,
|
||||
icon: 'fa-times',
|
||||
@@ -1064,10 +1007,7 @@ QUnit.test('activity edition', async function (assert) {
|
||||
QUnit.test('activity click on cancel', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_type_id: 1,
|
||||
can_write: true,
|
||||
@@ -1121,10 +1061,7 @@ QUnit.test('activity mark done popover close on ESCAPE', async function (assert)
|
||||
// This test is not in activity_mark_done_popover_tests.js as it requires the activity mark done
|
||||
// component to have a parent in order to allow testing interactions the popover.
|
||||
assert.expect(2);
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'default',
|
||||
activity_type_id: 1,
|
||||
@@ -1164,10 +1101,7 @@ QUnit.test('activity mark done popover click on discard', async function (assert
|
||||
// component to have a parent in order to allow testing interactions the popover.
|
||||
assert.expect(3);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'default',
|
||||
activity_type_id: 1,
|
||||
@@ -1228,10 +1162,7 @@ QUnit.test('data-oe-id & data-oe-model link redirection on click', async functio
|
||||
assert.step('do-action:openFormView_some.model_250');
|
||||
});
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'default',
|
||||
activity_type_id: 1,
|
||||
@@ -1268,10 +1199,7 @@ QUnit.test('button related to file uploading is replaced when updating activity
|
||||
assert.expect(2);
|
||||
|
||||
const activityId = 513;
|
||||
this.data['res.partner'].records.push({
|
||||
activity_ids: [activityId],
|
||||
id: 100,
|
||||
});
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
activity_category: 'upload_file',
|
||||
activity_type_id: 28,
|
||||
|
||||
@@ -76,7 +76,7 @@ QUnit.test('base rendering editable', async function (assert) {
|
||||
QUnit.test('hover following button', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [1] });
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.followers'].records.push({
|
||||
id: 1,
|
||||
is_active: true,
|
||||
@@ -143,7 +143,7 @@ QUnit.test('hover following button', async function (assert) {
|
||||
QUnit.test('click on "follow" button', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [1] });
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.followers'].records.push({
|
||||
id: 1,
|
||||
is_active: true,
|
||||
@@ -197,7 +197,7 @@ QUnit.test('click on "follow" button', async function (assert) {
|
||||
QUnit.test('click on "unfollow" button', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [1] });
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.followers'].records.push({
|
||||
id: 1,
|
||||
is_active: true,
|
||||
|
||||
@@ -187,7 +187,7 @@ QUnit.test('click on partner follower details', async function (assert) {
|
||||
QUnit.test('click on edit follower', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [2] });
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
this.data['mail.followers'].records.push({
|
||||
id: 2,
|
||||
is_active: true,
|
||||
|
||||
@@ -490,6 +490,7 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/tests/webclient/**/helpers.js',
|
||||
'web/static/tests/qunit.js',
|
||||
'web/static/tests/main.js',
|
||||
'web/static/tests/mock_relational_fields_tests.js',
|
||||
'web/static/tests/mock_server_tests.js',
|
||||
'web/static/tests/setup.js',
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ var MockServer = Class.extend({
|
||||
}
|
||||
}
|
||||
|
||||
// fill relational fields' inverse.
|
||||
for (const modelName in this.data) {
|
||||
this.data[modelName].records.forEach(record => this._updateComodelRelationalFields(modelName, record.id, record));
|
||||
}
|
||||
this.debug = options.debug;
|
||||
|
||||
this.currentDate = options.currentDate || moment().format("YYYY-MM-DD");
|
||||
@@ -578,6 +582,7 @@ var MockServer = Class.extend({
|
||||
model.records.push(record);
|
||||
this._applyDefaults(model, values);
|
||||
this._writeRecord(modelName, values, id);
|
||||
this._updateComodelRelationalFields(modelName, id, values);
|
||||
return id;
|
||||
},
|
||||
/**
|
||||
@@ -1846,14 +1851,18 @@ var MockServer = Class.extend({
|
||||
return _.contains(ids, record.id);
|
||||
});
|
||||
|
||||
// update value of one2many fields pointing to the deleted records
|
||||
// update value of relationnal fields pointing to the deleted records
|
||||
_.each(this.data, function (d) {
|
||||
var relatedFields = _.pick(d.fields, function (field) {
|
||||
return field.type === 'one2many' && field.relation === model;
|
||||
return field.relation === model;
|
||||
});
|
||||
_.each(Object.keys(relatedFields), function (relatedField) {
|
||||
_.each(d.records, function (record) {
|
||||
record[relatedField] = _.difference(record[relatedField], ids);
|
||||
if (Array.isArray(record[relatedField])) {
|
||||
record[relatedField] = _.difference(record[relatedField], ids);
|
||||
} else if (ids.includes(record[relatedField])) {
|
||||
record[relatedField] = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1914,7 +1923,12 @@ var MockServer = Class.extend({
|
||||
* @returns {boolean} currently, always return 'true'
|
||||
*/
|
||||
_mockWrite: function (model, args) {
|
||||
_.each(args[0], this._writeRecord.bind(this, model, args[1]));
|
||||
_.each(args[0], id => {
|
||||
const originalRecord = this._mockSearchRead(model, [[['id', '=', id]]], {})[0];
|
||||
this._writeRecord(model, args[1], id);
|
||||
const updatedRecord = this.data[model].records.find(record => record.id === id);
|
||||
this._updateComodelRelationalFields(model, id, updatedRecord, originalRecord);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
@@ -2076,6 +2090,66 @@ var MockServer = Class.extend({
|
||||
_.each(tree.childNodes, function (c) { self._traverse(c, f); });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Fill all inverse fields of the relational fields present in the record
|
||||
* to be created/updated.
|
||||
*
|
||||
* @param {string} modelName
|
||||
* @param {Object} record record that have been created/updated.
|
||||
* @param {Object|undefined} originalRecord record before update.
|
||||
*/
|
||||
_updateComodelRelationalFields(modelName, recordId, record, originalRecord) {
|
||||
for (const fname in record) {
|
||||
const field = this.data[modelName].fields[fname];
|
||||
const comodelName = field.relation || record[field['model_name_ref_fname']];
|
||||
const inverseFieldName = field['inverse_fname_by_model_name'] && field['inverse_fname_by_model_name'][comodelName];
|
||||
if (!inverseFieldName) {
|
||||
// field has no inverse, skip it.
|
||||
continue;
|
||||
}
|
||||
const relatedRecordIds = Array.isArray(record[fname]) ? record[fname] : [record[fname]];
|
||||
// we only want to set a value for comodel inverse field if the model field has a value.
|
||||
if (record[fname]) {
|
||||
for (const relatedRecordId of relatedRecordIds) {
|
||||
let inverseFieldNewValue = recordId;
|
||||
const relatedRecord = this.data[comodelName].records.find(record => record.id === relatedRecordId);
|
||||
const relatedFieldValue = relatedRecord && relatedRecord[inverseFieldName];
|
||||
if (
|
||||
relatedFieldValue === undefined ||
|
||||
relatedFieldValue === recordId ||
|
||||
field.type !== 'one2many' && relatedFieldValue.includes(recordId)
|
||||
) {
|
||||
// related record does not exist or the related value is already up to date.
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(relatedFieldValue)) {
|
||||
inverseFieldNewValue = [...relatedFieldValue, recordId];
|
||||
}
|
||||
this._writeRecord(comodelName, { [inverseFieldName]: inverseFieldNewValue }, relatedRecordId);
|
||||
}
|
||||
} else if (field.type === 'many2one_reference') {
|
||||
// we need to clean the many2one_field as well.
|
||||
const comodel_inverse_field = this.data[comodelName].fields[inverseFieldName];
|
||||
const model_many2one_field = comodel_inverse_field['inverse_fname_by_model_name'][modelName];
|
||||
this._writeRecord(modelName, { [model_many2one_field]: false }, recordId);
|
||||
}
|
||||
// it's an update, get the records that were originally referenced but are not
|
||||
// anymore and update their relational fields.
|
||||
if (originalRecord) {
|
||||
const originalRecordIds = Array.isArray(originalRecord[fname]) ? originalRecord[fname] : [originalRecord[fname]];
|
||||
// search read returns [id, name], let's ensure the removedRecordIds are integers.
|
||||
const removedRecordIds = originalRecordIds.filter(recordId => Number.isInteger(recordId) && !relatedRecordIds.includes(recordId));
|
||||
for (const removedRecordId of removedRecordIds) {
|
||||
const removedRecord = this.data[comodelName].records.find(record => record.id === removedRecordId);
|
||||
let inverseFieldNewValue = false;
|
||||
if (Array.isArray(removedRecord[inverseFieldName])) {
|
||||
inverseFieldNewValue = removedRecord[inverseFieldName].filter(id => id !== recordId);
|
||||
}
|
||||
this._writeRecord(comodelName, { [inverseFieldName]: inverseFieldNewValue }, removedRecordId);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Write a record. The main difficulty is that we have to apply x2many
|
||||
* commands
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import MockServer from 'web.MockServer';
|
||||
|
||||
QUnit.module('web', {}, function () {
|
||||
QUnit.module('mock_relational_fields_tests.js', {
|
||||
beforeEach() {
|
||||
this.data = {
|
||||
foo: {
|
||||
fields: {
|
||||
one2many_field: { type: 'one2many', relation: 'bar', inverse_fname_by_model_name: { bar: 'many2one_field' } },
|
||||
many2one_field: { type: 'many2one', relation: 'bar', inverse_fname_by_model_name: { bar: 'one2many_field' } },
|
||||
many2many_field: { type: 'many2many', relation: 'bar', inverse_fname_by_model_name: { bar: 'many2many_field' } },
|
||||
many2one_reference: { type: 'many2one_reference', model_name_ref_fname: 'res_model', inverse_fname_by_model_name: { bar: 'one2many_field' } },
|
||||
res_model: { type: 'char' },
|
||||
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
bar: {
|
||||
fields: {
|
||||
many2one_field: { type: 'many2one', relation: 'foo' },
|
||||
one2many_field: { type: 'one2many', relation: 'foo', inverse_fname_by_model_name: { foo: 'many2one_field' } },
|
||||
many2many_field: { type: 'many2many', relation: 'foo', inverse_fname_by_model_name: { foo: 'many2many_field' } },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('many2one_ref should auto fill inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['foo'].records.push({
|
||||
id: 2,
|
||||
res_model: 'bar',
|
||||
many2one_reference: 1,
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
assert.deepEqual([2], mockServer.data['bar'].records[0].one2many_field);
|
||||
|
||||
mockServer._mockUnlink('foo', [2]);
|
||||
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
|
||||
});
|
||||
|
||||
QUnit.test('many2one should auto fill inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['foo'].records.push({
|
||||
id: 2,
|
||||
many2one_field: 1,
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
assert.deepEqual([2], mockServer.data['bar'].records[0].one2many_field);
|
||||
|
||||
mockServer._mockUnlink('foo', [2]);
|
||||
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
|
||||
});
|
||||
|
||||
QUnit.test('one2many should auto fill inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['bar'].records.push({ id: 2 });
|
||||
this.data['foo'].records.push({
|
||||
id: 3,
|
||||
one2many_field: [1, 2],
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
assert.strictEqual(3, mockServer.data['bar'].records[0].many2one_field);
|
||||
assert.strictEqual(3, mockServer.data['bar'].records[1].many2one_field);
|
||||
|
||||
mockServer._mockUnlink('foo', [3]);
|
||||
assert.strictEqual(false, mockServer.data['bar'].records[0].many2one_field);
|
||||
assert.strictEqual(false, mockServer.data['bar'].records[1].many2one_field);
|
||||
});
|
||||
|
||||
QUnit.test('many2many should auto fill inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['foo'].records.push({
|
||||
id: 2,
|
||||
many2many_field: [1],
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
assert.deepEqual([2], mockServer.data['bar'].records[0].many2many_field);
|
||||
|
||||
mockServer._mockUnlink('foo', [2]);
|
||||
assert.deepEqual([], mockServer.data['bar'].records[0].many2many_field);
|
||||
});
|
||||
|
||||
QUnit.test('one2many update should update inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['bar'].records.push({ id: 2 });
|
||||
this.data['foo'].records.push({
|
||||
id: 3,
|
||||
one2many_field: [1, 2],
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
mockServer._mockWrite('foo', [[3], { one2many_field: [1] }]);
|
||||
assert.strictEqual(3, mockServer.data['bar'].records[0].many2one_field);
|
||||
assert.strictEqual(false, mockServer.data['bar'].records[1].many2one_field);
|
||||
});
|
||||
|
||||
QUnit.test('many2many update should update inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['foo'].records.push({
|
||||
id: 2,
|
||||
many2many_field: [1],
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
mockServer._mockWrite('foo', [[2], { many2many_field: [] }]);
|
||||
assert.deepEqual([], mockServer.data['bar'].records[0].many2many_field);
|
||||
});
|
||||
|
||||
QUnit.test('many2one update should update inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['foo'].records.push({
|
||||
id: 2,
|
||||
many2one_field: 1,
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
mockServer._mockWrite('foo', [[2], { many2one_field: false }]);
|
||||
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
|
||||
});
|
||||
|
||||
QUnit.test('many2one_ref update should update inverse field', async function (assert) {
|
||||
this.data['bar'].records.push({ id: 1 });
|
||||
this.data['foo'].records.push({
|
||||
id: 2,
|
||||
res_model: 'bar',
|
||||
many2one_reference: 1,
|
||||
});
|
||||
const mockServer = new MockServer(this.data, {});
|
||||
mockServer._mockWrite('foo', [[2], { many2one_reference: false }]);
|
||||
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -14,10 +14,7 @@ QUnit.test('grant course access', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 5 });
|
||||
this.data['slide.channel'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['slide.channel'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
can_write: true,
|
||||
id: 12,
|
||||
@@ -54,10 +51,7 @@ QUnit.test('refuse course access', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 5 });
|
||||
this.data['slide.channel'].records.push({
|
||||
activity_ids: [12],
|
||||
id: 100,
|
||||
});
|
||||
this.data['slide.channel'].records.push({ id: 100 });
|
||||
this.data['mail.activity'].records.push({
|
||||
can_write: true,
|
||||
id: 12,
|
||||
|
||||
Reference in New Issue
Block a user