

odoo 给列表视图添加按钮实现数据文件导入 - 授客
source link: https://www.cnblogs.com/shouke/p/17135868.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.

Odoo 14.0-20221212 (Community Edition)
模块文件组织结构
说明:为了更好的表达本文主题,一些和主题无关的文件、代码已略去
odoo14\custom\estate
│ __init__.py
│ __manifest__.py
│
├─models
│ estate_customer.py
│ __init__.py
│
├─security
│ ir.model.access.csv
│
├─static
│ ├─img
│ │ icon.png
│ │
│ └─src
│ ├─js
│ │ estate_customer_tree_upload.js
│ │
│ └─xml
│ estate_customer_tree_view_buttons.xml
│
└─views
estate_customer_views.xml
estate_menus.xml
webclient_templates.xml
测试模型定义
odoo14\custom\estate\models\estate_customer.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import base64
import openpyxl
from odoo.exceptions import UserError
from odoo import models, fields, _ # _ = GettextAlias()
from tempfile import TemporaryFile
class EstateCustomer(models.Model):
_name = 'estate.customer'
_description = 'estate customer'
name = fields.Char(required=True)
age = fields.Integer()
description = fields.Text()
def create_customer_from_attachment(self, attachment_ids=None):
"""
:param attachment_ids: 上传的数据文件ID列表
"""
attachments = self.env['ir.attachment'].browse(attachment_ids)
if not attachments:
raise UserError(_("未找到上传的文件"))
for attachment in attachments:
file_name_suffix = attachment.name.split('.')[-1]
# 针对文本文件,暂时不实现数据存储,仅演示如何处理文本文件
if file_name_suffix in ['txt', 'html']: # 文本文件
lines = base64.decodebytes(attachment.datas).decode('utf-8').split('\n')
for line in lines:
print(line)
elif file_name_suffix in ['xlsx', 'xls']: # excel文件
file_obj = TemporaryFile('w+b')
file_obj.write(base64.decodebytes(attachment.datas))
book = openpyxl.load_workbook(file_obj, read_only=False)
sheets = book.worksheets
for sheet in sheets:
rows = sheet.iter_rows(min_row=2, max_col=3) # 从第二行开始读取,每行读取3列
for row in rows:
name_cell, age_cell, description_cell = row
self.create({'name': name_cell.value, 'age': age_cell.value, 'description': description_cell.value})
else:
raise UserError(_("不支持的文件类型,暂时仅支持.txt,.html,.xlsx,.xls文件"))
return {
'action_type': 'reload', # 导入成功后,希望前端执行的动作类型, reload-刷新tree列表, do_action-执行action
}
- 函数返回值,具体需要返回啥,实际取决于下文js实现(上传成功后需要执行的操作),这里结合实际可能的需求,额外提供另外几种返回值供参考:
形式1:实现替换当前页面的效果
return {
'action_type': 'do_action',
'action': {
'name': _('导入数据'),
'res_model': 'estate.customer',
'views': [[False, "tree"]],
'view_mode': 'tree',
'type': 'ir.actions.act_window',
'context': self._context,
'target': 'main'
}
}
形式2:弹出对话框效果
return {
'action_type': 'do_action',
'action': {
'name': _('导入成功'),
'res_model': 'estate.customer.wizard',
'views': [[False, "form"]],
'view_mode': 'form',
'type': 'ir.actions.act_window',
'context': self._context,
'target': 'new'
}
}
说明:打开estate.customer.wizard
默认form
视图
形式3:实现类似浏览器刷新当前页面效果
return {
'action_type': 'do_action',
'action': {
'type': 'ir.actions.client',
'tag': 'reload' # 或者替换成 'tag': 'reload_context',
}
}
odoo14\custom\estate\models\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import estate_customer
测试数据文件
mydata.xlsx
姓名 | 年龄 | 备注 |
---|---|---|
张三 | 30 | 喜好运动 |
李四 | 28 | 喜欢美食 |
王五 | 23 |
测试模型视图定义
odoo14\custom\estate\views\estate_customer_views.xml
<?xml version="1.0"?>
<odoo>
<record id="link_estate_customer_action" model="ir.actions.act_window">
<field name="name">顾客信息</field>
<field name="res_model">estate.customer</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_customer_view_tree" model="ir.ui.view">
<field name="name">estate.customer.tree</field>
<field name="model">estate.customer</field>
<field name="arch" type="xml">
<tree js_class="estate_customer_tree" limit="15">
<field name="name" string="Title"/>
<field name="age" string="Age"/>
<field name="description" string="Remark"/>
</tree>
</field>
</record>
<record id="estate_customer_view_form" model="ir.ui.view">
<field name="name">estate.customer.form</field>
<field name="model">estate.customer</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" />
<field name="age"/>
<field name="description"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
说明:<tree js_class="estate_customer_tree" limit="15">
,其中estate_customer_tree
为下文javascript中定义的组件,实现添加自定义按钮;limit
设置列表视图每页最大显示记录数
odoo14\custom\estate\views\estate_menus.xml
<?xml version="1.0"?>
<odoo>
<menuitem id="test_menu_root" name="Real Estate" web_icon="estate,static/img/icon.png">
<menuitem id="data_import_testing" name="数据导入测试" action="link_estate_customer_action"/>
</menuitem>
</odoo>
estate_customer_tree
组件定义
为列表视图添加自定义上传数据文件按钮
odoo14\custom\estate\static\src\js\estate_customer_tree_upload.js
odoo.define('estate.upload.customer.mixin', function (require) {
"use strict";
var core = require('web.core');
var _t = core._t;
var qweb = core.qweb;
var UploadAttachmentMixin = {
start: function () {
// 定义一个唯一的fileUploadID(形如 my_file_upload_upload737)和一个回调方法
this.fileUploadID = _.uniqueId('my_file_upload');
$(window).on(this.fileUploadID, this._onFileUploaded.bind(this));
return this._super.apply(this, arguments);
},
_onAddAttachment: function (ev) {
// 一旦选择了附件,自动提交表单(关闭上传对话框)
var $input = $(ev.currentTarget).find('input.o_input_file');
if ($input.val() !== '') {
// o_estate_customer_upload定义在对应的QWeb模版中
var $binaryForm = this.$('.o_estate_customer_upload form.o_form_binary_form');
$binaryForm.submit();
}
},
_onFileUploaded: function () {
// 创建附件后的回调,根据附件ID执行相关处程序
var self = this;
var attachments = Array.prototype.slice.call(arguments, 1);
// 获取附件ID
var attachent_ids = attachments.reduce(function(filtered, record) {
if (record.id) {
filtered.push(record.id);
}
return filtered;
}, []);
// 请求模型方法
return this._rpc({
model: 'estate.customer', //模型名称
method: 'create_customer_from_attachment', // 模型方法
args: ["", attachent_ids],
context: this.initialState.context,
}).then(function(result) { // result为一个字典
if (result.action_type == 'reload') {
self.trigger_up('reload'); // 实现在不刷新页面的情况下,刷新列表视图// 此处换成 self.reload(); 发现效果也是一样的
} else if (result.action_type == 'do_action') {
self.do_action(result.action); // 执行action动作
} else { // 啥也不做
}
// 重置 file input, 如果需要,可以再次选择相同的文件,如果不添加以下这行代码,不刷新当前页面的情况下,无法重复导入相同的文件
self.$('.o_estate_customer_upload .o_input_file').val('');
}).catch(function () {
self.$('.o_estate_customer_upload .o_input_file').val('');
});
},
_onUpload: function (event) {
var self = this;
// 如果隐藏的上传表单不存在则创建
var $formContainer = this.$('.o_content').find('.o_estate_customer_upload');
if (!$formContainer.length) {
// estate.CustomerHiddenUploadForm定义在对应的QWeb模版中
$formContainer = $(qweb.render('estate.CustomerHiddenUploadForm', {widget: this}));
$formContainer.appendTo(this.$('.o_content'));
}
// 触发input选取文件
this.$('.o_estate_customer_upload .o_input_file').click();
},
}
return UploadAttachmentMixin;
});
odoo.define('estate.customer.tree', function (require) {
"use strict";
var core = require('web.core');
var ListController = require('web.ListController');
var ListView = require('web.ListView');
var UploadAttachmentMixin = require('estate.upload.customer.mixin');
var viewRegistry = require('web.view_registry');
var CustomListController = ListController.extend(UploadAttachmentMixin, {
buttons_template: 'EstateCustomerListView.buttons',
events: _.extend({}, ListController.prototype.events, {
'click .o_button_upload_estate_customer': '_onUpload',
'change .o_estate_customer_upload .o_form_binary_form': '_onAddAttachment',
}),
});
var CustomListView = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Controller: CustomListController,
}),
});
viewRegistry.add('estate_customer_tree', CustomListView);
});
说明:如果其它模块的列表视图也需要实现类似功能,想复用上述js,需要替换js中以下内容:
-
修改
estate.upload.customer.mixin
为其它自定义全局唯一值 -
替换
o_estate_customer_upload
为在对应按钮视图模板中定义的对应class属性值 -
替换
estate.CustomerHiddenUploadForm
为在对应按钮视图模板中定义的隐藏表单模版名称 -
替换
EstateCustomerListView.buttons
为对应按钮视图模板中定义的按钮模版名称 -
根据需要替换
this._rpc
函数中的model
参数值("estate.customer"),method
参数值("create_customer_from_attachment"),必要的话,修改then
函数实现。 -
替换
estate_customer_tree
为自定义全局唯一值 -
do_action
为Widget()
的快捷方式(定义在odoo14\odoo\addons\web\static\src\js\core\service_mixins.js
中),用于查找当前action管理器并执行action --do_action
函数的第一个参数,格式如下:{ 'type': 'ir.actions.act_window', 'name': _('导入数据'), 'res_model': 'estate.customer', 'views': [[False, "tree"], [False, "form"]], 'view_mode': 'tree', 'context': self._context, 'target': 'current' }
加载js脚本xml文件定义
odoo14\custom\estate\views\webclient_templates.xml
<?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/estate_customer_tree_upload.js"></script>
</xpath>
</template>
</odoo>
按钮视图模板定义
odoo14\custom\estate\static\src\xml\estate_customer_tree_view_buttons.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="estate.CustomerHiddenUploadForm">
<div class="d-none o_estate_customer_upload">
<t t-call="HiddenInputFile">
<t t-set="multi_upload" t-value="true"/>
<t t-set="fileupload_id" t-value="widget.fileUploadID"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<input type="hidden" name="model" value=""/>
<input type="hidden" name="id" value="0"/>
</t>
</div>
</t>
<t t-name="EstateCustomerListView.buttons" t-extend="ListView.buttons">
<t t-jquery="button.o_list_button_add" t-operation="after">
<!--btn表示按钮类
按钮颜色:btn-primary--主要按钮,btn-secondary次要按钮
按钮大小:btn-sm小按钮,btn-lg大按钮
默认按钮:btn-default-->
<button type="button" class="btn btn-primary o_button_upload_estate_customer">Upload</button>
</t>
</t>
</templates>
t-name
:定义模版名称
t-extend
:定义需要继承的模板。
t-jquery
:接收一个CSS 选择器,用于查找上下文中,同CSS选择器匹配的元素节点(为了方便描述,暂且称之为上下文节点)
t-operation
:设置需要对上下文节点执行的操作(为了方便描述,暂且将t-operation
属性所在元素称为模板元素),可选值如下:
-
append
将模板元素内容(body)追加到上下文节点的最后一个子元素后面。
-
prepend
将模板元素内容插入到上下文节点的第一个子元素之前。
-
before
将模板元素内容插入到上下文节点之前。
-
after
将模板元素内容插入到上下文节点之后。
-
inner
将模板元素内容替换上下文节点元素内容(所有子节点)
-
replace
将模板元素内容替换上下文节点
-
attributes
模版元素内容应该是任意数量的属性元素,每个元素都有一个名称属性和一些文本内容,上下文节点的命名属性将被设置为属性元素的值(如果已经存在则替换,如果不存在则添加)
注意:参考官方文档,t-extend
这种继承方式为旧的继承方式,已废弃,笔者实践了最新继承方式,如下
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="DataImportTestingListView.buttons" t-inherit="ListView.buttons" t-inherit-mode="primary">
<xpath expr="//button[@calss='btn btn-primary o_list_button_add']" position="after">
<button type="button" class="btn btn-primary o_button_upload_estate_customer">Upload</button>
</xpath>
</t>
</templates>
发现会报错:
ValueError: Module ListView not loaded or inexistent, or templates of addon being loaded (estate) are misordered
参考连接:https://www.odoo.com/documentation/14.0/zh_CN/developer/reference/javascript/qweb.html
模型访问权限配置
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_estate_customer_all_perm,access_estate_customer_all_perm,model_estate_customer,base.group_user,1,1,1,1
模块其它配置
odoo14\custom\estate\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import models
odoo14\custom\estate\__manifest__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
'name': 'estate',
'depends': ['base'],
'data':[
'security/ir.model.access.csv',
'views/webclient_templates.xml',
'views/estate_customer_views.xml',
'views/estate_menus.xml'
],
'qweb':[# templates定义文件不能放data列表中,提示不符合shema,因为未使用<odoo>元素进行“包裹”
'static/src/xml/estate_customer_tree_view_buttons.xml',
]
}

Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK