From be4b211ffe0249a4b40f2f290e3ef62ff72535f1 Mon Sep 17 00:00:00 2001 From: Lucas Perais Date: Fri, 5 Aug 2022 13:08:31 +0000 Subject: [PATCH] [FIX] web: ask to save new record before translating Fixup for 4caa9596bcc6bfe3494de451f8c988c23ea6efcf, where a rebase went bad. Have a translatable field in a form view. Create a new record with that form view. Click on the button to open translations for that field. Before this commit, the record was not before opening the translation dialog, hence changing the translations could not work properly. After this commit, this feature is back, and we ask to save a new record before opening the translation dialog. closes odoo/odoo#97824 Signed-off-by: Lucas Perais (lpe) --- addons/web/static/src/legacy/legacy_fields.js | 2 + .../src/views/fields/char/char_field.xml | 5 +- .../src/views/fields/text/text_field.xml | 5 +- .../src/views/fields/translation_button.js | 84 +++++++++++---- .../tests/views/form/form_view_tests.js | 100 ++++++++++++++++++ 5 files changed, 169 insertions(+), 27 deletions(-) diff --git a/addons/web/static/src/legacy/legacy_fields.js b/addons/web/static/src/legacy/legacy_fields.js index e67dfb2ff64..20744407f97 100644 --- a/addons/web/static/src/legacy/legacy_fields.js +++ b/addons/web/static/src/legacy/legacy_fields.js @@ -8,6 +8,7 @@ import { ComponentAdapter } from "web.OwlCompatibility"; import viewUtils from "web.viewUtils"; import { useWowlService } from "@web/legacy/utils"; import { RPCError } from "@web/core/network/rpc_service"; +import { useTranslationDialog } from "@web/views/fields/translation_button"; const { Component, useEffect, xml } = owl; const fieldRegistry = registry.category("fields"); @@ -22,6 +23,7 @@ const legacyFieldTemplate = xml` class FieldAdapter extends ComponentAdapter { setup() { super.setup(); + this.translationDialog = useTranslationDialog(); this.wowlEnv = this.env; this.env = Component.env; this.orm = useWowlService("orm"); diff --git a/addons/web/static/src/views/fields/char/char_field.xml b/addons/web/static/src/views/fields/char/char_field.xml index 00132c8a29d..8496c353ff6 100644 --- a/addons/web/static/src/views/fields/char/char_field.xml +++ b/addons/web/static/src/views/fields/char/char_field.xml @@ -19,11 +19,8 @@ diff --git a/addons/web/static/src/views/fields/text/text_field.xml b/addons/web/static/src/views/fields/text/text_field.xml index 3d9d0a517ec..fbdf81d7166 100644 --- a/addons/web/static/src/views/fields/text/text_field.xml +++ b/addons/web/static/src/views/fields/text/text_field.xml @@ -18,11 +18,8 @@ diff --git a/addons/web/static/src/views/fields/translation_button.js b/addons/web/static/src/views/fields/translation_button.js index a9faf663eaa..769ecffaea0 100644 --- a/addons/web/static/src/views/fields/translation_button.js +++ b/addons/web/static/src/views/fields/translation_button.js @@ -3,14 +3,75 @@ import { localization } from "@web/core/l10n/localization"; import { useService } from "@web/core/utils/hooks"; import { TranslationDialog } from "./translation_dialog"; +import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; -const { Component } = owl; +const { Component, useEnv } = owl; + +/** + * Prepares a function that will open the dialog that allows to edit translation + * values for a given field. + * + * It is mainly a factorization of the feature that is also used + * in legacy_fields. We expect it to be fully implemented in TranslationButton + * when legacy code is removed. + */ +export function useTranslationDialog() { + const dialog = useService("dialog"); + const rpc = useService("rpc"); + const env = useEnv(); + + async function openTranslationDialog({ record, fieldName, updateField }) { + if (!record.resId) { + let _continue = true; + await new Promise((resolve) => { + dialog.add(ConfirmationDialog, { + async confirm() { + _continue = await record.save({ stayInEdition: true }); + resolve(); + }, + cancel() { + _continue = false; + resolve(); + }, + body: env._t( + "You need to save this new record before editing the translation. Do you want to proceed?" + ), + title: env._t("Warning"), + }); + }); + if (!_continue) { + return; + } + } + const { resModel, resId, context } = record; + const result = await rpc("/web/dataset/call_button", { + model: "ir.translation", + method: "translate_fields", + args: [resModel, resId, fieldName], + kwargs: { context }, + }); + + dialog.add(TranslationDialog, { + domain: result.domain, + searchName: result.context.search_default_name, + fieldName: fieldName, + userLanguageValue: record.data[fieldName] || "", + isComingFromTranslationAlert: false, + isText: result.context.translation_type === "text", + showSource: result.context.translation_show_src, + updateField, + }); + } + + return openTranslationDialog; +} export class TranslationButton extends Component { setup() { this.user = useService("user"); this.rpc = useService("rpc"); this.dialog = useService("dialog"); + this.translationDialog = useTranslationDialog(); } get isMultiLang() { @@ -20,24 +81,9 @@ export class TranslationButton extends Component { return this.user.lang.split("_")[0].toUpperCase(); } - async onClick() { - const result = await this.rpc("/web/dataset/call_button", { - model: "ir.translation", - method: "translate_fields", - args: [this.props.resModel, this.props.resId, this.props.fieldName], - kwargs: { context: this.props.context }, - }); - - this.dialog.add(TranslationDialog, { - domain: result.domain, - searchName: result.context.search_default_name, - fieldName: this.props.fieldName, - userLanguageValue: this.props.value || "", - isComingFromTranslationAlert: false, - isText: result.context.translation_type === "text", - showSource: result.context.translation_show_src, - updateField: this.props.updateField, - }); + onClick() { + const { fieldName, record, updateField } = this.props; + this.translationDialog({ fieldName, record, updateField }); } } TranslationButton.template = "web.TranslationButton"; diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index 9fefb804c36..d463e92bb97 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -20,6 +20,7 @@ import { triggerEvent, } from "@web/../tests/helpers/utils"; import { FieldOne2Many } from "web.relational_fields"; +import { FieldChar as LegacyFieldChar } from "web.basic_fields"; import * as legacyFieldRegistry from "web.field_registry"; import { toggleActionMenu, toggleGroupByMenu, toggleMenuItem } from "@web/../tests/search/helpers"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; @@ -36,6 +37,8 @@ import BasicModel from "web.BasicModel"; import { localization } from "@web/core/l10n/localization"; import { SIZES } from "@web/core/ui/ui_service"; +import * as legacyCore from "web.core"; + const fieldRegistry = registry.category("fields"); const serviceRegistry = registry.category("services"); const widgetRegistry = registry.category("view_widgets"); @@ -8030,6 +8033,103 @@ QUnit.module("Views", (hooks) => { assert.strictEqual(target.querySelector(".modal-title").textContent, "Translate: foo"); }); + QUnit.test("ask to save new record before opening translate dialog", async function (assert) { + serverData.models.partner.fields.foo.translate = true; + + patchWithCleanup(localization, { + multiLang: true, + }); + + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + `, + mockRPC(route, args) { + assert.step(args.method); + if (route === "/web/dataset/call_button") { + return { context: {}, domain: [] }; + } + if (args.method === "get_installed") { + return [ + ["CUST", "custom lang"], + ["CUST2", "second custom"], + ]; + } + }, + }); + + assert.verifySteps(["get_views", "onchange"]); + assert.containsOnce(target, ".o_form_editable"); + await click(target, ".o_field_translate.btn-link"); + assert.containsOnce(target, ".modal"); + assert.strictEqual(target.querySelector(".modal-title").textContent, "Warning"); + + await click(target.querySelectorAll(".modal-footer button")[1]); // cancel + assert.verifySteps([]); + + await click(target, ".o_field_translate.btn-link"); + await click(target.querySelectorAll(".modal-footer button")[0]); // save + assert.verifySteps(["create", "read", "translate_fields", "get_installed", "search_read"]); + assert.containsOnce(target, ".modal"); + assert.strictEqual(target.querySelector(".modal-title").textContent, "Translate: foo"); + }); + + QUnit.test("legacy field support translation dialog", async (assert) => { + serverData.models.partner.fields.foo.translate = true; + + patchWithCleanup(legacyCore._t.database, { + multi_lang: true, + parameters: { + code: "CUST", + }, + }); + + const myChar = LegacyFieldChar.extend({ + async _render() { + // DIY because the compatibility layer doesn't support + // legacy widget having multiple nodes in their this.$el. + await this._super(...arguments); + const div = document.createElement("div"); + const $div = $(div).append(this.$el); + this.setElement($div); + }, + }); + + legacyFieldRegistry.add("legacy_char", myChar); + await makeView({ + type: "form", + resModel: "partner", + serverData, + resId: 1, + arch: ` +
+ + `, + mockRPC(route, args) { + assert.step(args.method); + if (route === "/web/dataset/call_button") { + return { context: {}, domain: [] }; + } + if (args.method === "get_installed") { + return [ + ["CUST", "custom lang"], + ["CUST2", "second custom"], + ]; + } + }, + }); + assert.verifySteps(["get_views", "read"]); + await clickEdit(target); + await click(target, ".o_field_legacy_char .o_field_translate.btn-link"); + assert.verifySteps(["translate_fields", "get_installed", "search_read"]); + assert.containsOnce(target, ".modal"); + assert.strictEqual(target.querySelector(".modal-title").textContent, "Translate: foo"); + }); + QUnit.test("translation alerts are preserved on pager change", async function (assert) { serverData.models.partner.fields.foo.translate = true;