6

odoo wizard界面显示带复选框列表及勾选数据获取 - 授客

 2 years ago
source link: https://www.cnblogs.com/shouke/p/17135887.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

odoo wizard界面显示带复选框列表及勾选数据获取

Odoo 14.0-20221212 (Community Edition)

如下图(非实际项目界面截图,仅用于介绍本文主题),打开记录详情页(form视图),点击某个按钮(图中的"选取ffers"按钮),弹出一个向导(wizard)界面,并将详情页中内联tree视图("Offers" Tab页)的列表记录展示到向导界面,且要支持复选框,用于选取目标记录,然执行目标操作。

1569452-20230219225500178-1964806774.png
1569452-20230219225526547-1415556427.png

详情页所属模型EstateProperty

class EstateProperty(models.Model):
    _name = 'estate.property'
    _description = 'estate property table'
    # ... 略
    offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")

    def action_do_something(self, args):
        # do something 
        print(args)

OffersTab页Tree列表所属模型EstatePropertyOffer

class EstatePropertyOffer(models.Model):
    _name = 'estate.property.offer'
    _description = 'estate property offer'
    
    # ... 略
    property_id = fields.Many2one('estate.property', required=True)

代码组织结构

为了更好的介绍本文主题,下文给出了项目文件大致组织结构(为了让大家看得更清楚,仅保留关键文件)

odoo14          
├─custom
│  ├─estate
│  │  │  __init__.py
│  │  │  __manifest__.py
│  │  │          
│  │  ├─models
│  │  │  estate_property.py
│  │  │  estate_property_offer.py
│  │  │  __init__.py
│  │  │          
│  │  ├─security
│  │  │      ir.model.access.csv
│  │  │      
│  │  ├─static
│  │  │  │      
│  │  │  └─src
│  │  │      │          
│  │  │      └─js
│  │  │              list_renderer.js
│  │  │              
│  │  ├─views
│  │  │      estate_property_offer_views.xml
│  │  │      estate_property_views.xml
│  │  │      webclient_templates.xml     
│  │  │          
│  │  └─wizards
│  │        demo_wizard.py
│  │        demo_wizard_views.xml
│  │        __init__.py
│  │          
├─odoo
│  │  api.py
│  │  exceptions.py
│  │  ...略
│  │  __init__.py
│  │  
│  ├─addons
│  │  │  __init__.py
│  ...略
...略       

wizard简介

wizard(向导)通过动态表单描述与用户(或对话框)的交互会话。向导只是一个继承TransientModel而非model的模型。TransientModel类扩展Model并重用其所有现有机制,具有以下特殊性:

  • wizard记录不是永久的;它们在一定时间后自动从数据库中删除。这就是为什么它们被称为瞬态(transient)。

  • wizard可以通过关系字段(many2onemany2many)引用常规记录或wizard记录,但常规记录不能通过many2one字段引用wizard记录

注意:为了更清楚的表达本文主题,代码文件中部分代码已略去

wizard实现

odoo14\custom\estate\wizards\demo_wizard.py
实现版本1
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import logging
from odoo import models,fields,api
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)

class DemoWizard(models.TransientModel):
    _name = 'demo.wizard'
    _description = 'demo wizard'

    property_id = fields.Many2one('estate.property', string='property')
    offer_ids = fields.One2many(related='property_id.offer_ids')

    def action_confirm(self):
        '''选中记录后,点击确认按钮,执行的操作'''

        #### 根据需要对获取的数据做相应处理
        # ... 获取数据,代码略(假设获取的数据存放在 data 变量中)
     
        record_ids = []
        for id, value_dict in data.items():
            record_ids.append(value_dict.get('data', {}).get('id'))
        if not record_ids: 
            raise UserError('请选择记录')

        self.property_id.action_do_something(record_ids)                
        return True      
    

    @api.model
    def action_select_records_via_checkbox(self, args):
        '''通过wizard窗口界面复选框选取记录时触发的操作
        @params: args 为字典
        '''
        # ...存储收到的数据(假设仅存储data部分的数据),代码略
        
        return True # 注意,执行成功则需要配合前端实现,返回True

    @api.model
    def default_get(self, fields_list):
        '''获取wizard 窗口界面默认值,包括记录列表 #因为使用了@api.model修饰符,self为空记录集,所以不能通过self.fieldName = value 的方式赋值'''

        res = super(DemoWizard, self).default_get(fields_list)
        record_ids = self.env.context.get('active_ids') # 获取当前记录ID列表(当前记录详情页所属记录ID列表) # self.env.context.get('active_id') # 获取当前记录ID

        property = self.env['estate.property'].browse(record_ids)
        res['property_id'] = property.id

        offer_ids = property.offer_ids.mapped('id')
        res['offer_ids'] = [(6, 0, offer_ids)]
        return res
  • 注意,不能使用类属性来接收数据,因为类属性供所有对象共享,会相互影响,数据错乱。

  • action_select_records_via_checkbox函数接收的args参数,其类型为字典,形如以下,其中f412cde5-1e5b-408c-8fc0-1841b9f9e4de为UUID,供web端使用,用于区分不同页面操作的数据,'estate.property.offer_3'为供web端使用的记录ID,'data'键值代表记录的数据,其id键值代表记录在数据库中的主键id,context键值代表记录的上下文。arg数据格式为:

    {'uuid':{'recordID1':{'data': {}, 'context':{}}, 'recordID2': {'data': {}, 'context':{}}}}
    
    {'f412cde5-1e5b-408c-8fc0-1841b9f9e4de': {'estate.property.offer_3': {'data': {'price': 30000, 'partner_id': {'context': {}, 'count': 0, 'data': {'display_name': 'Azure Interior, Brandon Freeman', 'id': 26}, 'domain': [], 'fields': {'display_name': {'type': 'char'}, 'id': {'type': 'integer'}}, 'id': 'res.partner_4', 'limit': 1, 'model': 'res.partner', 'offset': -1, 'ref': 26, 'res_ids': [], 'specialData': {}, 'type': 'record', 'res_id': 26}, 'validity': 7, 'date_deadline': '2022-12-30', 'status': 'Accepted', 'id': 21}, 'context': {'lang': 'en_US', 'tz': 'Europe/Brussels', 'uid': 2, 'allowed_company_ids': [1], 'params': {'action': 85, 'cids': 1, 'id': 41, 'menu_id': 70, 'model': 'estate.property', 'view_type': 'form'}, 'active_model': 'estate.property', 'active_id': 41, 'active_ids': [41], 'property_pk_id': 41}}}}
    
实现版本2
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import uuid
import logging
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError, MissingError

_logger = logging.getLogger(__name__)

class DemoWizard(models.TransientModel):
    _name = 'demo.wizard'
    _description = 'demo wizard'

    property_id = fields.Many2one('estate.property', string='property')
    property_pk_id = fields.Integer(related='property_id.id') # 用于action_confirm中获取property
    offer_ids = fields.One2many(related='property_id.offer_ids')

    @api.model
    def action_confirm(self, data:dict): 
        '''选中记录后,点击确认按钮,执行的操作'''

        #### 根据需要对获取的数据做相应处理
        record_ids = []
        for id, value_dict in data.items():
            record_ids.append(value_dict.get('data', {}).get('id'))
        if not record_ids:
            raise UserError('请选择记录')
            
        property_pk_id = None
        for id, value_dict in data.items():
            property_pk_id = value_dict.get('context', {}).get('property_pk_id')
            break

        if not property_pk_id:
            raise ValidationError('do something fail')
            
        property = self.env['estate.property'].browse([property_pk_id]) # 注意,,所以,这里不能再通过self.property_id获取了
        if property.exists():
            property.action_do_something(record_ids)
        else:
            raise MissingError('do something fail:当前property记录(id=%s)不存在' % property_pk_id)
        return True

    
    @api.model
    def default_get(self, fields_list):
        '''获取wizard 窗口界面默认值,包括记录列表'''

        res = super(DemoWizard, self).default_get(fields_list)
        record_ids = self.env.context.get('active_ids')
        
        property = self.env['estate.property'].browse(record_ids)
        res['property_id'] = property.id
        res['property_pk_id'] = property.id

        offer_ids = property.offer_ids.mapped('id')
        res['offer_ids'] = [(6, 0, offer_ids)]
        return res
odoo14\custom\estate\wizards\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from . import demo_wizard
odoo14\custom\estate\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from . import models
from . import wizards
odoo14\custom\estate\wizards\demo_wizard_views.xml
实现版本1

对应demo_wizard.py实现版本1

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <data>
        <record id="demo_wizard_view_form" model="ir.ui.view">
            <field name="name">demo.wizard.form</field>
            <field name="model">demo.wizard</field>
            <field name="arch" type="xml">
                <form>
                    <field name="offer_ids">
                        <tree hasCheckBoxes="true" modelName="demo.wizard" modelMethod="action_select_records_via_checkbox" jsMethodOnModelMethodDone="enableActionConfirmButton()" jsMethodOnToggleCheckbox="disableActionConfirmButton()">
                            <field name="price" string="Price"/>
                            <field name="partner_id" string="partner ID"/>
                            <field name="validity" string="Validity(days)"/>
                            <field name="date_deadline" string="Deadline"/>
                            <button name="action_accept_offer" string=""  type="object" icon="fa-check" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
                            <button name="action_refuse_offer" string=""  type="object" icon="fa-times" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
                            <field name="status" string="Status"/>
                        </tree>
                    </field>
                    <footer>
                        <button name="action_confirm" type="object" string="确认(do something you want)" class="oe_highlight"/>
                        <button string="取消" class="oe_link" special="cancel"/>
                    </footer>
                </form>
            </field>
        </record>
        
        <record id="action_demo_wizard" model="ir.actions.act_window">
            <field name="name">选取offers</field>
            <field name="res_model">demo.wizard</field>
            <field name="type">ir.actions.act_window</field>
            <field name="view_mode">form</field>
            <field name="target">new</field>            
        </record>
    </data>
</odoo>
<tree hasCheckBoxes="true" modelName="demo.wizard" modelMethod="action_select_records_via_checkbox" jsMethodOnModelMethodDone="enableActionConfirmButton()" jsMethodOnToggleCheckbox="disableActionConfirmButton()">
  • hasCheckBoxes 设置"true",则显示复选框。以下属性皆在hasCheckBoxes"true"的情况下起作用。
  • modelName 点击列表复选框时,需要访问的模型名称,需要配合modelMethod方法使用,缺一不可。可选
  • modelMethod 点击列表复选框时,需要调用的模型方法,通过该方法收集列表勾选记录的数据。可选。
  • jsMethodOnModelMethodDone 定义modelMethod方法执行完成后,需要调用的javascript方法(注意,包括参数,如果没有参数则写成(),形如 jsMethod())。可选。
  • jsMethodOnToggleCheckbox 定义点击列表复选框时需要调用的javascript方法,比modelMethod优先执行(注意,包括参数,如果没有参数则写成(),形如 jsMethod())。可选。

以上参数同下文saveSelectionsToSessionStorage 参数可同时共存

如果需要将action绑定到指定模型指定视图的Action,可以在ir.actions.act_window定义中添加binding_model_idbinding_view_types字段,如下:

        <record id="action_demo_wizard" model="ir.actions.act_window">
            <field name="name">选取offers</field>
            <field name="res_model">demo.wizard</field>
            <field name="type">ir.actions.act_window</field>
            <field name="view_mode">form</field>
            <field name="target">new</field>            
            <!-- 添加Action菜单 -->
            <field name="binding_model_id" ref="estate.model_estate_property"/>
            <field name="binding_view_types">form</field>
        </record>
1569452-20230219225557773-1306660383.png

参考连接:https://www.odoo.com/documentation/14.0/zh_CN/developer/reference/addons/actions.html

实现版本2

对应demo_wizard.py实现版本2

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <data>
        <record id="demo_wizard_view_form" model="ir.ui.view">
            <field name="name">demo.wizard.form</field>
            <field name="model">demo.wizard</field>
            <field name="arch" type="xml">
                <form>
                    <field name="property_pk_id" invisible="1"/>
                    <field name="offer_ids" context="{'property_pk_id': property_pk_id}">
                        <tree string="List" hasCheckBoxes="true" saveSelectionsToSessionStorage="true">
                            <field name="price" string="Price"/>
                            <field name="partner_id" string="partner ID"/>
                            <field name="validity" string="Validity(days)"/>
                            <field name="date_deadline" string="Deadline"/>
                            <button name="action_accept_offer" string=""  type="object" icon="fa-check" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
                            <button name="action_refuse_offer" string=""  type="object" icon="fa-times" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
                            <field name="status" string="Status"/>
                        </tree>
                    </field>
                    <footer>
                        <button name="action_confirm" onclick="do_confirm_action('demo.wizard','action_confirm')"  string="确认(do something you want)" class="oe_highlight"/>
                        <button string="取消" class="oe_link" special="cancel"/>
                    </footer>
                </form>
            </field>
        </record>
        
        <record id="action_demo_wizard" model="ir.actions.act_window">
            <field name="name">选取offers</field>
            <field name="res_model">demo.wizard</field>
            <field name="type">ir.actions.act_window</field>
            <field name="view_mode">form</field>
            <field name="target">new</field>            
        </record>
    </data>
</odoo>
  • saveSelectionsToSessionStorage"true"则表示点击复选框时,将当前选取的记录存到浏览器sessionStorage中,可选
odoo14\custom\estate\security\ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
# ...略
access_demo_wizard_model,access_demo_wizard_model,model_demo_wizard,base.group_user,1,1,1,1

注意:wizard模型也是需要添加模型访问权限配置的

复选框及勾选数据获取实现

大致思路通过继承web.ListRenderer实现自定义ListRenderer,进而实现复选框展示及勾选数据获取。

odoo14\custom\estate\static\src\js\list_renderer.js

注意:之所以将uuid函数定义在list_renderer.js中,是为了避免因为js顺序加载问题,可能导致加载list_renderer.js时找不到uuid函数定义问题。

function uuid() {
	var s = [];
	var hexDigits = "0123456789abcdef";
	for (var i = 0; i < 36; i++) {
		s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
	}
	s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
	s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
	s[8] = s[13] = s[18] = s[23] = "-";

	var uuid = s.join("");
	return uuid;
}

odoo.define('estate.ListRenderer', function (require) {
    "use strict";

 	var ListRenderer = require('web.ListRenderer');
	ListRenderer = ListRenderer.extend({
	    init: function (parent, state, params) {
		    this._super.apply(this, arguments);
		    this.hasCheckBoxes = false;
			if ('hasCheckBoxes' in params.arch.attrs && params.arch.attrs['hasCheckBoxes']) {
                this.objectID = uuid();
                $(this).attr('id', this.objectID);

			    this.hasCheckBoxes = true;
			    this.hasSelectors = true;
			    this.records = {}; // 存放当前界面记录
			    this.recordsSelected = {}; // 存放选取的记录
			    this.modelName = undefined; // 定义点击列表复选框时需要访问的模型
			    this.modelMethod = undefined; // 定义点击列表复选框时需要调用的模型方法
			    this.jsMethodOnModelMethodDone = undefined; // 定义modelMethod方法执行完成后,需要调用的javascript方法
			    this.jsMethodOnToggleCheckbox = undefined; // 定义点击列表复选框时需要调用的javascript方法,比modelMethod优先执行


			    if ('modelName' in params.arch.attrs && params.arch.attrs['modelName']) {
			        this.modelName = params.arch.attrs['modelName'];
			    }
			    if ('modelMethod' in params.arch.attrs && params.arch.attrs['modelMethod']) {
			        this.modelMethod = params.arch.attrs['modelMethod'];
			    }
			    if ('jsMethodOnModelMethodDone' in params.arch.attrs && params.arch.attrs['jsMethodOnModelMethodDone']){
			        this.jsMethodOnModelMethodDone = params.arch.attrs['jsMethodOnModelMethodDone'];
			    }

			    if ('jsMethodOnToggleCheckbox' in params.arch.attrs && params.arch.attrs['jsMethodOnToggleCheckbox']) {
			        this.jsMethodOnToggleCheckbox = params.arch.attrs['jsMethodOnToggleCheckbox'];
			    }
                
                if ('saveSelectionsToSessionStorage' in params.arch.attrs && params.arch.attrs['saveSelectionsToSessionStorage']) {
			        this.saveSelectionsToSessionStorage = params.arch.attrs['saveSelectionsToSessionStorage'];
			    }
            }
		},
//		_onToggleSelection: function (ev) {
            // 点击列表表头的全选/取消全选复选框时会调用该函数
//		    this._super.apply(this, arguments);
//        },
        _onToggleCheckbox: function (ev) {
            if (this.hasCheckBoxes) {
                var classOfEvTarget = $(ev.target).attr('class');
                /* cstom-control-input 刚好点中复选框input,
                custom-control custom-checkbox 刚好点中复选框input的父元素div
                o_list_record_selector 点击到复选框外上述div的父元素*/                
                if (['custom-control custom-checkbox', 'custom-control-input', 'o_list_record_selector'].includes(classOfEvTarget)){
                    if (this.jsMethodOnToggleCheckbox) {
                        eval(this.jsMethodOnToggleCheckbox)
                    }

                    var id = $(ev.currentTarget).closest('tr').data('id'); // 'custom-control-input' == classOfEvTarget
                    var checked = !this.$(ev.currentTarget).find('input').prop('checked') // 获取复选框是否框选 'custom-control-input' != classOfEvTarget
                    if ('custom-control-input' ==  classOfEvTarget) {
                        checked = this.$(ev.currentTarget).find('input').prop('checked')
                    }
                    
                    if (id == undefined) {
                        if (checked == true) { // 全选
                            this.recordsSelected = JSON.parse(JSON.stringify(this.records));
                        } else { // 取消全选
                            this.recordsSelected = {};
                        }
                    } else {
                        if (checked == true) { // 勾选单条记录
                            this.recordsSelected[id] = this.records[id];
                        } else { // 取消勾选单条记录
                            delete this.recordsSelected[id];
                        }
                    }

                    if (this.saveSelectionsToSessionStorage) {
                        window.sessionStorage[this.objectID] = JSON.stringify(this.recordsSelected);
                    }
                    
                    // 通过rpc请求模型方法,用于传输界面勾选的记录数据
                    if (this.modelName && this.modelMethod) {
                        self = this;
                        this._rpc({
                                model: this.modelName,
                                method: this.modelMethod,
                                args: [this.recordsSelected],
                            }).then(function (res) {
                                if (self.jsMethodOnModelMethodDone) {
                                    eval(self.jsMethodOnModelMethodDone);
                                }
                            });
                    }
                }
            }

            this._super.apply(this, arguments);

        },
        _renderRow: function (record) {
            // 打开列表页时会渲染行,此时存储渲染的记录
            if (this.hasCheckBoxes) {
                this.records[record.id] = {'data': record.data, 'context': record.context};
            }
            return this._super.apply(this, arguments);
        }

	});

odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer; //覆盖原有的ListRender服务
});

实践过程中,有尝试过以下实现方案,视图通过指定相同服务ID web.ListRenderer来覆盖框架自带的web.ListRenderer定义,这种实现方案只能在非Debug模式下正常工作,且会导致无法开启Debug模式,odoo.define实现中会对服务是否重复定义做判断,如果重复定义则会抛出JavaScript异常。

odoo.define('web.ListRenderer', function (require) {
    "use strict";
    //...略,同上述代码
    // odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer; 
    return ListRenderer;
});

笔者后面发现,可以使用include替代extend方法修改现有的web.ListRenderer,如下

odoo.define('estate.ListRenderer', function (require) {
    "use strict";

 	var ListRenderer = require('web.ListRenderer');
	ListRenderer = ListRenderer.include({//...略,同上述代码});
    
    // odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer;  //不需要添加这行代码了
});
odoo14\custom\estate\static\src\js\demo_wizard_views.js
实现版本1

demo_wizard_views.xml实现版本1使用

function disableActionConfirmButton(){ // 禁用按钮
    $("button[name='action_confirm']").attr("disabled", true);
}

function enableActionConfirmButton(){ // 启用按钮
    $("button[name='action_confirm']").attr("disabled", false);
}

这里的设计是,执行复选框操作时,先禁用按钮,不允许执行确认操作,因为执行复选框触发的请求可能没那么快执行完成,前端数据可能没完全传递给后端,此时去执行操作,可能会导致预期之外的结果。所以,等请求完成再启用按钮。

实现版本2

demo_wizard_views.xml实现版本2使用

function do_confirm_action(modelName, modelMethod, context){
    $("button[name='action_confirm']").attr("disabled", true); // 点击按钮后,禁用按钮状态,比较重复点击导致重复发送请求    
    var wizard_dialog = $(event.currentTarget.offsetParent.parentElement.parentElement);
    var dataUUID = $(event.currentTarget.parentElement.parentElement.parentElement.parentElement).find('div.o_list_view').prop('id');
    var rpc = odoo.__DEBUG__.services['web.rpc'];
    rpc.query({
        model: modelName,
        method: modelMethod,
        args: [JSON.parse(window.sessionStorage.getItem(dataUUID) || '{}')]
    }).then(function (res)         if (res == true) {
            wizard_dialog.css('display', 'none'); // 隐藏对话框
            window.sessionStorage.removeItem(dataUUID);
        } else {
            $("button[name='action_confirm']").attr("disabled", false);
        }
    }).catch(function (err) {
        $("button[name='action_confirm']").attr("disabled", false);
    });
}
odoo14\odoo\addons\base\rng\tree_view.rng

可选操作。如果希望hasCheckBoxesmodelNamemodelMethod等也可作用于非内联tree视图,则需要编辑该文件,添加hasCheckBoxesmodelNamemodelMethod等属性,否则,更新应用的时候会报错。

<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0"
             xmlns:a="http://relaxng.org/ns/annotation/1.0"
             datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
    <!-- ...此处内容已省略 -->
    <rng:define name="tree">
        <rng:element name="tree">
            <!-- ...此处内容已省略 -->
            <rng:optional><rng:attribute name="decoration-warning"/></rng:optional>
            <rng:optional><rng:attribute name="banner_route"/></rng:optional>
            <rng:optional><rng:attribute name="sample"/></rng:optional>
            <!--在此处添加新属性>
            <rng:optional><rng:attribute name="hasCheckBoxes"/></rng:optional>
            <rng:optional><rng:attribute name="modelName"/></rng:optional>
            <rng:optional><rng:attribute name="modelMethod"/></rng:optional>
            <rng:optional><rng:attribute name="jsMethodOnModelMethodDone"/></rng:optional>
            <rng:optional><rng:attribute name="jsMethodOnToggleCheckbox"/></rng:optional>
            <rng:optional><rng:attribute name="saveSelectionsToSessionStorage"/></rng:optional>
            <!-- ...此处内容已省略 -->
        </rng:element>
    </rng:define>
    <!-- ...此处内容已省略 -->
</rng:grammar>
odoo14\custom\estate\views\webclient_templates.xml

用于加载自定义js

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="assets_common" inherit_id="web.assets_common" name="Backend Assets (used in backend interface)">
         <xpath expr="//script[last()]" position="after">
             <script type="text/javascript" src="/estate/static/src/js/list_renderer.js"></script>
             <script type="text/javascript" src="/estate/static/src/js/demo_wizard_views.js"></script>
         </xpath>
    </template>
</odoo>
odoo14\custom\estate\__manifest__.py

加载自定义模板文件,进而实现自定义js文件的加载

#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
    'name': 'estate',
    'depends': ['base'],
    'data':[
        'views/webclient_templates.xml',
        'security/ir.model.access.csv',
        #...略
        'wizards/demo_wizard_views.xml'
        'views/estate_property_views.xml',
        'views/estate_property_offer_views.xml',
     ]
}

记录详情页视图实现

odoo14\custom\estate\views\estate_property_views.xml
<?xml version="1.0"?>
<odoo>
    <!--...略-->
    <record id="estate_property_view_form" model="ir.ui.view">
        <field name="name">estate.property.form</field>
        <field name="model">estate.property</field>
        <field name="arch" type="xml">
            <form string="estate property form">
                <header>
                     <button name="%(action_demo_wizard)d"
                                type="action"
                                string="选取offers" class="oe_highlight"/>
                    <!--...略-->
                </header>
                <sheet>
                    <!--...略-->                    
                    <notebook>
                        <!--...略-->                        
                        <page string="Offers">
                            <field name="offer_ids" attrs="{'readonly': [('state', 'in', ['Offer Accepted','Sold','Canceled'])]}"/>
                        </page>
                        <!--...略-->     
                    </notebook>
                </sheet>
            </form>
        </field>
    </record>    
</odoo>

说明:class="oe_highlight" 设置按钮高亮显示

https://blog.csdn.net/CBGCampus/article/details/128196983


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK