odoo18/addons/web/static/src/views/debug_items.js

395 lines
13 KiB
JavaScript

import { _t } from "@web/core/l10n/translation";
import { Dialog } from "@web/core/dialog/dialog";
import { evaluateBooleanExpr } from "@web/core/py_js/py";
import { editModelDebug } from "@web/core/debug/debug_utils";
import { formatDateTime, deserializeDateTime } from "@web/core/l10n/dates";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { formatMany2one } from "@web/views/fields/formatters";
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
import { Component, onWillStart, useState, xml } from "@odoo/owl";
import { serializeDate, serializeDateTime } from "../core/l10n/dates";
const debugRegistry = registry.category("debug");
//------------------------------------------------------------------------------
// Get view
//------------------------------------------------------------------------------
class GetViewDialog extends Component {
static template = "web.DebugMenu.GetViewDialog";
static components = { Dialog };
static props = {
arch: { type: String },
close: { type: Function },
};
}
export function getView({ component, env }) {
return {
type: "item",
description: _t("Computed Arch"),
callback: () => {
env.services.dialog.add(GetViewDialog, { arch: component.env.config.rawArch });
},
sequence: 270,
section: "ui",
};
}
debugRegistry.category("view").add("getView", getView);
//------------------------------------------------------------------------------
// Edit View
//------------------------------------------------------------------------------
export function editView({ accessRights, component, env }) {
if (!accessRights.canEditView) {
return null;
}
const { viewId, viewType: type } = component.env.config;
if (!type) {
return;
}
const displayName = type[0].toUpperCase() + type.slice(1);
const description = _t("View: %(displayName)s", { displayName });
return {
type: "item",
description,
callback: () => {
editModelDebug(env, description, "ir.ui.view", viewId);
},
sequence: 240,
section: "ui",
};
}
debugRegistry.category("view").add("editView", editView);
//------------------------------------------------------------------------------
// Edit SearchView
//------------------------------------------------------------------------------
export function editSearchView({ accessRights, component, env }) {
if (!accessRights.canEditView) {
return null;
}
const { searchViewId } = component.componentProps.info;
if (searchViewId === undefined) {
return null;
}
const description = _t("SearchView");
return {
type: "item",
description,
callback: () => {
editModelDebug(env, description, "ir.ui.view", searchViewId);
},
sequence: 230,
section: "ui",
};
}
debugRegistry.category("view").add("editSearchView", editSearchView);
// -----------------------------------------------------------------------------
// View Metadata
// -----------------------------------------------------------------------------
class GetMetadataDialog extends Component {
static template = "web.DebugMenu.GetMetadataDialog";
static components = { Dialog };
static props = {
resModel: String,
resId: Number,
close: Function,
};
setup() {
this.orm = useService("orm");
this.dialogService = useService("dialog");
this.title = _t("View Metadata");
this.state = useState({});
onWillStart(() => this.loadMetadata());
}
onClickCreateXmlid() {
const context = Object.assign({}, this.context, {
default_module: "__custom__",
default_res_id: this.state.id,
default_model: this.props.resModel,
});
this.dialogService.add(FormViewDialog, {
context,
onRecordSaved: () => this.loadMetadata(),
resModel: "ir.model.data",
});
}
async toggleNoupdate() {
await this.env.services.orm.call("ir.model.data", "toggle_noupdate", [
this.props.resModel,
this.state.id,
]);
await this.loadMetadata();
}
async loadMetadata() {
const args = [[this.props.resId]];
const result = await this.orm.call(this.props.resModel, "get_metadata", args);
const metadata = result[0];
this.state.id = metadata.id;
this.state.xmlid = metadata.xmlid;
this.state.xmlids = metadata.xmlids;
this.state.noupdate = metadata.noupdate;
this.state.creator = formatMany2one(metadata.create_uid);
this.state.lastModifiedBy = formatMany2one(metadata.write_uid);
this.state.createDate = formatDateTime(deserializeDateTime(metadata.create_date));
this.state.writeDate = formatDateTime(deserializeDateTime(metadata.write_date));
}
}
export function viewMetadata({ component, env }) {
const resId = component.model.root.resId;
if (!resId) {
return null; // No record
}
return {
type: "item",
description: _t("Metadata"),
callback: () => {
env.services.dialog.add(GetMetadataDialog, {
resModel: component.props.resModel,
resId,
});
},
sequence: 110,
section: "record",
};
}
debugRegistry.category("form").add("viewMetadata", viewMetadata);
// -----------------------------------------------------------------------------
// View Raw Record Data
// -----------------------------------------------------------------------------
class RawRecordDialog extends Component {
static template = xml`
<Dialog title="props.title">
<pre t-esc="content"/>
</Dialog>
`;
static components = { Dialog };
static props = {
record: { type: Object },
title: { type: String },
close: { type: Function },
};
get content() {
const record = this.props.record;
return JSON.stringify(record, Object.keys(record).sort(), 2);
}
}
export function viewRawRecord({ component, env }) {
const { resId, resModel } = component.model.config;
if (!resId) {
return null;
}
const description = _t("Data");
return {
type: "item",
description,
callback: async () => {
const records = await component.model.orm.read(resModel, [resId]);
env.services.dialog.add(RawRecordDialog, {
title: _t("Data: %(model)s(%(id)s)", { model: resModel, id: resId }),
record: records[0],
});
},
sequence: 120,
section: "record",
};
}
debugRegistry.category("form").add("viewRawRecord", viewRawRecord);
// -----------------------------------------------------------------------------
// Set Defaults
// -----------------------------------------------------------------------------
class SetDefaultDialog extends Component {
static template = "web.DebugMenu.SetDefaultDialog";
static components = { Dialog };
static props = {
record: { type: Object },
fieldNodes: { type: Object },
close: { type: Function },
};
setup() {
this.orm = useService("orm");
this.state = {
fieldToSet: "",
condition: "",
scope: "self",
};
this.fields = this.props.record.fields;
this.activeFields = this.props.record.activeFields;
this.fieldNamesInView = this.props.record.fieldNames;
this.fieldNamesBlackList = ["message_attachment_count"];
this.fieldsValues = this.props.record.data;
this.modifierDatas = {};
this.defaultFields = this.getDefaultFields();
this.conditions = this.getConditions();
}
getDefaultFields() {
return this.fieldNamesInView
.filter((fieldName) => !this.fieldNamesBlackList.includes(fieldName))
.map((fieldName) => {
const fieldInfo = this.fields[fieldName];
const valueDisplayed = this.display(fieldInfo, this.fieldsValues[fieldName]);
const value = valueDisplayed[0];
const displayed = valueDisplayed[1];
const evalContext = this.props.record.evalContextWithVirtualIds;
// ignore fields which are empty, invisible, readonly, o2m or m2m
if (
!value ||
evaluateBooleanExpr(this.activeFields[fieldName].invisible, evalContext) ||
evaluateBooleanExpr(this.activeFields[fieldName].readonly, evalContext) ||
fieldInfo.type === "one2many" ||
fieldInfo.type === "many2many" ||
fieldInfo.type === "binary" ||
Object.entries(this.props.fieldNodes)
.filter(([key, value]) => value.name === fieldName)
.some(([key, value]) => value.options.isPassword)
) {
return false;
}
return {
name: fieldName,
string: fieldInfo.string,
value,
displayed,
};
})
.filter((val) => val)
.sort((field) => field.string);
}
getConditions() {
return this.fieldNamesInView
.filter((fieldName) => {
const fieldInfo = this.fields[fieldName];
return fieldInfo.change_default;
})
.map((fieldName) => {
const fieldInfo = this.fields[fieldName];
const valueDisplayed = this.display(fieldInfo, this.fieldsValues[fieldName]);
const value = valueDisplayed[0];
const displayed = valueDisplayed[1];
return {
name: fieldName,
string: fieldInfo.string,
value: value,
displayed: displayed,
};
});
}
display(fieldInfo, value) {
let displayed = value;
if (value && fieldInfo.type === "many2one") {
displayed = value[1];
value = value[0];
} else if (value && fieldInfo.type === "selection") {
displayed = fieldInfo.selection.find((option) => {
return option[0] === value;
})[1];
}
return [value, displayed];
}
async saveDefault() {
if (!this.state.fieldToSet) {
return;
}
let fieldToSet = this.defaultFields.find((field) => {
return field.name === this.state.fieldToSet;
}).value;
if (fieldToSet.constructor.name.toLowerCase() === "date") {
fieldToSet = serializeDate(fieldToSet);
} else if (fieldToSet.constructor.name.toLowerCase() === "datetime") {
fieldToSet = serializeDateTime(fieldToSet);
}
await this.orm.call("ir.default", "set", [
this.props.record.resModel,
this.state.fieldToSet,
fieldToSet,
this.state.scope === "self",
true,
this.state.condition || false,
]);
this.props.close();
}
}
export function setDefaults({ component, env }) {
return {
type: "item",
description: _t("Set Default Values"),
callback: () => {
env.services.dialog.add(SetDefaultDialog, {
record: component.model.root,
fieldNodes: component.props.archInfo.fieldNodes,
});
},
sequence: 150,
section: "record",
};
}
debugRegistry.category("form").add("setDefaults", setDefaults);
//------------------------------------------------------------------------------
// Manage Attachments
//------------------------------------------------------------------------------
export function manageAttachments({ component, env }) {
const resId = component.model.root.resId;
if (!resId) {
return null; // No record
}
const description = _t("Attachments");
return {
type: "item",
description,
callback: () => {
env.services.action.doAction({
res_model: "ir.attachment",
name: description,
views: [
[false, "list"],
[false, "form"],
],
type: "ir.actions.act_window",
domain: [
["res_model", "=", component.props.resModel],
["res_id", "=", resId],
],
context: {
default_res_model: component.props.resModel,
default_res_id: resId,
skip_res_field_check: true,
},
});
},
sequence: 140,
section: "record",
};
}
debugRegistry.category("form").add("manageAttachments", manageAttachments);