[IMP] mail: split related paths only once
closes odoo/odoo#102501 X-original-commit: ab802a367c1e115e6f02a3657f42fae4e691ad28 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com> Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -136,7 +136,7 @@ function addOnChanges(modelName, onChanges) {
|
||||
if (!Object.prototype.hasOwnProperty.call(onChange, 'methodName')) {
|
||||
throw new Error("at least one onChange definition lacks a methodName (the name of the method to be called on change).");
|
||||
}
|
||||
if (!Array.isArray(onChange.dependencies) || onChange.dependencies.some(d => typeof d !== 'string')) {
|
||||
if (!Array.isArray(onChange.dependencies)) {
|
||||
throw new Error("onChange dependencies must be an array of strings.");
|
||||
}
|
||||
if (typeof onChange.methodName !== 'string') {
|
||||
@@ -148,6 +148,16 @@ function addOnChanges(modelName, onChanges) {
|
||||
throw new Error(`unknown key "${key}" in onChange definition. Allowed keys: ${allowedKeys.join(", ")}.`);
|
||||
}
|
||||
}
|
||||
// paths of dependencies are splitted now to avoid having to do it
|
||||
// each time the path is followed.
|
||||
const splittedDependencies = [];
|
||||
for (const dependency of onChange.dependencies) {
|
||||
if (typeof dependency !== 'string') {
|
||||
throw new Error("onChange dependencies must be an array of strings.");
|
||||
}
|
||||
splittedDependencies.push(dependency.split('.'));
|
||||
}
|
||||
onChange.dependencies = splittedDependencies;
|
||||
}
|
||||
}, `Cannot add onChanges to model "${modelName}": `);
|
||||
registry.get(modelName).get('onChanges').push(...onChanges);
|
||||
|
||||
@@ -124,7 +124,15 @@ export class ModelField {
|
||||
* the first operation that is able to determine an order for the two
|
||||
* records.
|
||||
*/
|
||||
this.sort = sort;
|
||||
this.sort;
|
||||
/**
|
||||
* Sorted fields define sorting rules that can depend on related fields.
|
||||
* The path to a related field is defined by a period-separated sequence
|
||||
* of the relations to follow. To avoid wasting performances splitting
|
||||
* the path each time a relation must be followed, it is spllited at
|
||||
* field instantiation and stored here.
|
||||
*/
|
||||
this.sortedFieldSplittedPaths;
|
||||
/**
|
||||
* Determines whether and how elements of this field should be summed
|
||||
* into a counter field (only makes sense for relational x2many).
|
||||
@@ -150,16 +158,25 @@ export class ModelField {
|
||||
this.required = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Automatically make relateds readonly.
|
||||
*/
|
||||
if (this.related) {
|
||||
// Automatically make relateds readonly.
|
||||
this.readonly = true;
|
||||
}
|
||||
if (this.compute) {
|
||||
// Automatically make computes readonly.
|
||||
this.readonly = true;
|
||||
}
|
||||
if (sort) {
|
||||
this.sort = [];
|
||||
// Only keep unique paths, as they will be used to create listeners,
|
||||
// and we do not want to create useless duplicate listeners.
|
||||
const relatedPathSet = new Set();
|
||||
for (const [compareRule, pathAsString] of sort) {
|
||||
relatedPathSet.add(pathAsString);
|
||||
this.sort.push([compareRule, pathAsString.split('.')]);
|
||||
}
|
||||
this.sortedFieldSplittedPaths = [...relatedPathSet].map(pathAsString => pathAsString.split('.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,8 +57,7 @@ export class RelationSet {
|
||||
// access all useful values of current record (and relations) to mark them as dependencies
|
||||
this.record.modelManager.startListening(listener);
|
||||
const compareDefinition = this.field.sort;
|
||||
const relatedPathSet = new Set(compareDefinition.map(operation => operation[1])); // only keep unique paths to avoid unnecessary listeners
|
||||
for (const relatedPath of relatedPathSet) {
|
||||
for (const relatedPath of this.field.sortedFieldSplittedPaths) {
|
||||
followRelations(value, relatedPath);
|
||||
}
|
||||
this.record.modelManager.stopListening(listener);
|
||||
|
||||
@@ -526,6 +526,35 @@ export class ModelManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @throws {Error}
|
||||
*/
|
||||
_checkOnChangesOnModels() {
|
||||
for (const model of Object.values(this.models)) {
|
||||
for (const { dependencies, methodName } of registry.get(model.name).get('onChanges')) {
|
||||
for (const dependency of dependencies) {
|
||||
let currentModel = model;
|
||||
let currentField;
|
||||
for (const fieldName of dependency) {
|
||||
if (!currentModel) {
|
||||
throw new Error(`OnChange '${methodName}' defines a dependency with path '${dependency.join('.')}', but this dependency does not resolve: ${currentField} is not a relational field, therefore there is no relation to follow.`);
|
||||
}
|
||||
currentField = currentModel.__fieldMap.get(fieldName);
|
||||
if (!currentField) {
|
||||
throw new Error(`OnChange '${methodName}' defines a dependency with path '${dependency.join('.')}', but this path does not resolve: ${currentModel}/${fieldName} does not exist.`);
|
||||
}
|
||||
if (currentField.to) {
|
||||
currentModel = this.models[currentField.to];
|
||||
} else {
|
||||
currentModel = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @throws {Error} in case some fields are not correct.
|
||||
@@ -580,6 +609,20 @@ export class ModelManager {
|
||||
if (![model.name, 'Record'].includes(inverseField.to)) {
|
||||
throw new Error(`${field} has its inverse ${inverseField} referring to an invalid model (${inverseField.to}).`);
|
||||
}
|
||||
if (field.sort) {
|
||||
for (const path of field.sortedFieldSplittedPaths) {
|
||||
let currentField = field;
|
||||
for (const fieldName of path) {
|
||||
if (!currentField.to) {
|
||||
throw new Error(`Field ${field} defines a sort with path '${path.join('.')}', but this path does not resolve: ${currentField} is not a relational field, therefore there is no relation to follow.`);
|
||||
}
|
||||
if (!this.models[currentField.to].__fieldMap.has(fieldName)) {
|
||||
throw new Error(`Field ${field} defines a sort with path '${path.join('.')}', but this path does not resolve: ${this.models[currentField.to]}/${fieldName} does not exist.`);
|
||||
}
|
||||
currentField = this.models[currentField.to].__fieldMap.get(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const identifyingField of model.__identifyingFieldNames) {
|
||||
const field = model.__fieldMap.get(identifyingField);
|
||||
@@ -921,6 +964,7 @@ export class ModelManager {
|
||||
* should have matching reversed relation.
|
||||
*/
|
||||
this._checkProcessedFieldsOnModels();
|
||||
this._checkOnChangesOnModels();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,15 +6,12 @@
|
||||
* is undefined.
|
||||
*
|
||||
* @param {Record} record
|
||||
* @param {string} relatedPath field names, dot separated to follow relations
|
||||
* @param {string[]} relatedPath Array of field names.
|
||||
* @returns {any}
|
||||
*/
|
||||
export function followRelations(record, relatedPath) {
|
||||
let target = record;
|
||||
for (const field of relatedPath.split('.')) {
|
||||
if (!target.constructor.__fieldMap.has(field)) {
|
||||
throw Error(`field(${field}) does not exist on ${target}`);
|
||||
}
|
||||
for (const field of relatedPath) {
|
||||
target = target[field];
|
||||
if (!target) {
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user