Hello,
I'm currently working on version 18 of Odoo. I've only been using the tool for a few weeks. I discovered the ‘Binary’ field which allows you to import files. Once a file has been loaded, several icons appear. One of them is a bin for deleting the file. I'd like to have a dialogue box asking for confirmation that I'm sure I want to delete the file. I've managed to do this in the source code by modifying it. But to be cleaner and more modular, it would be better to have a custom module that would allow me to do this. Except that no matter how hard I try, nothing works. I don't necessarily get errors. When I do have errors, I manage to correct them. In addition to this, I use another module called ‘Binary Field Attachment Preview’ from Pysquad Informatics LLP which allows me to view the files. I don't know whether there might be some sort of conflict between the modules.
Thanks for your help!
Here's the source code modified as I want it, with confirmation:
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { isBinarySize, toBase64Length } from "@web/core/utils/binary";
import { download } from "@web/core/network/download";
import { standardFieldProps } from "../standard_field_props";
import { FileUploader } from "../file_handler";
import { _t } from "@web/core/l10n/translation";
import { Component, xml } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";
export const MAX_FILENAME_SIZE_BYTES = 0xFF; // filenames do not exceed 255 bytes on Linux/Windows/MacOS
export class BinaryField extends Component {
static template = "web.BinaryField";
static components = {
FileUploader,
};
static props = {
...standardFieldProps,
acceptedFileExtensions: { type: String, optional: true },
fileNameField: { type: String, optional: true },
};
static defaultProps = {
acceptedFileExtensions: "*",
};
setup() {
this.notification = useService("notification");
this.dialog = useService("dialog");
}
get fileName() {
return (
this.props.record.data[this.props.fileNameField] ||
this.props.record.data[this.props.name] ||
""
).slice(0, toBase64Length(MAX_FILENAME_SIZE_BYTES));
}
update({ data, name }) {
const { fileNameField, record } = this.props;
const changes = { [this.props.name]: data || false };
if (fileNameField in record.fields && record.data[fileNameField] !== name) {
changes[fileNameField] = name || '';
}
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],
};
}
onClickDelete() {
this.dialog.add(ConfirmDeleteDialog, {
onConfirmCallback: () => this.update({}),
});
}
async onFileDownload() {
await download({
data: this.getDownloadData(),
url: "/web/content",
});
}
}
export class ListBinaryField extends BinaryField {
static template = "web.ListBinaryField";
}
export const binaryField = {
component: BinaryField,
displayName: _t("File"),
supportedOptions: [
{
label: _t("Accepted file extensions"),
name: "accepted_file_extensions",
type: "string",
},
],
supportedTypes: ["binary"],
extractProps: ({ attrs, options }) => ({
acceptedFileExtensions: options.accepted_file_extensions,
fileNameField: attrs.filename,
}),
};
export const listBinaryField = {
...binaryField,
component: ListBinaryField,
};
class ConfirmDeleteDialog extends Component {
static template = xml`
<Dialog title="'Confirmation de suppression'">
<div>
Êtes-vous sûr de vouloir supprimer ce fichier ?
</div>
<t t-set-slot="footer">
<button class="btn btn-danger" t-on-click="onConfirm">Supprimer</button>
<button class="btn btn-secondary" t-on-click="props.close">Annuler</button>
</t>
</Dialog>
`;
static components = { Dialog };
static props = ['close', 'onConfirmCallback'];
onConfirm() {
this.props.onConfirmCallback(); // Appel du callback
this.props.close(); // Fermeture manuelle du dialog
}
}
registry.category("fields").add("binary", binaryField);
registry.category("fields").add("list.binary", listBinaryField);
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.BinaryField">
<t t-if="!props.readonly">
<t t-if="props.record.data[props.name]">
<div class="w-100 d-inline-flex gap-1">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
onUploaded.bind="update"
>
<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"
aria-label="Download"
t-on-click="onFileDownload"
>
</button>
</t>
<t t-set-slot="toggler">
<input type="text" class="o_input" t-att-value="fileName" readonly="readonly" />
<button
class="btn btn-link btn-sm lh-1 fa fa-pencil o_select_file_button"
data-tooltip="Edit"
aria-label="Edit"
>
</button>
</t>
<button
class="btn btn-link btn-sm lh-1 fa fa-trash o_clear_file_button"
data-tooltip="Clear"
aria-label="Clear"
t-on-click="onClickDelete"
/>
<!-- </button> -->
</FileUploader>
</div>
</t>
<t t-else="">
<label class="o_select_file_button btn btn-primary">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
onUploaded.bind="update"
>
<t t-set-slot="toggler">
Upload your file
</t>
</FileUploader>
</label>
</t>
</t>
<t t-elif="props.record.resId and props.record.data[props.name]">
<a class="o_form_uri" href="#" t-on-click.prevent="onFileDownload">
<span class="fa fa-download me-2" />
<t t-if="fileName" t-esc="fileName" />
</a>
</t>
</t>
<t t-name="web.ListBinaryField" t-inherit="web.BinaryField">
<xpath expr="//label[hasclass('o_select_file_button')]" position="replace">
<label class="o_select_file_button btn btn-sm btn-link p-0">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
onUploaded.bind="update"
>
<t t-set-slot="toggler">
<i class="fa fa-upload fa-fw"/> Upload your file
</t>
</FileUploader>
</label>
</xpath>
</t>
</templates>