实践环境

Odoo 14.0-20221212 (Community Edition)

需求描述

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

详情页所属模型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>

效果如下

参考连接: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

odoo wizard界面显示带复选框列表及勾选数据获取的更多相关文章

  1. 20151215单选按钮列表,复选框列表:CheckBoxList

    单选框:RadioButton GroupName:组名,如果要实现单选效果每个单选按钮的组名必须一样 是否被选中 RadioButton.checked 单选按钮列表:RadioButtonList ...

  2. js 复选框 全选都选 如果某一个子复选框没选中 则全选按钮不选中

    <!DOCTYPE HTML> <html> <head> <meta charset=UTF-8> <title>js 复选框 全选都选 ...

  3. js 全选选框与取消全选代码

    设置一个全选选框和四个子选框,要实现点击全选后四个子选框选中,取消全选后四个子选框也取消.全选后点击某个子选框,全选也能取消.当四个子选框都选中时,全选框也被选择. 实现代码: <script& ...

  4. tree的所有节点都勾选上或者取消勾选

    还有一个功能,就是让tree的所有节点都勾选上或者取消勾选,在api中找了一下有一个方法: check target 选中指定节点. 那我们只能是选中根节点后,实现全选. getRoot none 获 ...

  5. 实现在DevExpress.XtraGrid.GridControl的列头绘制复选框以实现全选的功能

    首先新建一个Win Form测试项目,拖一个GridControl控件到窗体上. public partial class Form1 : Form { public Form1() { Initia ...

  6. easyui datagrid 让某行复选框置灰不能选

    easyui中datagrid 让某行复选框置灰不能进行选中操作,以下为主要部分的code. //加载完毕后获取所有的checkbox遍历 onLoadSuccess: function(data){ ...

  7. layui 复选框checkbox 实现全选全选

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. [oldboy-django][2深入django]老师管理 -- form表单如何生成多选框标签,多选框的默认值显示,以及多选框数据插入到数据库,多选框数据更改到数据库

    1 form表单如何生成多选框(包含了多选框可选择内容) - Form设置班级输入框为 select多选 - 多选 class TeacherForm(Form): name = fields.Cha ...

  9. element-ui 里面el-checkbox多选框,实现全选单选

    data里面定义了 data:[],        actionids:[],//选择的那个actionid        num1:0,//没选择的计数        num2:0,//选中的计数  ...

  10. bootgrid修改成可以全勾选和全取消勾选操作

    1. 引言 由于项目需要,需要在不同页面上选择全勾选能全部勾选所有的记录,反勾选也如此.这个需求可以解决了一个样例:如果有150条记录,当前页就10条,你又在每一个页面勾选部分的记录,然后,如果你要全 ...

随机推荐

  1. turtle绘制风轮

    题目要求: 使用turtle库,绘制一个风轮效果,其中,每个风轮内角为45度,风轮边长150像素. 我的代码: import turtle turtle.setup(500,500,100,200) ...

  2. (二).JavaScript的运算符和表达式,数据类型转化

    4. 运算符和表达式 4.3 赋值运算符和表达式 1.赋值运算符 = 作用:赋值运算符就是将右边的内容赋值给左边的变量或属性. var result = 1 + 2; 2.复合赋值运算符 +=,-=, ...

  3. 什么是5G垂直行业?

    什么是垂直行业呢? 感觉"垂直行业"这个词在太多地方遇到,但是这个词的涵盖范围到底是什么呢? 垂直这一概念源于两条直线(或平面)的直角交叉,两条直线是相互作为参照物的.比如,我们可 ...

  4. MySQL:查询语句 case when then 的用法

    转载网址: https://blog.csdn.net/h123hlll/article/details/122366213

  5. fatal: unable to access 'https://github.com/github-eliviate/papers.git/': Failed to connect to github.com port 443 after 21107 ms: Timed out

    fatal: unable to access 'https://github.com/github-eliviate/papers.git/': Failed to connect to githu ...

  6. RPA的应用及工作原理

    通过本章学习,您将了解到: 什么是RPA RPA能做些什么 RPA的应用有什么特点 RPA是怎样进行工作的 RPA怎么实现人机协作 RPA的未来趋势怎么样 什么是RPA RPA是利用软件来执行业务流程 ...

  7. 阿里云OSS前端直传+net core后端签名

    OSS前端直传+后端签名 一.服务端签名后前端直传 首先安装阿里云SDK Aliyun.OSS.SDK.NetCore public static string accessKeyId = " ...

  8. CTF-NEFU校赛-题解

    Write by NEFUNSI: ghosin 0ERROR 签到 signin 下载 signin.txt 打开得到一串 base64,解码得到 flag{we1come_t0_NEFUCTF!} ...

  9. Service Mesh之Istio基础入门

    技术背景 分布式服务治理 所谓分布式服务治理就是对服务不断增长的复杂度的管控和管理:管控及管理包含网络拓扑变动.网络延时.通信安全.API网关.服务注册和发现.服务熔断容错.服务超时重试.服务部署.数 ...

  10. 【踩坑系列】发送微信模板消息返回40165 invalid weapp pagepath

    1. 踩坑经历 最近做了个需求,需要往公司微信公众号推送一个模板消息,并且点击该消息需要跳转到公司小程序的某个页面. 1.1 拿到模板id 既然是发送模板消息,第一步就需要登录微信公众号后台新建模板消 ...