[Python] How to override JavaScript base addon file to custom addon in odoo 16

Discussão em 'Python' iniciado por Stack, Agosto 9, 2025.

  1. Stack

    Stack Membro Participativo

    This is the js file in "documents" enterprise base addon I just want to add documetns_type_id in here export const inspectorFields = [] through custom addon.

    /** @odoo-module **/

    import { device } from "web.config";
    import { str_to_datetime } from "web.time";
    import { session } from "@web/session";
    import { KeepLast } from "@web/core/utils/concurrency";
    import { intersection } from "@web/core/utils/arrays";
    import { AutoComplete } from "@web/core/autocomplete/autocomplete";
    import { x2ManyCommands } from "@web/core/orm_service";
    import { useBus, useService } from "@web/core/utils/hooks";
    import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
    import { FileUploader } from "@web/views/fields/file_handler";
    import { ChatterContainer } from "@mail/components/chatter_container/chatter_container";
    import { DocumentsInspectorField } from "./documents_inspector_field";
    import { download } from "@web/core/network/download";
    import { onNewPdfThumbnail } from "../helper/documents_pdf_thumbnail_service";
    import { useTriggerRule } from "@documents/views/hooks";

    const { Component, markup, useEffect, useState, useRef, onPatched, onWillUpdateProps, onWillStart } = owl;

    async function toggleArchive(model, resModel, resIds, doArchive) {
    const method = doArchive ? "action_archive" : "action_unarchive";
    const action = await model.orm.call(resModel, method, [resIds]);
    if (action && Object.keys(action).length !== 0) {
    model.action.doAction(action);
    }
    }

    export const inspectorFields = [
    "attachment_id",
    "active",
    "activity_ids",
    "available_rule_ids",
    "checksum",
    "display_name",
    "folder_id",
    "thumbnail_status",
    "lock_uid",
    "message_attachment_count",
    "message_follower_ids",
    "message_ids",
    "mimetype",
    "name",
    "owner_id",
    "partner_id",
    "previous_attachment_ids",
    "res_id",
    "res_model",
    "res_model_name",
    "res_name",
    "tag_ids",
    "type",
    "url",
    "file_size",
    ];

    export class DocumentsInspector extends Component {
    setup() {
    this.orm = useService("orm");
    this.action = useService("action");
    this.dialogService = useService("dialog");
    this.notificationService = useService("notification");
    this.documentsReplaceInput = useRef("replaceFileInput");
    this.chatterContainer = useRef("chatterContainer");
    this.keepLast = new KeepLast();
    this.previewLockCount = 0;
    this.str_to_datetime = str_to_datetime;
    const { triggerRule } = useTriggerRule();
    this._triggerRule = triggerRule;
    const { bus: fileUploadBus } = useService("file_upload");
    useBus(fileUploadBus, "FILE_UPLOAD_LOADED", (ev) => {
    let documentId = ev.detail.upload.data.get("document_id");
    if (documentId && this.resIds.includes(Number.parseInt(documentId))) {
    this.state.previousAttachmentDirty = true;
    }
    });

    // Avoid generating new urls if they were generated within this component's lifetime
    this.generatedUrls = {};
    this.state = useState({
    previousAttachmentData : null,
    previousAttachmentDirty : true,
    showChatter : this.isMobile,
    });
    const updateLockedState = (props) => {
    this.isLocked =
    (props.selection.find((rec) => rec.data.lock_uid && rec.data.lock_uid[0] !== session.uid) && true) ||
    false;
    const folderIds = props.selection.map((rec) => rec.data.folder_id[0]);
    const folders = this.env.searchModel.getFolders().filter((folder) => folderIds.includes(folder.id));
    this.isEditDisabled = !!folders.find((folder) => !folder.has_write_access);
    };
    onWillStart(() => {
    updateLockedState(this.props);
    this.updateAttachmentHistory(null);
    });
    onWillUpdateProps((nextProps) => {
    this.generatedUrl = false;
    updateLockedState(nextProps);
    this.updateAttachmentHistory(nextProps);
    });

    // Chatter
    const chatterCloseHandler = () => {
    this.state.showChatter = this.isMobile;
    };
    const chatterReloadHandler = async () => {
    const record = this.props.selection[0];
    if (!record) {
    return;
    }
    await record.load();
    await record.model.notify();
    };
    useEffect(
    (el) => {
    if (!el) {
    return;
    }
    el.addEventListener("o-close-chatter", chatterCloseHandler);
    el.addEventListener("reload", chatterReloadHandler);
    return () => {
    el.removeEventListener("o-close-chatter", chatterCloseHandler);
    el.removeEventListener("reload", chatterReloadHandler);
    };
    },
    () => [this.chatterContainer.el && this.chatterContainer.el.querySelector(".o_Chatter")]
    );

    // Pdf thumbnails
    if (this.props.withFilePreview) {
    this.pdfService = useService("documents_pdf_thumbnail");
    onWillStart(async () => {
    this.pdfService.enqueueRecords(this.props.selection);
    })
    onWillUpdateProps(async (nextProps) => {
    this.pdfService.enqueueRecords(nextProps.selection);
    })
    onNewPdfThumbnail(({ detail }) => {
    if (this.props.selection.find(rec => rec.resId === detail.record.resId)) {
    this.render(true);
    }
    });
    }

    //Mobile specific
    if (!this.env.isSmall) {
    return;
    }
    this.inspectorMobileRef = useRef("inspectorMobile");
    this.shouldOpenInspector = false;
    onWillUpdateProps((nextProps) => {
    // Only open the inspector if there is only one selected element and
    // it was not previously selected.
    this.shouldOpenInspector = nextProps.selection.length === 1;
    });
    onPatched(() => {
    if (!this.inspectorMobileRef.el) {
    return;
    }
    if (this.shouldOpenInspector) {
    this.inspectorMobileRef.el.setAttribute("open", "");
    }
    });
    }

    get resIds() {
    return this.props.selection.map((rec) => rec.resId);
    }

    get isMobile() {
    return this.env.isSmall;
    }

    updateAttachmentHistory(nextProps) {
    const props = nextProps || this.props;
    const record = props.selection[0];
    if (props.selection.length !== 1) {
    this.state.showChatter = this.isMobile;
    }
    if (!record || props.selection.length !== 1 || !record.data.previous_attachment_ids.count) {
    this.keepLast.add(Promise.resolve());
    this.state.previousAttachmentData = null;
    return;
    }
    const previousRecord = this.props.selection.length === 1 && this.props.selection[0];
    if (
    nextProps &&
    previousRecord &&
    previousRecord.resId === record.resId &&
    !this.state.previousAttachmentDirty
    ) {
    return;
    }
    this.keepLast.add(
    this.orm
    .searchRead(
    "ir.attachment",
    [["id", "in", record.data.previous_attachment_ids.records.map((rec) => rec.resId)]],
    ["name", "create_date", "create_uid"],
    {
    order: "create_date desc",
    }
    )
    .then((result) => {
    this.state.previousAttachmentData = result;
    this.state.previousAttachmentDirty = false;
    })
    );
    }

    getCurrentFolder() {
    return this.env.searchModel.getSelectedFolder();
    }

    getFolderDescription() {
    return markup(this.getCurrentFolder().description);
    }

    /**
    * Returns an object with additional data for our record
    */
    getRecordAdditionalData(record) {
    const additionalData = {
    isGif: new RegExp("image.*(gif)").test(record.data.mimetype),
    isImage: new RegExp("image.*(jpeg|jpg|png)").test(record.data.mimetype),
    isYoutubeVideo: false,
    youtubeToken: undefined,
    };
    if (record.data.url && record.data.url.length) {
    const youtubeUrlMatch = record.data.url.match(
    "youtu(?:.be|be.com)/(?:.*v(?:/|=)|(?:.*/)?)([a-zA-Z0-9-_]{11})"
    );
    if (youtubeUrlMatch && youtubeUrlMatch.length > 1) {
    additionalData.isYoutubeVideo = true;
    additionalData.youtubeToken = youtubeUrlMatch[1];
    }
    }
    return additionalData;
    }

    /**
    * Returns the classes to give to the file preview
    */
    getPreviewClasses(record, additionalData) {
    const nbPreviews = this.props.selection.length;
    const classes = ["o_document_preview"];
    if (record.data.type === "empty") {
    classes.push("o_document_request_preview");
    }
    if (nbPreviews === 1) {
    classes.push("o_documents_single_preview");
    }
    if (additionalData.isImage || additionalData.isYoutubeVideo || (record.isPdf() && record.hasThumbnail())) {
    classes.push("o_documents_preview_image");
    } else {
    classes.push("o_documents_preview_mimetype");
    }
    if (additionalData.isYoutubeVideo || additionalData.isGif) {
    classes.push("o_non_image_preview");
    }
    return classes.join(" ");
    }

    isPdfOnly() {
    return this.props.selection.every((record) => record.isPdf());
    }

    download(records) {
    if (records.length === 1) {
    download({
    data: {},
    url: `/documents/content/${records[0].resId}`,
    });
    } else {
    download({
    data: {
    file_ids: records.map(rec => rec.resId),
    zip_name: `documents-${moment().format("YYYY-MM-DD")}.zip`,
    },
    url: "/document/zip",
    });
    }
    }

    onDownload() {
    if (!this.props.selection.length) {
    return;
    }
    this.download(this.props.selection);
    }

    // Override during tests.
    _writeInClipboard(text) {
    navigator.clipboard.writeText(text);
    }

    async onShare() {
    const resIds = this.resIds;
    if (!this.generatedUrls[resIds]) {
    this.generatedUrls[resIds] = await this.orm.call(
    "documents.share",
    "action_get_share_url",
    [{
    document_ids: [x2ManyCommands.replaceWith(this.resIds)],
    folder_id: this.env.searchModel.getSelectedFolderId(),
    type: "ids",
    }],
    );
    }
    this._writeInClipboard(this.generatedUrls[resIds]);
    this.notificationService.add(
    this.env._t("The share url has been copied to your clipboard."),
    {
    type: "success",
    },
    );
    }

    async onReplace(ev) {
    if (!ev.target.files.length) {
    return;
    }
    const record = this.props.selection[0];
    await this.env.documentsView.bus.trigger("documents-upload-files", {
    files: ev.target.files,
    folderId: this.env.searchModel.getSelectedFolderId() || (record.data.folder_id && record.data.folder_id[0]),
    recordId: this.props.selection[0].resId,
    tagIds: this.env.searchModel.getSelectedTagIds(),
    });
    ev.target.value = "";
    }

    async onLock() {
    await this.doLockAction(async () => {
    const record = this.props.selection[0];
    await this.orm.call("documents.document", "toggle_lock", this.resIds);
    await record.load();
    await record.model.notify();
    });
    }

    async _toggleArchive(state) {
    const record = this.props.selection[0];
    await toggleArchive(record.model, record.resModel, this.resIds, state);
    await record.model.load();
    await record.model.notify();
    }

    async onArchive() {
    await this._toggleArchive(true);
    }

    async onUnarchive() {
    await this._toggleArchive(false);
    }

    async onDelete() {
    await this.props.selection[0].model.root.deleteRecords(this.props.selection);
    }

    getFieldProps(fieldName, additionalProps) {
    const props = {
    record: this.props.selection[0],
    name: fieldName,
    selection: this.props.selection,
    inspectorReadonly: this.isLocked || this.isEditDisabled,
    lockAction: this.doLockAction.bind(this),
    };
    if (additionalProps) {
    Object.assign(props, additionalProps);
    }
    return props;
    }

    _getCommonM2M(field) {
    const selection = this.props.selection;
    let commonData = selection[0].data[field].records.map((rec) => rec.resId);
    for (let idx = 1; idx < selection.length; idx++) {
    if (commonData.length === 0) {
    break;
    }
    commonData = intersection(
    commonData,
    selection[idx].data[field].records.map((rec) => rec.resId)
    );
    }
    return commonData.map((id) => selection[0].data[field].records.find((data) => data.resId === id));
    }

    getCommonTags() {
    const searchModelTags = this.env.searchModel.getTags().reduce((res, tag) => {
    res[tag.id] = tag;
    return res;
    }, {});
    return this._getCommonM2M("tag_ids")
    .filter((rec) => searchModelTags[rec.resId])
    .map((rec) => {
    const tag = searchModelTags[rec.resId];
    return {
    id: rec.resId,
    name: tag.display_name,
    group_name: tag.group_name,
    };
    });
    }

    getCommonRules() {
    let commonRules = this._getCommonM2M("available_rule_ids");
    if (this.props.selection.length > 1) {
    commonRules = commonRules.filter((rule) => !rule.data.limited_to_single_record);
    }
    return commonRules;
    }

    getAdditionalTags(commonTags) {
    return this.env.searchModel.getTags().filter((tag) => {
    return !commonTags.find((cTag) => cTag.id === tag.id);
    });
    }

    async removeTag(tag) {
    const record = this.props.selection[0];
    record.model.root._multiSave(record, {
    tag_ids: [x2ManyCommands.forget(tag.id)],
    });
    }

    async addTag(tag, { input }) {
    const record = this.props.selection[0];
    record.model.root._multiSave(record, {
    tag_ids: [x2ManyCommands.linkTo(tag.value)],
    });
    input.focus();
    }

    getTagAutocompleteProps(additionalTags) {
    return {
    value: "",
    onSelect: this.addTag.bind(this),
    sources: [
    {
    options: (request) => {
    request = request.toLowerCase();
    return additionalTags
    .filter((tag) =>
    (tag.group_name + " > " + tag.display_name).toLowerCase().includes(request)
    )
    .map((tag) => {
    return {
    id: tag.id,
    value: tag.id,
    label: tag.group_name + " > " + tag.display_name,
    };
    });
    },
    },
    ],
    placeholder: this.env._t(" + Add a tag"),
    };
    }

    async onClickResModel() {
    const record = this.props.selection[0];
    const action = await this.orm.call(record.data.res_model, "get_formview_action", [[record.data.res_id]], {
    context: record.model.user.context,
    });
    await this.action.doAction(action);
    }

    async triggerRule(rule) {
    await this._triggerRule(
    this.props.selection.map(rec => rec.resId),
    rule.resId,
    );
    }

    async onDeletePreviousAttachment(attachmentId) {
    if (this.deleting) {
    return;
    }
    await this.doLockAction(async () => {
    this.deleting = true;
    await this.orm.unlink("ir.attachment", [attachmentId]);
    const record = this.props.selection[0];
    const model = this.props.selection[0].model;
    await record.load();
    this.state.previousAttachmentDirty = true;
    await model.notify();
    this.deleting = false;
    });
    }

    async onDownloadPreviousAttachment(attachmentId) {
    window.location = `/web/content/${attachmentId}?download=true`;
    }

    async onRestorePreviousAttachment(attachmentId) {
    const record = this.props.selection[0];
    await this.doLockAction(async () => {
    await this.orm.write("documents.document", [record.resId], {
    attachment_id: attachmentId,
    });
    await record.load();
    this.state.previousAttachmentDirty = true;
    await record.model.notify();
    });
    }

    openPreview(mainDocument = false, isPdfSplit = false) {
    if ((isPdfSplit && !this.isPdfOnly()) || this.previewLockCount) {
    return;
    }
    const documents = this.props.selection.filter(rec => rec.isViewable());
    this.env.documentsView.bus.trigger("documents-open-preview", {
    documents: documents,
    mainDocument: mainDocument || documents[0],
    isPdfSplit,
    rules: this.getCommonRules(),
    hasPdfSplit: !this.isLocked && !this.isEditDisabled,
    });
    }

    async onEditModel() {
    const record = this.props.selection[0];
    let defaultResourceRef = false;
    if (record.data.res_model && record.data.res_id) {
    defaultResourceRef = `${record.data.res_model},${record.data.res_id}`;
    }
    const models = await this.orm.searchRead("ir.model", [["model", "=", record.data.res_model]], ["id"], {
    limit: 1,
    });
    this.action.doAction(
    {
    name: this.env._t("Edit the linked record"),
    type: "ir.actions.act_window",
    res_model: "documents.link_to_record_wizard",
    views: [[false, "form"]],
    target: "new",
    context: {
    default_document_ids: [record.resId],
    default_resource_ref: defaultResourceRef,
    default_is_readonly_model: true,
    default_model_id: models[0].id,
    },
    },
    {
    onClose: async () => {
    await record.model.load();
    record.model.notify();
    },
    }
    );
    }

    onDeleteModel() {
    const recordId = this.props.selection[0].resId;
    const model = this.props.selection[0].model;
    this.dialogService.add(ConfirmationDialog, {
    body: this.env._t("Do you really want to unlink this record?"),
    confirm: async () => {
    await this.orm.call("documents.workflow.rule", "unlink_record", [[recordId]]);
    await model.load();
    model.notify();
    },
    });
    }

    async doLockAction(func) {
    this.previewLockCount++;
    await func();
    this.previewLockCount--;
    }
    }

    DocumentsInspector.components = {
    AutoComplete,
    ChatterContainer,
    DocumentsInspectorField,
    FileUploader,
    };

    if (device.isMobile) {
    DocumentsInspector.template = "documents.DocumentsInspectorMobile";
    } else {
    DocumentsInspector.template = "documents.DocumentsInspector";
    }


    I have created a view "assets.xml" in my custom addons
    ateam_adaptations/views/assets.xml

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
    <template id="assets_backend" name="documents assets backend" inherit_id="web.assets_backend">
    <xpath expr="." position="inside">
    <script type="text/javascript"
    src="/ateam_adaptations/static/src/views/inspector/documents_inspector.js"
    />
    </xpath>
    </template>
    </odoo>


    and this is how i am overriding the js file through custom addons
    ateam_adaptations/static/src/views/inspector/documents_inspector.js:

    odoo.define('ateam_adaptations.documents_inspector', function (require) {
    "use strict";

    console.log("documents_inspector_extension");

    var DocumentsInspector = require('@documents/static/src/views/inspector/documents_inspector.js');
    console.log("DocumentsInspector", DocumentsInspector);

    // Extend the inspectorFields array to include "document_type_id"
    DocumentsInspector.inspectorFields.push("document_type_id");

    return DocumentsInspector;
    });

    Continue reading...

Compartilhe esta Página