實踐環境 Odoo 14.0-20221212 (Community Edition) 代碼實現 模塊文件組織結構 說明:為了更好的表達本文主題,一些和主題無關的文件、代碼已略去 odoo14\custom\estate │ __init__.py │ __manifest__.py │ ├─mod ...
實踐環境
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
組件定義
js實現
為列表視圖添加自定義上傳數據文件按鈕
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',
]
}
最終效果
作者:授客
微信/QQ:1033553122
全國軟體測試QQ交流群:7156436
Git地址:https://gitee.com/ishouke
友情提示:限於時間倉促,文中可能存在錯誤,歡迎指正、評論!
作者五行缺錢,如果覺得文章對您有幫助,請掃描下邊的二維碼打賞作者,金額隨意,您的支持將是我繼續創作的源動力,打賞後如有任何疑問,請聯繫我!!!
微信打賞
支付寶打賞 全國軟體測試交流QQ群