[FIX] web: fix binary field in settings form

Steps:
- Install sales app.
- Go to settings of sales.
- See header field there is no download button.

Issue:
- Binary field should have download button and
user should be able to download file with out
issue.

Cause:
- Since Settings is abstact model so it does not
contains id field in it and download button visibility
depends on id field.

Fix:
- Extend binary field for setting view to get proper
file from related field where actual file is stored.

task-3620555

closes odoo/odoo#160070

Signed-off-by: Jorge Pinna Puissant (jpp) <jpp@odoo.com>
This commit is contained in:
kcv-odoo
2024-04-10 06:46:42 +00:00
parent bea603f865
commit 821ee43327
5 changed files with 179 additions and 14 deletions
@@ -47,19 +47,23 @@ export class BinaryField extends Component {
return this.props.record.update(changes);
}
getDownloadData() {
return {
model: this.props.record.resModel,
id: this.props.record.resId,
field: this.props.name,
filename_field: this.fileName,
filename: this.fileName || "",
download: true,
data: isBinarySize(this.props.record.data[this.props.name])
? null
: this.props.record.data[this.props.name],
};
}
async onFileDownload() {
await download({
data: {
model: this.props.record.resModel,
id: this.props.record.resId,
field: this.props.name,
filename_field: this.fileName,
filename: this.fileName || "",
download: true,
data: isBinarySize(this.props.record.data[this.props.name])
? null
: this.props.record.data[this.props.name],
},
data: this.getDownloadData(),
url: "/web/content",
});
}
@@ -9,7 +9,7 @@
acceptedFileExtensions="props.acceptedFileExtensions"
onUploaded.bind="update"
>
<t t-if="props.record.resId and !props.record.dirty">
<t name="download" t-if="props.record.resId and !props.record.dirty">
<button
class="btn btn-link btn-sm lh-1 fa fa-download o_download_file_button"
data-tooltip="Download"
@@ -0,0 +1,26 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { BinaryField, binaryField } from "@web/views/fields/binary/binary_field";
export class SettingsBinaryField extends BinaryField {
static template = "web.SettingsBinaryField";
getDownloadData() {
const related = this.props.record.fields[this.props.name].related;
return {
...super.getDownloadData(),
model: this.props.record.fields[related.split(".")[0]].relation,
field: related.split(".")[1] ?? related.split(".")[0],
id: this.props.record.data[related.split(".")[0]][0],
}
}
}
const settingsBinaryField = {
...binaryField,
component: SettingsBinaryField,
};
registry.category("fields").add("base_settings.binary", settingsBinaryField);
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="web.SettingsBinaryField" t-inherit="web.BinaryField" t-inherit-mode="primary">
<xpath expr="//t[@name='download']" position="attributes">
<attribute name="t-if"/>
</xpath>
</t>
</templates>
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
import { makeFakeLocalizationService, makeMockXHR } from "@web/../tests/helpers/mock_services";
import {
click,
editInput,
@@ -12,6 +12,7 @@ import {
} from "@web/../tests/helpers/utils";
import { editSearch } from "@web/../tests/search/helpers";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { browser } from "@web/core/browser/browser";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { errorService } from "@web/core/errors/error_service";
import { registry } from "@web/core/registry";
@@ -41,6 +42,25 @@ QUnit.module("SettingsFormView", (hooks) => {
fields: {
foo: { string: "Foo", type: "boolean" },
bar: { string: "Bar", type: "boolean" },
task_id: {
string: "many2one field",
type: "many2one",
relation: "task",
default: 100,
},
file: {
string: "related binary field",
type: "binary",
relation: "task",
related: "task_id.file",
default: "coucou==\n",
},
file_name: {
string: "related file name",
type: "char",
related: "task_id.file_name",
default: "coucou.txt",
},
tasks: { string: "one2many field", type: "one2many", relation: "task" },
baz: {
string: "Baz",
@@ -54,7 +74,17 @@ QUnit.module("SettingsFormView", (hooks) => {
},
},
task: {
fields: {},
fields: {
file: { string: "Binary", type: "binary" },
file_name: { string: "File Name", type: "char"}
},
records: [
{
id: 100,
file: "coucou==\n",
file_name: "coucou.txt",
},
],
},
},
};
@@ -1868,4 +1898,99 @@ QUnit.module("SettingsFormView", (hooks) => {
await click(target.querySelector("button[name='2']"));
assert.verifySteps(["/web/action/run"]);
});
QUnit.test("BinaryField is correctly rendered in Settings form view", async function (assert) {
async function send(data) {
assert.ok(data instanceof FormData);
assert.strictEqual(
data.get("field"),
"file",
"we should download the field document"
);
assert.strictEqual(
data.get("data"),
"coucou==\n",
"we should download the correct data"
);
this.status = 200;
this.response = new Blob([data.get("data")], { type: "text/plain" });
}
const MockXHR = makeMockXHR("", send);
patchWithCleanup(browser, { XMLHttpRequest: MockXHR });
await makeView({
type: "form",
resModel: "res.config.settings",
serverData,
arch: `
<form string="Settings" class="oe_form_configuration o_base_settings" js_class="base_settings">
<app string="Sale" name="sale">
<block title="Title of group Bar">
<setting>
<field name="task_id" invisible="1"/>
<field name="file" filename="file_name"/>
<field name="file_name"/>
</setting>
</block>
</app>
</form>`,
});
assert.containsOnce(
target,
'.o_field_widget[name="file"] .fa-download',
"Download button should be display in settings form view"
);
assert.strictEqual(
target.querySelector('.o_field_widget[name="file"].o_field_binary .o_input').value,
"coucou.txt",
"the binary field should display the file name in the input"
);
assert.containsOnce(
target,
".o_field_binary .o_clear_file_button",
"there shoud be a button to clear the file"
);
assert.strictEqual(
target.querySelector(".o_field_char input").value,
"coucou.txt",
"the filename field should have the file name as value"
);
// Testing the download button in the field
// We must avoid the browser to download the file effectively
const prom = makeDeferred();
const downloadOnClick = (ev) => {
const target = ev.target;
if (target.tagName === "A" && "download" in target.attributes) {
ev.preventDefault();
document.removeEventListener("click", downloadOnClick);
prom.resolve();
}
};
document.addEventListener("click", downloadOnClick);
registerCleanup(() => document.removeEventListener("click", downloadOnClick));
await click(target.querySelector(".fa-download"));
await prom;
await click(target.querySelector(".o_field_binary .o_clear_file_button"));
assert.isNotVisible(
target.querySelector(".o_field_binary input"),
"the input should be hidden"
);
assert.containsOnce(
target,
".o_field_binary .o_select_file_button",
"there should be a button to upload the file"
);
assert.strictEqual(
target.querySelector(".o_field_char input").value,
"",
"the filename field should be empty since we removed the file"
);
});
});