[FIX] web: image/sign field doesn't correctly update its unique

Before this commit, if the value of an image/sign field is modified by an
onchange and the record is saved manually, the old image is displayed.

Why:
ImageField's rawCacheKey, which is used to know if the image has been updated,
is not updated. So we will reuse the old image.

Solution:
rawCacheKey becomes a getter that always returns the current value of __last_updated.

How to reproduce:
- Go to a form view with a char field and an image field
- Edit the char field
- An onchange is performed and modifies the value of the image field
- The new image is displayed
- Click on the save button

Before this commit:
    The old image is displayed

After this commit:
    The new image is displayed

closes odoo/odoo#142471

Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Signed-off-by: Francois Georis (fge) <fge@odoo.com>
This commit is contained in:
FrancoisGe
2023-11-20 11:13:26 +00:00
parent 0bc8d169f5
commit daab70796f
3 changed files with 263 additions and 11 deletions
@@ -9,7 +9,7 @@ import { isBinarySize } from "@web/core/utils/binary";
import { fileTypeMagicWordMap, imageCacheKey } from "@web/views/fields/image/image_field";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { Component, onWillUpdateProps, useState } from "@odoo/owl";
import { Component, useState } from "@odoo/owl";
const placeholder = "/web/static/img/placeholder.png";
@@ -31,15 +31,10 @@ export class SignatureField extends Component {
this.state = useState({
isValid: true,
});
}
this.rawCacheKey = this.props.record.data.write_date;
onWillUpdateProps((nextProps) => {
const { record } = this.props;
const { record: nextRecord } = nextProps;
if (record.resId !== nextRecord.resId || nextRecord.mode === "readonly") {
this.rawCacheKey = nextRecord.data.write_date;
}
});
get rawCacheKey() {
return this.props.record.data.write_date;
}
get getUrl() {
@@ -54,8 +49,7 @@ export class SignatureField extends Component {
});
} else {
// Use magic-word technique for detecting image type
const magic =
fileTypeMagicWordMap[this.props.record.data[this.props.name][0]] || "png";
const magic = fileTypeMagicWordMap[this.value[0]] || "png";
return `data:image/${magic};base64,${this.props.record.data[this.props.name]}`;
}
}
@@ -141,6 +135,7 @@ export class SignatureField extends Component {
export const signatureField = {
component: SignatureField,
fieldDependencies: [{ name: "write_date", type: "datetime" }],
supportedOptions: [
{
label: _t("Prefill with"),
@@ -374,6 +374,49 @@ QUnit.module("Fields", (hooks) => {
}
);
QUnit.test("save record with image field modified by onchange", async function (assert) {
serverData.models.partner.onchanges = {
foo: (data) => {
data.document = MY_IMAGE;
},
};
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
rec.document = "3 kb";
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
// 1659692220000
const lastUpdates = ["2022-08-05 09:37:00"];
let index = 0;
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: /* xml */ `
<form>
<field name="foo"/>
<field name="document" widget="image" />
</form>`,
mockRPC(_route, { method, args }) {
if (method === "web_save") {
args[1].write_date = lastUpdates[index];
args[1].document = "3 kb";
index++;
}
},
});
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000");
await editInput(target, "[name='foo'] input", "grrr");
assert.strictEqual(
target.querySelector("div[name=document] img").dataset.src,
`data:image/png;base64,${MY_IMAGE}`
);
await clickSave(target);
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659692220000");
});
QUnit.test("ImageField: option accepted_file_extensions", async function (assert) {
await makeView({
type: "form",
@@ -695,6 +738,52 @@ QUnit.module("Fields", (hooks) => {
);
});
QUnit.test("unique in url doesn't change on onchange", async (assert) => {
serverData.models.partner.onchanges = {
foo: () => {},
};
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
rec.document = "3 kb";
rec.write_date = "2022-08-05 08:37:00";
await makeView({
resId: 1,
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="foo" />
<field name="document" widget="image" required="1" />
</form>`,
mockRPC(route, { method, args }) {
assert.step(method);
if (method === "web_save") {
// 1659692220000
args[1].write_date = "2022-08-05 09:37:00";
}
},
});
assert.verifySteps(["get_views", "web_read"]);
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000");
assert.verifySteps([]);
// same unique as before
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000");
await editInput(target, ".o_field_widget[name='foo'] input", "grrr");
assert.verifySteps(["onchange"]);
// also same unique
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000");
await clickSave(target);
assert.verifySteps(["web_save"]);
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659692220000");
});
QUnit.test("unique in url change on record change", async (assert) => {
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
rec.document = "3 kb";
@@ -1,10 +1,13 @@
/** @odoo-module **/
import {
click,
clickSave,
editInput,
getFixture,
makeDeferred,
nextTick,
patchWithCleanup,
triggerEvent,
triggerEvents,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
@@ -13,6 +16,11 @@ import { NameAndSignature } from "@web/core/signature/name_and_signature";
let serverData;
let target;
function getUnique(target) {
const src = target.dataset.src;
return new URL(src).searchParams.get("unique");
}
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
target = getFixture();
@@ -231,4 +239,164 @@ QUnit.module("Fields", (hooks) => {
assert.strictEqual(sign3.style.width, "120px");
assert.strictEqual(sign3.style.height, "40px");
});
QUnit.test(
"clicking save manually after changing signature should change the unique of the image src",
async function (assert) {
serverData.models.partner.fields.foo = { type: "char" };
serverData.models.partner.onchanges = { foo: () => {} };
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
rec.sign = "3 kb";
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
// 1659692220000, 1659695820000
const lastUpdates = ["2022-08-05 09:37:00", "2022-08-05 10:37:00"];
let index = 0;
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: /* xml */ `
<form>
<field name="foo" />
<field name="sign" widget="signature" />
</form>`,
mockRPC(route, { method, args }) {
if (route === "/web/sign/get_fonts/") {
return {};
}
if (method === "web_save") {
assert.step("web_save");
args[1].write_date = lastUpdates[index];
args[1].sign = "4 kb";
index++;
}
},
});
assert.strictEqual(
getUnique(target.querySelector(".o_field_signature img")),
"1659688620000"
);
await click(target, ".o_field_signature img", { skipVisibilityCheck: true });
assert.containsOnce(target, ".modal canvas");
let canvas = target.querySelector(".modal canvas");
canvas.setAttribute("width", "2px");
canvas.setAttribute("height", "2px");
let ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.moveTo(0, 0);
ctx.lineTo(0, 2);
ctx.stroke();
await triggerEvent(target, ".o_web_sign_signature", "change");
await click(target, ".modal-footer .btn-primary");
const MYB64 = `iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABRJREFUGFdjZGD438DAwNjACGMAACQlBAMW7JulAAAAAElFTkSuQmCC`;
assert.strictEqual(
target.querySelector("div[name=sign] img").dataset.src,
`data:image/png;base64,${MYB64}`
);
await editInput(target, ".o_field_widget[name='foo'] input", "grrr");
assert.strictEqual(
target.querySelector("div[name=sign] img").dataset.src,
`data:image/png;base64,${MYB64}`
);
await clickSave(target);
assert.verifySteps(["web_save"]);
assert.strictEqual(
getUnique(target.querySelector(".o_field_signature img")),
"1659692220000"
);
await click(target, ".o_field_signature img", { skipVisibilityCheck: true });
assert.containsOnce(target, ".modal canvas");
canvas = target.querySelector(".modal canvas");
canvas.setAttribute("width", "2px");
canvas.setAttribute("height", "2px");
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.moveTo(0, 0);
ctx.lineTo(2, 0);
ctx.stroke();
await triggerEvent(target, ".o_web_sign_signature", "change");
await click(target, ".modal-footer .btn-primary");
const MYB64_2 = `iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABVJREFUGFdjZGD438DAwMDACCJAAAAWHgGCN0++VgAAAABJRU5ErkJggg==`;
assert.notOk(MYB64 === MYB64_2);
assert.strictEqual(
target.querySelector("div[name=sign] img").dataset.src,
`data:image/png;base64,${MYB64_2}`
);
await clickSave(target);
assert.verifySteps(["web_save"]);
assert.strictEqual(
getUnique(target.querySelector(".o_field_signature img")),
"1659695820000"
);
}
);
QUnit.test("save record with signature field modified by onchange", async function (assert) {
const MYB64 = `iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABRJREFUGFdjZGD438DAwNjACGMAACQlBAMW7JulAAAAAElFTkSuQmCC`;
serverData.models.partner.fields.foo = { type: "char" };
serverData.models.partner.onchanges = {
foo: (data) => {
data.sign = MYB64;
},
};
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
rec.sign = "3 kb";
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
// 1659692220000, 1659695820000
const lastUpdates = ["2022-08-05 09:37:00", "2022-08-05 10:37:00"];
let index = 0;
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: /* xml */ `
<form>
<field name="foo" />
<field name="sign" widget="signature" />
</form>`,
mockRPC(route, { method, args }) {
if (method === "web_save") {
assert.step("web_save");
args[1].write_date = lastUpdates[index];
args[1].sign = "4 kb";
index++;
}
},
});
assert.strictEqual(
getUnique(target.querySelector(".o_field_signature img")),
"1659688620000"
);
await editInput(target, "[name='foo'] input", "grrr");
assert.strictEqual(
target.querySelector("div[name=sign] img").dataset.src,
`data:image/png;base64,${MYB64}`
);
await clickSave(target);
assert.strictEqual(
getUnique(target.querySelector(".o_field_signature img")),
"1659692220000"
);
assert.verifySteps(["web_save"]);
});
});