[IMP] mail: split localId and record indexing
Part of task-2741982 closes odoo/odoo#97026 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -64,11 +64,17 @@ export class ChatterContainer extends Component {
|
||||
if (owl.status(this) === "destroyed") {
|
||||
return;
|
||||
}
|
||||
const values = { id: this.chatterId, ...props, className: undefined };
|
||||
const values = { id: this.chatterId, ...props };
|
||||
delete values.className;
|
||||
if (values.threadId === undefined) {
|
||||
values.threadId = clear();
|
||||
}
|
||||
this.chatter = messaging.models['Chatter'].insert(values);
|
||||
if (owl.status(this) === "destroyed") {
|
||||
// insert might trigger a re-render which might destroy the current component
|
||||
this.chatter.delete();
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Refresh the chatter when the parent view is (re)loaded.
|
||||
* This serves mainly at loading initial data, but also on reload there
|
||||
|
||||
+13
-10
@@ -1,6 +1,5 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useModels } from '@mail/component_hooks/use_models';
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/components/discuss_public_view/discuss_public_view';
|
||||
import { getMessagingComponent } from "@mail/utils/messaging_component";
|
||||
@@ -13,16 +12,17 @@ export class DiscussPublicViewContainer extends Component {
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
useModels();
|
||||
super.setup();
|
||||
}
|
||||
|
||||
get discussPublicView() {
|
||||
return this.messaging && this.messaging.models['DiscussPublicView'].findFromIdentifyingData(this.messaging);
|
||||
}
|
||||
|
||||
get messaging() {
|
||||
return this.env.services.messaging.modelManager.messaging;
|
||||
this.env.services.messaging.get().then(messaging => {
|
||||
messaging.models['Thread'].insert(messaging.models['Thread'].convertData(this.props.data.channelData));
|
||||
this.discussPublicView = messaging.models['DiscussPublicView'].insert(this.props.data.discussPublicViewData);
|
||||
if (this.discussPublicView.shouldDisplayWelcomeViewInitially) {
|
||||
this.discussPublicView.switchToWelcomeView();
|
||||
} else {
|
||||
this.discussPublicView.switchToThreadView();
|
||||
}
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,4 +30,7 @@ export class DiscussPublicViewContainer extends Component {
|
||||
Object.assign(DiscussPublicViewContainer, {
|
||||
components: { DiscussPublicView: getMessagingComponent('DiscussPublicView') },
|
||||
template: 'mail.DiscussPublicViewContainer',
|
||||
props: {
|
||||
data: Object,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -850,9 +850,19 @@ export class ModelField {
|
||||
throw Error(`${record} is not a record. Did you try to use link() instead of insert() with data?`);
|
||||
}
|
||||
const otherModel = record.modelManager.models[this.to];
|
||||
if (!otherModel.modelManager.get(otherModel, record.localId, { isCheckingInheritance: true })) {
|
||||
throw Error(`Record ${record.localId} is not valid for relational field ${this.fieldName}.`);
|
||||
if (otherModel.__records.has(record)) {
|
||||
return;
|
||||
}
|
||||
// support for inherited models (eg. relation targeting `Record`)
|
||||
for (const subModel of Object.values(this.models)) {
|
||||
if (!(subModel.prototype instanceof otherModel)) {
|
||||
continue;
|
||||
}
|
||||
if (subModel.__records.has(record)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw Error(`Record ${record} is not valid for relational field ${this.fieldName}.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
export class ModelIndexAnd {
|
||||
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
this.recordsByValuesTree = new Map();
|
||||
this.valuesByRecords = new Map();
|
||||
this.singleton = undefined;
|
||||
}
|
||||
|
||||
addRecord(record, data) {
|
||||
if (this.model.__identifyingFieldNames.size === 0) {
|
||||
this.singleton = record;
|
||||
return;
|
||||
}
|
||||
const valuesOfRecord = [];
|
||||
let res = this.recordsByValuesTree;
|
||||
const { length, [length - 1]: lastFieldName } = [...this.model.__identifyingFieldNames];
|
||||
for (const fieldName of this.model.__identifyingFieldNames) {
|
||||
const fieldValue = data[fieldName];
|
||||
if (fieldValue === undefined) {
|
||||
throw new Error(`Identifying field "${fieldName}" is lacking a value on ${this.model} with 'and' identifying mode`);
|
||||
}
|
||||
valuesOfRecord.push(fieldValue);
|
||||
if (!res.has(fieldValue)) {
|
||||
res.set(fieldValue, fieldName === lastFieldName ? record : new Map());
|
||||
}
|
||||
res = res.get(fieldValue);
|
||||
}
|
||||
this.valuesByRecords.set(record, valuesOfRecord);
|
||||
}
|
||||
|
||||
findRecord(data) {
|
||||
if (this.model.__identifyingFieldNames.size === 0) {
|
||||
return this.singleton;
|
||||
}
|
||||
let res = this.recordsByValuesTree;
|
||||
for (const fieldName of this.model.__identifyingFieldNames) {
|
||||
const fieldValue = data[fieldName];
|
||||
if (fieldValue === undefined) {
|
||||
throw new Error(`Identifying field "${fieldName}" is lacking a value on ${this.model} with 'and' identifying mode`);
|
||||
}
|
||||
res = res.get(fieldValue);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
removeRecord(record) {
|
||||
if (this.model.__identifyingFieldNames.size === 0) {
|
||||
this.singleton = undefined;
|
||||
return;
|
||||
}
|
||||
const values = this.valuesByRecords.get(record);
|
||||
let res = this.recordsByValuesTree;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const index = values[i];
|
||||
if (i === values.length - 1) {
|
||||
res.delete(index);
|
||||
break;
|
||||
}
|
||||
res = res.get(index);
|
||||
}
|
||||
this.valuesByRecords.delete(record);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
export class ModelIndexXor {
|
||||
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
this.recordsByValuesByFields = new Map();
|
||||
this.fieldNameAndValueByRecords = new Map();
|
||||
}
|
||||
|
||||
addRecord(record, data) {
|
||||
const [fieldName, fieldValue] = [...this.model.__identifyingFieldNames].reduce(([fieldName, fieldValue], currentFieldName) => {
|
||||
const currentFieldValue = data[currentFieldName];
|
||||
if (currentFieldValue === undefined) {
|
||||
return [fieldName, fieldValue];
|
||||
}
|
||||
if (fieldName) {
|
||||
throw new Error(`Identifying field on ${this.model} with 'xor' identifying mode should have only one of the conditional values given in data. Currently have both "${fieldName}" and "${currentFieldName}".`);
|
||||
}
|
||||
return [currentFieldName, currentFieldValue];
|
||||
}, [undefined, undefined]);
|
||||
if (!this.recordsByValuesByFields.has(fieldName)) {
|
||||
this.recordsByValuesByFields.set(fieldName, new Map());
|
||||
}
|
||||
this.recordsByValuesByFields.get(fieldName).set(fieldValue, record);
|
||||
this.fieldNameAndValueByRecords.set(record, [fieldName, fieldValue]);
|
||||
}
|
||||
|
||||
findRecord(data) {
|
||||
const [fieldName, fieldValue] = [...this.model.__identifyingFieldNames].reduce(([fieldName, fieldValue], currentFieldName) => {
|
||||
const currentFieldValue = data[currentFieldName];
|
||||
if (currentFieldValue === undefined) {
|
||||
return [fieldName, fieldValue];
|
||||
}
|
||||
if (fieldName) {
|
||||
throw new Error(`Identifying field on ${this.model} with 'xor' identifying mode should have only one of the conditional values given in data. Currently have both "${fieldName}" and "${currentFieldName}".`);
|
||||
}
|
||||
return [currentFieldName, currentFieldValue];
|
||||
}, [undefined, undefined]);
|
||||
if (!this.recordsByValuesByFields.has(fieldName)) {
|
||||
this.recordsByValuesByFields.set(fieldName, new Map());
|
||||
}
|
||||
return this.recordsByValuesByFields.get(fieldName).get(fieldValue);
|
||||
}
|
||||
|
||||
removeRecord(record) {
|
||||
const [fieldName, fieldValue] = this.fieldNameAndValueByRecords.get(record);
|
||||
this.recordsByValuesByFields.get(fieldName).delete(fieldValue);
|
||||
this.fieldNameAndValueByRecords.delete(record);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,29 +31,29 @@ export class Listener {
|
||||
this.name = name;
|
||||
this.onChange = onChange;
|
||||
/**
|
||||
* Set of localIds that have been accessed on model manager between the
|
||||
* Set of records that have been accessed on model manager between the
|
||||
* last call to `startListening` and `stopListening` with this listener
|
||||
* as parameter.
|
||||
* Each localId has its own way to know the listeners that are observing
|
||||
* Each record has its own way to know the listeners that are observing
|
||||
* it (to be able to notify them if it changes). This set holds the
|
||||
* inverse of that information, which is useful to be able to remove
|
||||
* this listener (when the need arises) from those localIds without
|
||||
* this listener (when the need arises) from those records without
|
||||
* having to verify the presence of this listener on each possible
|
||||
* localId one by one.
|
||||
* record one by one.
|
||||
*/
|
||||
this.lastObservedLocalIds = new Set();
|
||||
this.lastObservedRecords = new Set();
|
||||
/**
|
||||
* Map between localIds and a set of fields on those localIds that have
|
||||
* Map between records and a set of fields on those records that have
|
||||
* been accessed on model manager between the last call to
|
||||
* `startListening` and `stopListening` with this listener as parameter.
|
||||
* Each field of each localId has its own way to know the listeners that
|
||||
* Each field of each record has its own way to know the listeners that
|
||||
* are observing it (to be able to notify them if it changes). This map
|
||||
* holds the inverse of that information, which is useful to be able to
|
||||
* remove this listener (when the need arises) from those fields without
|
||||
* having to verify the presence of this listener on each possible field
|
||||
* one by one.
|
||||
*/
|
||||
this.lastObservedFieldsByLocalId = new Map();
|
||||
this.lastObservedFieldsByRecord = new Map();
|
||||
/**
|
||||
* Set of model that have been accessed with `all()` on model manager
|
||||
* between the last call to `startListening` and `stopListening` with
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import { registry } from '@mail/model/model_core';
|
||||
import { ModelField } from '@mail/model/model_field';
|
||||
import { ModelIndexAnd } from '@mail/model/model_index_and';
|
||||
import { ModelIndexXor } from '@mail/model/model_index_xor';
|
||||
import { FieldCommand, unlinkAll } from '@mail/model/model_field_command';
|
||||
import { RelationSet } from '@mail/model/model_field_relation_set';
|
||||
import { Listener } from '@mail/model/model_listener';
|
||||
import { followRelations } from '@mail/model/model_utils';
|
||||
import { makeDeferred } from '@mail/utils/deferred';
|
||||
|
||||
/**
|
||||
* Object that manage models and records, notably their update cycle: whenever
|
||||
* some records are requested for update (either with model static method
|
||||
@@ -71,16 +72,14 @@ export class ModelManager {
|
||||
*/
|
||||
this._listenersObservingAllByModel = new Map();
|
||||
/**
|
||||
* Map between localId and a set of listeners that are using it. The
|
||||
* following fields use localId instead of record reference because the
|
||||
* listener might try to use a record that doesn't exist yet.
|
||||
* Map between record and a set of listeners that are using it.
|
||||
*/
|
||||
this._listenersObservingLocalId = new Map();
|
||||
this._listenersObservingRecord = new Map();
|
||||
/**
|
||||
* Map between fields of localId and a set of listeners that are
|
||||
* Map between fields of record and a set of listeners that are
|
||||
* using it.
|
||||
*/
|
||||
this._listenersObservingFieldOfLocalId = new Map();
|
||||
this._listenersObservingFieldOfRecord = new Map();
|
||||
/**
|
||||
* Map of listeners that should be notified at the end of the current
|
||||
* update cycle. Value contains list of info to help for debug.
|
||||
@@ -169,7 +168,7 @@ export class ModelManager {
|
||||
entry.set(listener, [info]);
|
||||
}
|
||||
}
|
||||
const allRecords = Object.values(model.__records);
|
||||
const allRecords = [...model.__records];
|
||||
if (filterFunc) {
|
||||
return allRecords.filter(filterFunc);
|
||||
}
|
||||
@@ -213,7 +212,7 @@ export class ModelManager {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exists(model, record) {
|
||||
return Boolean(record.localId);
|
||||
return model.__records.has(record);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,45 +220,30 @@ export class ModelManager {
|
||||
* identifying data, if it exists.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {Object} data
|
||||
* @param {Object} [data={}]
|
||||
* @returns {Record|undefined}
|
||||
*/
|
||||
findFromIdentifyingData(model, data) {
|
||||
return this.get(model, this._getRecordIndex(model, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the record of provided model that matches provided
|
||||
* local id. Useful to convert a local id to a record.
|
||||
* Note that even if there's a record in the system having provided local
|
||||
* id, if the resulting record is not an instance of this model, this getter
|
||||
* assumes the record does not exist.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {string} localId
|
||||
* @param {Object} param2
|
||||
* @param {boolean} [param2.isCheckingInheritance=false]
|
||||
* @returns {Record|undefined} record, if exists
|
||||
*/
|
||||
get(model, localId, { isCheckingInheritance = false } = {}) {
|
||||
if (!localId) {
|
||||
findFromIdentifyingData(model, data = {}) {
|
||||
const data2 = { ...data };
|
||||
for (const fieldName of model.__identifyingFieldNames) {
|
||||
const field = model.__fieldMap[fieldName];
|
||||
if (data2[field.fieldName] === undefined && field.default !== undefined) {
|
||||
data2[field.fieldName] = field.default;
|
||||
}
|
||||
}
|
||||
const record = model.__recordsIndex.findRecord(this._preInsertIdentifyingFieldsFromData(model, data2));
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
if (!isCheckingInheritance && this.isDebug) {
|
||||
const modelName = localId.split('(')[0];
|
||||
if (modelName !== model.name) {
|
||||
throw Error(`wrong format of localId ${localId} for ${model}.`);
|
||||
}
|
||||
}
|
||||
for (const listener of this._listeners) {
|
||||
listener.lastObservedLocalIds.add(localId);
|
||||
if (!this._listenersObservingLocalId.has(localId)) {
|
||||
this._listenersObservingLocalId.set(localId, new Map());
|
||||
listener.lastObservedRecords.add(record);
|
||||
if (!this._listenersObservingRecord.has(record)) {
|
||||
this._listenersObservingRecord.set(record, new Map());
|
||||
}
|
||||
const entry = this._listenersObservingLocalId.get(localId);
|
||||
const entry = this._listenersObservingRecord.get(record);
|
||||
const info = {
|
||||
listener,
|
||||
reason: `get record - ${localId}`,
|
||||
reason: `findFromIdentifyingData record - ${record}`,
|
||||
};
|
||||
if (entry.has(listener)) {
|
||||
entry.get(listener).push(info);
|
||||
@@ -267,24 +251,7 @@ export class ModelManager {
|
||||
entry.set(listener, [info]);
|
||||
}
|
||||
}
|
||||
const record = model.__records[localId];
|
||||
if (record) {
|
||||
return record;
|
||||
}
|
||||
if (!isCheckingInheritance) {
|
||||
return;
|
||||
}
|
||||
// support for inherited models (eg. relation targeting `Record`)
|
||||
for (const subModel of Object.values(this.models)) {
|
||||
if (!(subModel.prototype instanceof model)) {
|
||||
continue;
|
||||
}
|
||||
const record = subModel.__records[localId];
|
||||
if (record) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return;
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,18 +309,18 @@ export class ModelManager {
|
||||
this._listeners.delete(listener);
|
||||
this._listenersToNotifyInUpdateCycle.delete(listener);
|
||||
this._listenersToNotifyAfterUpdateCycle.delete(listener);
|
||||
for (const localId of listener.lastObservedLocalIds) {
|
||||
this._listenersObservingLocalId.get(localId).delete(listener);
|
||||
const listenersObservingFieldOfLocalId = this._listenersObservingFieldOfLocalId.get(localId);
|
||||
for (const field of listener.lastObservedFieldsByLocalId.get(localId) || []) {
|
||||
listenersObservingFieldOfLocalId.get(field).delete(listener);
|
||||
for (const record of listener.lastObservedRecords) {
|
||||
this._listenersObservingRecord.get(record).delete(listener);
|
||||
const listenersObservingFieldOfRecord = this._listenersObservingFieldOfRecord.get(record);
|
||||
for (const field of listener.lastObservedFieldsByRecord.get(record) || []) {
|
||||
listenersObservingFieldOfRecord.get(field).delete(listener);
|
||||
}
|
||||
}
|
||||
for (const model of listener.lastObservedAllByModel) {
|
||||
this._listenersObservingAllByModel.get(model).delete(listener);
|
||||
}
|
||||
listener.lastObservedLocalIds.clear();
|
||||
listener.lastObservedFieldsByLocalId.clear();
|
||||
listener.lastObservedRecords.clear();
|
||||
listener.lastObservedFieldsByRecord.clear();
|
||||
listener.lastObservedAllByModel.clear();
|
||||
}
|
||||
|
||||
@@ -436,9 +403,17 @@ export class ModelManager {
|
||||
// Make model manager accessible from model.
|
||||
model.modelManager = this;
|
||||
model.fields = {};
|
||||
// Contains all records. key is local id, while value is the record.
|
||||
model.__records = {};
|
||||
model.identifyingMode = definition.get('identifyingMode');
|
||||
model.__records = new Set();
|
||||
model.__recordCount = 0;
|
||||
model.__recordsIndex = (() => {
|
||||
switch (model.identifyingMode) {
|
||||
case 'and':
|
||||
return new ModelIndexAnd(model);
|
||||
case 'xor':
|
||||
return new ModelIndexXor(model);
|
||||
}
|
||||
})();
|
||||
this._listenersObservingAllByModel.set(model, new Map());
|
||||
this.models[model.name] = model;
|
||||
}
|
||||
@@ -650,10 +625,10 @@ export class ModelManager {
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} model
|
||||
* @param {string} localId
|
||||
* @returns {Record}
|
||||
*/
|
||||
_create(model, localId) {
|
||||
_create(model) {
|
||||
const localId = `${model.name}_${++model.__recordCount}`;
|
||||
/**
|
||||
* Prepare record state. Assign various keys and values that are
|
||||
* expected to be found on every record.
|
||||
@@ -690,17 +665,17 @@ export class ModelManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this._listenersObservingLocalId.has(localId)) {
|
||||
this._listenersObservingLocalId.set(localId, new Map());
|
||||
if (!this._listenersObservingRecord.has(record)) {
|
||||
this._listenersObservingRecord.set(record, new Map());
|
||||
}
|
||||
this._listenersObservingFieldOfLocalId.set(localId, new Map());
|
||||
this._listenersObservingFieldOfRecord.set(record, new Map());
|
||||
for (const field of model.__fieldList) {
|
||||
this._listenersObservingFieldOfLocalId.get(localId).set(field, new Map());
|
||||
this._listenersObservingFieldOfRecord.get(record).set(field, new Map());
|
||||
}
|
||||
/**
|
||||
* Register record.
|
||||
*/
|
||||
model.__records[record.localId] = record;
|
||||
model.__records.add(record);
|
||||
/**
|
||||
* Auto-bind record methods so that `this` always refer to the record.
|
||||
*/
|
||||
@@ -722,10 +697,10 @@ export class ModelManager {
|
||||
infoList,
|
||||
});
|
||||
}
|
||||
for (const [listener, infoList] of this._listenersObservingLocalId.get(localId)) {
|
||||
for (const [listener, infoList] of this._listenersObservingRecord.get(record)) {
|
||||
this._markListenerToNotify(listener, {
|
||||
listener,
|
||||
reason: `_create: localId - ${record}`,
|
||||
reason: `_create: record - ${record}`,
|
||||
infoList,
|
||||
});
|
||||
}
|
||||
@@ -740,7 +715,7 @@ export class ModelManager {
|
||||
this._ensureNoLockingListener();
|
||||
const model = record.constructor;
|
||||
if (!record.exists()) {
|
||||
throw Error(`Cannot delete already deleted record ${record.localId}.`);
|
||||
throw Error(`Cannot delete already deleted record ${record}.`);
|
||||
}
|
||||
const lifecycleHooks = registry.get(model.name).get('lifecycleHooks');
|
||||
if (lifecycleHooks.has('_willDelete')) {
|
||||
@@ -758,14 +733,15 @@ export class ModelManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
model.__recordsIndex.removeRecord(record);
|
||||
this._createdRecordsComputes.delete(record);
|
||||
this._createdRecordsCreated.delete(record);
|
||||
this._createdRecordsOnChange.delete(record);
|
||||
this._updatedRecordsCheckRequired.delete(record);
|
||||
for (const [listener, infoList] of this._listenersObservingLocalId.get(record.localId)) {
|
||||
for (const [listener, infoList] of this._listenersObservingRecord.get(record)) {
|
||||
this._markListenerToNotify(listener, {
|
||||
listener,
|
||||
reason: `_delete: localId - ${record}`,
|
||||
reason: `_delete: record - ${record}`,
|
||||
infoList,
|
||||
});
|
||||
}
|
||||
@@ -778,7 +754,7 @@ export class ModelManager {
|
||||
}
|
||||
delete record.__values;
|
||||
delete record.__listeners;
|
||||
delete model.__records[record.localId];
|
||||
model.__records.delete(record);
|
||||
delete record.localId;
|
||||
}
|
||||
|
||||
@@ -966,91 +942,6 @@ export class ModelManager {
|
||||
this._checkProcessedFieldsOnModels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an index on the given model for the given data.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {Object|Record} data insert data or record
|
||||
* @returns {string}
|
||||
*/
|
||||
_getRecordIndex(model, data) {
|
||||
const parts = [];
|
||||
switch (model.identifyingMode) {
|
||||
case 'and':
|
||||
for (const fieldName of model.__identifyingFieldNames) {
|
||||
const fieldValue = this._getValueFromDataOrFromDefault(model, fieldName, data);
|
||||
if (fieldValue === undefined) {
|
||||
throw new Error(`Identifying field "${fieldName}" is lacking a value on ${model} with 'and' identifying mode`);
|
||||
}
|
||||
parts.push(this._getRecordIndexFromField(model, fieldName, fieldValue));
|
||||
}
|
||||
break;
|
||||
case 'xor': {
|
||||
const [fieldName, fieldValue] = [...model.__identifyingFieldNames].reduce(([fieldName, fieldValue], currentFieldName) => {
|
||||
const currentFieldValue = this._getValueFromDataOrFromDefault(model, currentFieldName, data);
|
||||
if (currentFieldValue === undefined) {
|
||||
return [fieldName, fieldValue];
|
||||
}
|
||||
if (fieldName) {
|
||||
throw new Error(`Identifying field on ${model} with 'xor' identifying mode should have only one of the conditional values given in data. Currently have both "${fieldName}" and "${currentFieldName}".`);
|
||||
}
|
||||
return [currentFieldName, currentFieldValue];
|
||||
}, [undefined, undefined]);
|
||||
parts.push(this._getRecordIndexFromField(model, fieldName, fieldValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `${model.name}(${parts.join(', ')})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part of the record index that comes from the given fieldName
|
||||
* with the given fieldValue.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {string} fieldName
|
||||
* @param {any} fieldValue
|
||||
* @returns {string}
|
||||
*/
|
||||
_getRecordIndexFromField(model, fieldName, fieldValue) {
|
||||
const relationTo = model.__fieldMap[fieldName].to;
|
||||
if (!relationTo) {
|
||||
return `${fieldName}: ${fieldValue}`;
|
||||
}
|
||||
const OtherModel = this.models[relationTo];
|
||||
if (fieldValue instanceof OtherModel) {
|
||||
return `${fieldName}: ${this._getRecordIndex(OtherModel, fieldValue)}`;
|
||||
}
|
||||
const fieldValue2 = model.__fieldMap[fieldName].convertToFieldCommandList(fieldValue)[0];
|
||||
if (!(fieldValue2 instanceof FieldCommand)) {
|
||||
throw new Error(`Identifying element "${model}/${fieldName}" is expecting a command for relational field.`);
|
||||
}
|
||||
if (!['link', 'insert', 'replace', 'insert-and-replace'].includes(fieldValue2._name)) {
|
||||
throw new Error(`Identifying element "${model}/${fieldName}" is expecting a command of type "insert-and-replace", "replace", "insert" or "link". "${fieldValue2._name}" was given instead.`);
|
||||
}
|
||||
const relationValue = fieldValue2._value;
|
||||
if (!relationValue) {
|
||||
throw new Error(`Identifying element "${model}/${fieldName}" is lacking a relation value.`);
|
||||
}
|
||||
return `${fieldName}: ${this._getRecordIndex(OtherModel, relationValue)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the given fieldName from the provided data if it is
|
||||
* defined or from default value of this field otherwise.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {string} fieldName
|
||||
* @param {Object} data
|
||||
* @returns {any}
|
||||
*/
|
||||
_getValueFromDataOrFromDefault(model, fieldName, data) {
|
||||
if (data[fieldName] !== undefined) {
|
||||
return data[fieldName];
|
||||
}
|
||||
return model.__fieldMap[fieldName].default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} model
|
||||
@@ -1062,11 +953,12 @@ export class ModelManager {
|
||||
this._ensureNoLockingListener();
|
||||
const records = [];
|
||||
for (const data of dataList) {
|
||||
const localId = this._getRecordIndex(model, data);
|
||||
let record = this.get(model, localId);
|
||||
let record = this.findFromIdentifyingData(model, data);
|
||||
if (!record) {
|
||||
record = this._create(model, localId);
|
||||
this._update(record, this._addDefaultData(model, data), { ...options, allowWriteReadonly: true });
|
||||
const data2 = this._preInsertIdentifyingFieldsFromData(model, this._addDefaultData(model, data));
|
||||
record = this._create(model);
|
||||
model.__recordsIndex.addRecord(record, data2);
|
||||
this._update(record, data2, { ...options, allowWriteReadonly: true });
|
||||
} else {
|
||||
this._update(record, data, options);
|
||||
}
|
||||
@@ -1134,7 +1026,7 @@ export class ModelManager {
|
||||
* @param {ModelField} field
|
||||
*/
|
||||
_markRecordFieldAsChanged(record, field) {
|
||||
for (const [listener, infoList] of this._listenersObservingFieldOfLocalId.get(record.localId).get(field)) {
|
||||
for (const [listener, infoList] of this._listenersObservingFieldOfRecord.get(record).get(field)) {
|
||||
this._markListenerToNotify(listener, {
|
||||
listener,
|
||||
reason: `_update: ${field} of ${record}`,
|
||||
@@ -1183,6 +1075,54 @@ export class ModelManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes all commands given in data that concerns relation fields that
|
||||
* are identifying to execute their respective "insert-and-replace" commands
|
||||
* and to replace them by corresponding "replace" commands.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {Object} [data={}]
|
||||
* @returns {Object}
|
||||
*/
|
||||
_preInsertIdentifyingFieldsFromData(model, data) {
|
||||
const data2 = { ...data };
|
||||
for (const [fieldName, value] of Object.entries(data2)) {
|
||||
const field = model.__fieldMap[fieldName];
|
||||
if (!field) {
|
||||
throw new Error(`"${fieldName}" is not a field on "${model}".`);
|
||||
}
|
||||
if (!field.to) {
|
||||
continue;
|
||||
}
|
||||
if (!field.identifying) {
|
||||
continue;
|
||||
}
|
||||
const commands = field.convertToFieldCommandList(value);
|
||||
if (commands.length !== 1) {
|
||||
throw new Error(`Identifying field "${model}/${fieldName}" should receive a single command.`);
|
||||
}
|
||||
const [command] = commands;
|
||||
if (!(command instanceof FieldCommand)) {
|
||||
throw new Error(`Identifying field "${model}/${fieldName}" should receive a command.`);
|
||||
}
|
||||
if (!['insert-and-replace', 'replace'].includes(command._name)) {
|
||||
throw new Error(`Identifying field "${model}/${fieldName}" should receive a "replace" or "insert-and-replace" command.`);
|
||||
}
|
||||
if (command._name === 'replace') {
|
||||
continue;
|
||||
}
|
||||
if (!command._value) {
|
||||
throw new Error(`Identifying field "${model}/${fieldName}" is lacking a relation value.`);
|
||||
}
|
||||
if (typeof command._value[Symbol.iterator] === 'function') {
|
||||
throw new Error(`Identifying field "${model}/${fieldName}" should receive a single data object.`);
|
||||
}
|
||||
const [record] = this._insert(this.models[field.to], [command._value]);
|
||||
data2[fieldName] = record;
|
||||
}
|
||||
return data2;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function processes definition of declared fields in provided models.
|
||||
* Basically, models have fields declared in static prop `fields`, and this
|
||||
@@ -1280,24 +1220,24 @@ export class ModelManager {
|
||||
get: function getFieldValue() { // this is bound to record
|
||||
const field = model.__fieldMap[fieldName];
|
||||
if (this.modelManager._listeners.size) {
|
||||
if (!this.modelManager._listenersObservingLocalId.has(this.localId)) {
|
||||
this.modelManager._listenersObservingLocalId.set(this.localId, new Map());
|
||||
if (!this.modelManager._listenersObservingRecord.has(this)) {
|
||||
this.modelManager._listenersObservingRecord.set(this, new Map());
|
||||
}
|
||||
const entryLocalId = this.modelManager._listenersObservingLocalId.get(this.localId);
|
||||
const entryRecord = this.modelManager._listenersObservingRecord.get(this);
|
||||
const reason = `getField - ${field} of ${this}`;
|
||||
const entryField = this.modelManager._listenersObservingFieldOfLocalId.get(this.localId).get(field);
|
||||
const entryField = this.modelManager._listenersObservingFieldOfRecord.get(this).get(field);
|
||||
for (const listener of this.modelManager._listeners) {
|
||||
listener.lastObservedLocalIds.add(this.localId);
|
||||
listener.lastObservedRecords.add(this);
|
||||
const info = { listener, reason };
|
||||
if (entryLocalId.has(listener)) {
|
||||
entryLocalId.get(listener).push(info);
|
||||
if (entryRecord.has(listener)) {
|
||||
entryRecord.get(listener).push(info);
|
||||
} else {
|
||||
entryLocalId.set(listener, [info]);
|
||||
entryRecord.set(listener, [info]);
|
||||
}
|
||||
if (!listener.lastObservedFieldsByLocalId.has(this.localId)) {
|
||||
listener.lastObservedFieldsByLocalId.set(this.localId, new Set());
|
||||
if (!listener.lastObservedFieldsByRecord.has(this)) {
|
||||
listener.lastObservedFieldsByRecord.set(this, new Set());
|
||||
}
|
||||
listener.lastObservedFieldsByLocalId.get(this.localId).add(field);
|
||||
listener.lastObservedFieldsByRecord.get(this).add(field);
|
||||
if (entryField.has(listener)) {
|
||||
entryField.get(listener).push(info);
|
||||
} else {
|
||||
@@ -1324,7 +1264,7 @@ export class ModelManager {
|
||||
_update(record, data, options = {}) {
|
||||
this._ensureNoLockingListener();
|
||||
if (!record.exists()) {
|
||||
throw Error(`Cannot update already deleted record ${record.localId}.`);
|
||||
throw Error(`Cannot update already deleted record ${record}.`);
|
||||
}
|
||||
const { allowWriteReadonly = false } = options;
|
||||
const model = record.constructor;
|
||||
@@ -1349,7 +1289,7 @@ export class ModelManager {
|
||||
}
|
||||
const field = model.__fieldMap[fieldName];
|
||||
if (!field) {
|
||||
throw new Error(`Cannot create/update record with data unrelated to a field. (record: "${record.localId}", non-field attempted update: "${fieldName}")`);
|
||||
throw new Error(`Cannot create/update record with data unrelated to a field. (record: "${record}", non-field attempted update: "${fieldName}")`);
|
||||
}
|
||||
const newVal = data[fieldName];
|
||||
if (!field.parseAndExecuteCommands(record, newVal, options)) {
|
||||
|
||||
@@ -198,6 +198,8 @@ registerModel({
|
||||
* Will be used to update the current guest's name when joining the
|
||||
* channel by clicking on the 'joinButton'.
|
||||
*/
|
||||
pendingGuestName: attr(),
|
||||
pendingGuestName: attr({
|
||||
default: '',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ Component.env = legacyEnv;
|
||||
registry.category('wowlToLegacyServiceMappers').add('make_messaging_to_legacy_env', messagingToLegacyEnv);
|
||||
|
||||
const mainComponentsRegistry = registry.category('main_components');
|
||||
mainComponentsRegistry.add('DiscussPublicViewContainer', { Component: DiscussPublicViewContainer });
|
||||
mainComponentsRegistry.add('DiscussPublicViewContainer', { Component: DiscussPublicViewContainer, props: { data } });
|
||||
// needed by the attachment viewer
|
||||
mainComponentsRegistry.add('DialogManagerContainer', { Component: DialogManagerContainer });
|
||||
mainComponentsRegistry.add('PopoverManagerContainer', { Component: PopoverManagerContainer });
|
||||
@@ -76,13 +76,4 @@ Component.env = legacyEnv;
|
||||
mapLegacyEnvToWowlEnv(Component.env, env);
|
||||
odoo.isReady = true;
|
||||
await mount(MainComponentsContainer, document.body, { env, templates, dev: env.debug });
|
||||
|
||||
const messaging = await env.services.messaging.get();
|
||||
messaging.models['Thread'].insert(messaging.models['Thread'].convertData(data.channelData));
|
||||
const discussPublicView = messaging.models['DiscussPublicView'].insert(data.discussPublicViewData);
|
||||
if (discussPublicView.shouldDisplayWelcomeViewInitially) {
|
||||
discussPublicView.switchToWelcomeView();
|
||||
} else {
|
||||
discussPublicView.switchToThreadView();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -2193,7 +2193,7 @@ QUnit.test('sidebar quick search', async function (assert) {
|
||||
.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item
|
||||
`).dataset.threadLocalId,
|
||||
messaging.models['Thread'].findFromIdentifyingData({
|
||||
id: pyEnv['mail.channel'].search([['name', '=', 'channel12']]),
|
||||
id: pyEnv['mail.channel'].search([['name', '=', 'channel12']])[0],
|
||||
model: 'mail.channel',
|
||||
}).localId,
|
||||
"should have filtered to a single channel (channel12)"
|
||||
|
||||
Reference in New Issue
Block a user