From 33453faf231e6239fd98a41a3257478f6bbcd495 Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Wed, 6 Sep 2023 09:44:23 +0200 Subject: [PATCH] [FIX] web: support properties in calendar Before this commit, in a calendar view with a properties field, a crash will be display when you click on an event. closes odoo/odoo#134476 Related: odoo/enterprise#46990 Signed-off-by: Aaron Bohy (aab) --- .../src/model/relational_model/record.js | 5 +- .../relational_model/relational_model.js | 46 ++---------- .../src/model/relational_model/utils.js | 33 +++++++++ .../calendar_common_popover.js | 2 +- .../src/views/calendar/calendar_model.js | 9 +-- .../views/calendar/calendar_view_tests.js | 71 +++++++++++++++++-- .../static/tests/views/calendar/helpers.js | 2 +- 7 files changed, 114 insertions(+), 54 deletions(-) diff --git a/addons/web/static/src/model/relational_model/record.js b/addons/web/static/src/model/relational_model/record.js index ef28529cb9e..52afbb48d70 100644 --- a/addons/web/static/src/model/relational_model/record.js +++ b/addons/web/static/src/model/relational_model/record.js @@ -3,15 +3,16 @@ import { markRaw, markup, toRaw } from "@odoo/owl"; import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { Domain } from "@web/core/domain"; -import { evaluateBooleanExpr } from "@web/core/py_js/py"; import { serializeDate, serializeDateTime } from "@web/core/l10n/dates"; import { _t } from "@web/core/l10n/translation"; import { x2ManyCommands } from "@web/core/orm_service"; +import { evaluateBooleanExpr } from "@web/core/py_js/py"; import { pick } from "@web/core/utils/objects"; import { escape } from "@web/core/utils/strings"; import { DataPoint } from "./datapoint"; import { FetchRecordError, + applyProperties, getBasicEvalContext, getFieldContext, getFieldsSpec, @@ -889,7 +890,7 @@ export class Record extends DataPoint { if (!records.length) { throw new FetchRecordError(records.map((r) => r.id)); } - this.model._applyProperties(records, this.config); + applyProperties(records, this.config.activeFields, this.config.fields); if (this.resId) { this.model._updateSimilarRecords(this, records[0]); } diff --git a/addons/web/static/src/model/relational_model/relational_model.js b/addons/web/static/src/model/relational_model/relational_model.js index 5dd7f8c95bc..ed9468725ae 100644 --- a/addons/web/static/src/model/relational_model/relational_model.js +++ b/addons/web/static/src/model/relational_model/relational_model.js @@ -16,12 +16,12 @@ import { Group } from "./group"; import { Record } from "./record"; import { StaticList } from "./static_list"; import { - getFieldsSpec, - createPropertyActiveField, - extractInfoFromGroupData, - isRelational, FetchRecordError, + applyProperties, + extractInfoFromGroupData, getBasicEvalContext, + getFieldsSpec, + isRelational, } from "./utils"; /** @@ -186,40 +186,6 @@ export class RelationalModel extends Model { // Protected // ------------------------------------------------------------------------- - _applyProperties(records, config) { - for (const record of records) { - for (const fieldName in record) { - const field = config.fields[fieldName]; - if (fieldName !== "id" && field.type === "properties" && record[fieldName]) { - const parent = record[field.definition_record]; - const relatedPropertyField = { - fieldName, - }; - if (parent) { - relatedPropertyField.id = parent.id; - relatedPropertyField.displayName = parent.display_name; - } - for (const property of record[fieldName]) { - const propertyFieldName = `${fieldName}.${property.name}`; - if (!config.fields[propertyFieldName]) { - config.fields[propertyFieldName] = { - ...property, - name: propertyFieldName, - relatedPropertyField, - propertyName: property.name, - relation: property.comodel, - }; - } - if (!config.activeFields[propertyFieldName]) { - config.activeFields[propertyFieldName] = - createPropertyActiveField(property); - } - } - } - } - } - } - _askChanges() { const proms = []; this.bus.trigger("NEED_LOCAL_CHANGES", { proms }); @@ -533,7 +499,7 @@ export class RelationalModel extends Model { throw new FetchRecordError(resIds); } - this._applyProperties(records, config); + applyProperties(records, config.activeFields, config.fields); return records; } else { return resIds.map((resId) => { @@ -561,7 +527,7 @@ export class RelationalModel extends Model { }; const response = await this.orm.webSearchRead(config.resModel, config.domain, kwargs); - this._applyProperties(response.records, config); + applyProperties(response.records, config.activeFields, config.fields); return response; } diff --git a/addons/web/static/src/model/relational_model/utils.js b/addons/web/static/src/model/relational_model/utils.js index 8b21df9a4fc..6e847445d8a 100644 --- a/addons/web/static/src/model/relational_model/utils.js +++ b/addons/web/static/src/model/relational_model/utils.js @@ -76,6 +76,39 @@ export function addFieldDependencies(activeFields, fields, fieldDependencies = [ } } +export function applyProperties(records, activeFields, fields) { + for (const record of records) { + for (const fieldName in record) { + const field = fields[fieldName]; + if (fieldName !== "id" && field.type === "properties" && record[fieldName]) { + const parent = record[field.definition_record]; + const relatedPropertyField = { + fieldName, + }; + if (parent) { + relatedPropertyField.id = parent.id; + relatedPropertyField.displayName = parent.display_name; + } + for (const property of record[fieldName]) { + const propertyFieldName = `${fieldName}.${property.name}`; + if (!fields[propertyFieldName]) { + fields[propertyFieldName] = { + ...property, + name: propertyFieldName, + relatedPropertyField, + propertyName: property.name, + relation: property.comodel, + }; + } + if (!activeFields[propertyFieldName]) { + activeFields[propertyFieldName] = createPropertyActiveField(property); + } + } + } + } + } +} + function completeActiveField(activeField, extra) { if (extra.related) { for (const fieldName in extra.related.activeFields) { diff --git a/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.js b/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.js index d0df770ae12..026b7e583b8 100644 --- a/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.js +++ b/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.js @@ -21,7 +21,7 @@ export class CalendarCommonPopover extends Component { } get activeFields() { - return this.props.model.popoverFields; + return this.props.model.activeFields; } get isEventEditable() { return true; diff --git a/addons/web/static/src/views/calendar/calendar_model.js b/addons/web/static/src/views/calendar/calendar_model.js index 1209a355bb1..1b6729ede3f 100644 --- a/addons/web/static/src/views/calendar/calendar_model.js +++ b/addons/web/static/src/views/calendar/calendar_model.js @@ -11,7 +11,7 @@ import { _t } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; import { KeepLast } from "@web/core/utils/concurrency"; import { Model } from "@web/model/model"; -import { extractFieldsFromArchInfo } from "@web/model/relational_model/utils"; +import { applyProperties, extractFieldsFromArchInfo } from "@web/model/relational_model/utils"; export class CalendarModel extends Model { setup(params, services) { @@ -27,7 +27,7 @@ export class CalendarModel extends Model { const { activeFields, fields } = extractFieldsFromArchInfo({ fieldNodes }, params.fields); this.meta = { ...params, - popoverFields: activeFields, + activeFields, fields, firstDayOfWeek: localization.weekStart, formViewId: params.formViewId || formViewIdFromConfig, @@ -118,8 +118,8 @@ export class CalendarModel extends Model { get popoverFieldNodes() { return this.meta.popoverFieldNodes; } - get popoverFields() { - return this.meta.popoverFields; + get activeFields() { + return this.meta.activeFields; } get rangeEnd() { return this.data.range.end; @@ -485,6 +485,7 @@ export class CalendarModel extends Model { */ async loadRecords(data) { const rawRecords = await this.fetchRecords(data); + applyProperties(rawRecords, this.meta.activeFields, this.meta.fields); const records = {}; for (const rawRecord of rawRecords) { records[rawRecord.id] = this.normalizeRecord(rawRecord); diff --git a/addons/web/static/tests/views/calendar/calendar_view_tests.js b/addons/web/static/tests/views/calendar/calendar_view_tests.js index 44c6d643dd8..cda1289121d 100644 --- a/addons/web/static/tests/views/calendar/calendar_view_tests.js +++ b/addons/web/static/tests/views/calendar/calendar_view_tests.js @@ -960,12 +960,7 @@ QUnit.module("Views", ({ beforeEach }) => { assert.containsN(target, ".fc-event-container .fc-event", 10, "should display 10 events"); // move to next month await navigate(target, "next"); - assert.containsN( - target, - ".fc-event-container .fc-event", - 0, - "should display 0 events" - ); + assert.containsN(target, ".fc-event-container .fc-event", 0, "should display 0 events"); await pickDate(target, "2017-01-01"); assert.containsN( target, @@ -4814,4 +4809,68 @@ QUnit.module("Views", ({ beforeEach }) => { "The text in the title should be Test Display" ); }); + + QUnit.test(`calendar render properties`, async (assert) => { + serverData.models.event.fields.properties = { + string: "Properties", + type: "properties", + definition_record: "event_type_id", + definition_record_field: "definitions", + }; + serverData.models.event_type.fields.definitions = { + string: "Definitions", + type: "properties_definition", + }; + serverData.models.event_type.records[0].definitions = [ + { name: "event_prop_1", string: "My Char", type: "char" }, + { name: "event_prop_2", string: "My Selection", type: "selection" }, + ]; + + serverData.models.event.records[0].event_type_id = 1; + serverData.models.event.records[0].properties = [ + { + name: "property_1", + string: "My Char", + type: "char", + value: "hello", + }, + { + name: "property_2", + string: "My Selection", + type: "selection", + selection: [ + ["a", "A"], + ["b", "B"], + ["c", "C"], + ], + value: "b", + default: "c", + }, + ]; + + await makeView({ + type: "calendar", + resModel: "event", + serverData, + arch: ` + + + + + `, + async mockRPC(route, args) { + if (args.method === "check_access_rights") { + return true; + } + }, + }); + + await clickEvent(target, 1); + assert.deepEqual( + [...target.querySelectorAll(".o_popover [name='properties'] .o_property_field")].map( + (el) => el.textContent + ), + ["My Charhello", "My SelectionB"] + ); + }); }); diff --git a/addons/web/static/tests/views/calendar/helpers.js b/addons/web/static/tests/views/calendar/helpers.js index 942774268ee..1442338ddab 100644 --- a/addons/web/static/tests/views/calendar/helpers.js +++ b/addons/web/static/tests/views/calendar/helpers.js @@ -234,7 +234,7 @@ function makeFakeModelState() { popoverFieldNodes: { name: Field.parseFieldNode(fakeFieldNode, fakeModels, "event", "calendar"), }, - popoverFields: { + activeFields: { name: { context: "{}", invisible: false,