由於自己並非資訊相關科系畢業,基礎知識與技能相當貧乏,透過此次學習Odoo系統基礎建置與開發,我才初次接觸了如何在Windows系統透過VM來使用Linux系統,以及終端機指令的操作,來一步一步建置Odoo系統,包括所需的Python套件、PostgreSQL資料庫系統、pgAdmin資料庫管理工具、Odoo系統資料夾結構、Odoo系統設定文件、PyCharm整合開發環境、系統更新與維護方式等等,需要了解與學習的技術非常非常的多,自己也僅學習使用了一小部分的工具與技巧而已。
系統開發由於牽涉到許多資料夾結構與不同文件如csv、py、xml等,因此未來使用Git做為示範檔案存放空間的需求或許也將越來越迫切,現階段仍純粹在網誌整理、發布文章;本系列一共六篇學習筆記,為花費大量時間綜合整理Peter Wu老師課堂教學、《Odoo快速入門與實戰》簡體書,以及零散的網路教學文章而得,前五篇的學習筆記完全操作使用Odoo社群版,第六篇的學習筆記則加入「康虎雲報表」的安裝與教學,可至「康虎軟件工作室」官方網站下載相關軟體,配合學習筆記的說明來安裝使用。
自學Odoo系統的建置開發相當不容易:在台灣,Odoo系統的知名度與普及性遠不如SAP、鼎新、Oracle等大廠,資訊廠商與開發人員的數量也相對稀少,就連參考書籍也完全沒有繁體中文的版本,僅有少數英文書與簡體書可以購買(而且這些書都寫得很糟),加上這幾年Odoo系統的快速發展,不同的版本之間有不小的變動,學會的技術可能過幾年就完全不能用了…。綜合上述許多原因,即便本系列學習筆記已經整理很清楚了,對於完全零經驗的開發者而言,相信仍舊難以閱讀、理解。
目前Odoo系統在歐美與中國大陸的資源、社群與討論,都比台灣頻繁、密集得多,想要入門Odoo系統的學習者建議還是需要老師來帶領,才有辦法掌握操作、管理、開發的各項眉眉角角。
l 在models資料夾內創建openacademy_teacher.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademyteacher(models.Model):
_name = 'openacademy.teacher'
teacher_no = fields.Char(string = '教職編號')
teacher_name = fields.Char(string = '教員姓名')
# api裝飾器說明:
# @api.multi-用以回應多筆結果,如果我們在模型的方法上沒有添加任何裝飾器,那麼預設使用此裝飾器,以下有範例
# @api.one-用以回應一筆結果,官方不建議使用裝飾器
# @api.model-用以寫入資料庫,以下有範例
# @api.depends-用在計算的虛擬欄位,以下有範例
# @api.constraints-用於評估和檢查
# @api.onchange-用於系統在與用戶進行互動時,自動更新相關聯的字段,例如當單價發生變化時,金額會自動重新計算
# 操作函式說明:
# name_get(self)-預設用於獲取display_name,模型頁面顯示資料時,以及Many2one等字段關聯至此模型時,會使用此方法,以下有範例
# create(self, [vals])-用以創建,vals帶入使用者於系統輸入的資料,以下有範例
# write(self, [vals])-用以修改,vals帶入使用者於系統輸入的資料,以下有範例
# unlink(self)-用以刪除,以下有範例
# load([fields], [data])-用於從csv檔匯入數據
# export_data([fields], raw_data = False)-用於從頁面端匯出數據
# name_search(name = '', args = None, operator = 'ilike', limit = 100)-獲取資料列表,每個tuple代表一行紀錄,顯示名稱將會與參數name的字串進行核對
# name_create(name)-可用於根據名稱快速創建一行新紀錄
# default_get([fields])-可返回一個具有待創建新紀錄的預設值字典
# fields_get()-可用於描述模型字段的定義
# fields_view_get()-可用於獲取使用者介面視圖的結構
# read([fields])-返回的紀錄僅包括參數中包含的字段
# search_read([domain], [fields], offset = 0, limit = None, order = None)-可以在結果紀錄集上面再次執行查找的操作
@api.multi
def name_get(self):
result = []
for rec in self:
myname = "[%s]%s" % (rec.teacher_no, rec.teacher_name)
result.append((rec.id, myname))
return result
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademyteacher(models.Model):
_name = 'openacademy.teacher'
teacher_no = fields.Char(string = '教職編號')
teacher_name = fields.Char(string = '教員姓名')
# api裝飾器說明:
# @api.multi-用以回應多筆結果,如果我們在模型的方法上沒有添加任何裝飾器,那麼預設使用此裝飾器,以下有範例
# @api.one-用以回應一筆結果,官方不建議使用裝飾器
# @api.model-用以寫入資料庫,以下有範例
# @api.depends-用在計算的虛擬欄位,以下有範例
# @api.constraints-用於評估和檢查
# @api.onchange-用於系統在與用戶進行互動時,自動更新相關聯的字段,例如當單價發生變化時,金額會自動重新計算
# 操作函式說明:
# name_get(self)-預設用於獲取display_name,模型頁面顯示資料時,以及Many2one等字段關聯至此模型時,會使用此方法,以下有範例
# create(self, [vals])-用以創建,vals帶入使用者於系統輸入的資料,以下有範例
# write(self, [vals])-用以修改,vals帶入使用者於系統輸入的資料,以下有範例
# unlink(self)-用以刪除,以下有範例
# load([fields], [data])-用於從csv檔匯入數據
# export_data([fields], raw_data = False)-用於從頁面端匯出數據
# name_search(name = '', args = None, operator = 'ilike', limit = 100)-獲取資料列表,每個tuple代表一行紀錄,顯示名稱將會與參數name的字串進行核對
# name_create(name)-可用於根據名稱快速創建一行新紀錄
# default_get([fields])-可返回一個具有待創建新紀錄的預設值字典
# fields_get()-可用於描述模型字段的定義
# fields_view_get()-可用於獲取使用者介面視圖的結構
# read([fields])-返回的紀錄僅包括參數中包含的字段
# search_read([domain], [fields], offset = 0, limit = None, order = None)-可以在結果紀錄集上面再次執行查找的操作
@api.multi
def name_get(self):
result = []
for rec in self:
myname = "[%s]%s" % (rec.teacher_no, rec.teacher_name)
result.append((rec.id, myname))
return result
l 在models資料夾內創建openacademy_student.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
from datetime import datetime
class openacademystudent(models.Model):
_name = 'openacademy.student'
student_id = fields.Many2one('openacademy.studentclass', ondelete = 'cascade')
student_no = fields.Char(string = '學生學號', required = True)
student_name = fields.Char(string = '學生姓名', required = True)
student_contact = fields.Char(string = '聯絡人')
student_class = fields.Selection([('1', '一年級'), ('2', '二年級'), ('3', '三年級')], string = '年級')
student_birthday = fields.Date(string = '生日', default = datetime.now().strftime('%Y-%m-%d'))
student_fm = fields.Selection([('M', '男'), ('F', '女')], string = '性別', default = 'M')
student_memo = fields.Text(string = '備註')
student_obj = fields.Html(string = '圖片')
@api.multi
def name_get(self):
result = []
for rec in self:
if rec.student_class == '1':
myclass = '一年級'
elif rec.student_class == '2':
myclass = '二年級'
elif rec.student_class == '3':
myclass = '三年級'
myname = '[%s]%s-%s' % (rec.student_no, rec.student_name, myclass)
result.append((rec.id, myname))
return result
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
from datetime import datetime
class openacademystudent(models.Model):
_name = 'openacademy.student'
student_id = fields.Many2one('openacademy.studentclass', ondelete = 'cascade')
student_no = fields.Char(string = '學生學號', required = True)
student_name = fields.Char(string = '學生姓名', required = True)
student_contact = fields.Char(string = '聯絡人')
student_class = fields.Selection([('1', '一年級'), ('2', '二年級'), ('3', '三年級')], string = '年級')
student_birthday = fields.Date(string = '生日', default = datetime.now().strftime('%Y-%m-%d'))
student_fm = fields.Selection([('M', '男'), ('F', '女')], string = '性別', default = 'M')
student_memo = fields.Text(string = '備註')
student_obj = fields.Html(string = '圖片')
@api.multi
def name_get(self):
result = []
for rec in self:
if rec.student_class == '1':
myclass = '一年級'
elif rec.student_class == '2':
myclass = '二年級'
elif rec.student_class == '3':
myclass = '三年級'
myname = '[%s]%s-%s' % (rec.student_no, rec.student_name, myclass)
result.append((rec.id, myname))
return result
l 在models資料夾內創建openacademy_studentclass.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademystudentclass(models.Model):
_name = 'openacademy.studentclass'
studentclass_name = fields.Char(string = '班級')
studentclass_teacher = fields.Many2one('openacademy.teacher', string = '班導')
# One2many字段與Many2one字段相呼應,One2many字段並不會在資料庫產生欄位、儲存資料
# One2many字段屬性說明:
# comodel_name-設置與本模型關聯的另一個模型的名稱
# inverse_name-設置與本模型關聯的另一個模型內的關聯字段名稱
# string-是在前端介面看到的名稱
# ondelete-刪除關聯紀錄時本字段的動作-
預設是set null-關聯紀錄被刪除時本字段將被賦值為null;
restricted-關聯紀錄被刪除時會報錯,阻止關聯字段被刪除;
cascade-關聯字段被刪除則本紀錄也將被刪除
# context-關聯資料時附帶訊息,例如設置預設值
# domain-設定查詢條件,用於限定查詢的數據
studentclass_line = fields.One2many('openacademy.student', 'student_id', string = '學員明細')
@api.multi
def name_get(self):
result = []
for rec in self:
myname = '[%s]%s' % (rec.studentclass_name, rec.studentclass_teacher.teacher_name)
result.append((rec.id, myname))
return result
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademystudentclass(models.Model):
_name = 'openacademy.studentclass'
studentclass_name = fields.Char(string = '班級')
studentclass_teacher = fields.Many2one('openacademy.teacher', string = '班導')
# One2many字段與Many2one字段相呼應,One2many字段並不會在資料庫產生欄位、儲存資料
# One2many字段屬性說明:
# comodel_name-設置與本模型關聯的另一個模型的名稱
# inverse_name-設置與本模型關聯的另一個模型內的關聯字段名稱
# string-是在前端介面看到的名稱
# ondelete-刪除關聯紀錄時本字段的動作-
預設是set null-關聯紀錄被刪除時本字段將被賦值為null;
restricted-關聯紀錄被刪除時會報錯,阻止關聯字段被刪除;
cascade-關聯字段被刪除則本紀錄也將被刪除
# context-關聯資料時附帶訊息,例如設置預設值
# domain-設定查詢條件,用於限定查詢的數據
studentclass_line = fields.One2many('openacademy.student', 'student_id', string = '學員明細')
@api.multi
def name_get(self):
result = []
for rec in self:
myname = '[%s]%s' % (rec.studentclass_name, rec.studentclass_teacher.teacher_name)
result.append((rec.id, myname))
return result
l 在models資料夾內創建openacademy_score.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademyscore(models.Model):
_name = "openacademy.score"
score_year = fields.Char(string = '學年', required = True)
score_student = fields.Many2one('openacademy.student', string = '學生')
# digits = (5,1),5代表包含小數點共5個字元,1代表小數點後的位數
score_chinese = fields.Float(string = '國文分數', default = 0, digits = (5,1))
score_math = fields.Float(string = '數學分數', default = 0, digits = (5,1))
score_english = fields.Float(string = '英文分數', default = 0, digits = (5,1))
# compute代表計算而來的,並不會在資料庫產生欄位、儲存資料
score_total = fields.Float(string = '總分', compute = '_get_tot')
score_avg = fields.Float(string = '平均', compute = '_get_avg')
score_bank = fields.Many2one('res.bank', string = '銀行')
@api.multi
def name_get(self):
result = []
for rec in self:
myname = '[%s] %s-%s' % (rec.score_year, rec.score_student.student_no, rec.score_student.student_name)
result.append((rec.id, myname))
return result
# 必填欄位使用此方法驗證,是在UI做限制,而必填欄位使用required = True驗證,除了在UI亦在DB做限制
@api.model
def create(self, vals):
if vals['score_chinese'] < 0 or vals['score_math'] < 0 or vals['score_english'] < 0:
raise UserError('成績不能是負數')
result = super(openacademyscore, self).create(vals)
return result
@api.multi
def write(self, vals):
if vals['score_chinese'] < 0 or vals['score_math'] < 0 or vals['score_english'] < 0:
raise UserError('成績不能是負數')
result = super(openacademyscore, self).write(vals)
return result
@api.multi
def unlink(self):
for rec in self:
if rec.score_chinese > 0 or rec.score_math > 0 or rec.score_english > 0:
raise UserError('已有成績,不得刪除')
res = super(openacademyscore, self).unlink()
return res
@api.depends('score_chinese', 'score_math', 'score_english')
def _get_tot(self):
for rec in self:
rec.update({'score_total': rec.score_chinese + rec.score_math + rec.score_english})
@api.depends('score_chinese', 'score_math', 'score_english')
def _get_avg(self):
for rec in self:
rec.update({'score_avg': (rec.score_chinese + rec.score_math + rec.score_english) / 3})
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademyscore(models.Model):
_name = "openacademy.score"
score_year = fields.Char(string = '學年', required = True)
score_student = fields.Many2one('openacademy.student', string = '學生')
# digits = (5,1),5代表包含小數點共5個字元,1代表小數點後的位數
score_chinese = fields.Float(string = '國文分數', default = 0, digits = (5,1))
score_math = fields.Float(string = '數學分數', default = 0, digits = (5,1))
score_english = fields.Float(string = '英文分數', default = 0, digits = (5,1))
# compute代表計算而來的,並不會在資料庫產生欄位、儲存資料
score_total = fields.Float(string = '總分', compute = '_get_tot')
score_avg = fields.Float(string = '平均', compute = '_get_avg')
score_bank = fields.Many2one('res.bank', string = '銀行')
@api.multi
def name_get(self):
result = []
for rec in self:
myname = '[%s] %s-%s' % (rec.score_year, rec.score_student.student_no, rec.score_student.student_name)
result.append((rec.id, myname))
return result
# 必填欄位使用此方法驗證,是在UI做限制,而必填欄位使用required = True驗證,除了在UI亦在DB做限制
@api.model
def create(self, vals):
if vals['score_chinese'] < 0 or vals['score_math'] < 0 or vals['score_english'] < 0:
raise UserError('成績不能是負數')
result = super(openacademyscore, self).create(vals)
return result
@api.multi
def write(self, vals):
if vals['score_chinese'] < 0 or vals['score_math'] < 0 or vals['score_english'] < 0:
raise UserError('成績不能是負數')
result = super(openacademyscore, self).write(vals)
return result
@api.multi
def unlink(self):
for rec in self:
if rec.score_chinese > 0 or rec.score_math > 0 or rec.score_english > 0:
raise UserError('已有成績,不得刪除')
res = super(openacademyscore, self).unlink()
return res
@api.depends('score_chinese', 'score_math', 'score_english')
def _get_tot(self):
for rec in self:
rec.update({'score_total': rec.score_chinese + rec.score_math + rec.score_english})
@api.depends('score_chinese', 'score_math', 'score_english')
def _get_avg(self):
for rec in self:
rec.update({'score_avg': (rec.score_chinese + rec.score_math + rec.score_english) / 3})
l 在views資料夾內創建openacademy_teacher.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_teacher_tree" model="ir.ui.view">
<field name="name">view.openacademy.teacher.tree</field>
<field name="model">openacademy.teacher</field>
<field name="arch" type="xml">
<tree string="">
<field name="teacher_no"/>
<field name="teacher_name"/>
</tree>
</field>
</record>
<record id="view_openacademy_teacher_form" model="ir.ui.view">
<field name="name">view.openacademy.teacher.form</field>
<field name="model">openacademy.teacher</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="teacher_no"/>
<field name="teacher_name"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_teacher_view" model="ir.actions.act_window">
<field name="name">教職員資料</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.teacher</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_teacher_tree" model="ir.ui.view">
<field name="name">view.openacademy.teacher.tree</field>
<field name="model">openacademy.teacher</field>
<field name="arch" type="xml">
<tree string="">
<field name="teacher_no"/>
<field name="teacher_name"/>
</tree>
</field>
</record>
<record id="view_openacademy_teacher_form" model="ir.ui.view">
<field name="name">view.openacademy.teacher.form</field>
<field name="model">openacademy.teacher</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="teacher_no"/>
<field name="teacher_name"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_teacher_view" model="ir.actions.act_window">
<field name="name">教職員資料</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.teacher</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
l 在views資料夾內創建openacademy_student.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_student_tree" model="ir.ui.view">
<field name="name">view.openacademy.student.tree</field>
<field name="model">openacademy.student</field>
<field name="arch" type="xml">
<tree string="">
<field name="student_no"/>
<field name="student_name"/>
<field name="student_contact"/>
<field name="student_class"/>
<field name="student_birthday"/>
<field name="student_fm"/>
<field name="student_memo"/>
</tree>
</field>
</record>
<record id="view_openacademy_student_form" model="ir.ui.view">
<field name="name">view.openacademy.student.form</field>
<field name="model">openacademy.student</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="student_no"/>
<field name="student_name"/>
<field name="student_contact"/>
<field name="student_class"/>
<field name="student_birthday"/>
<field name="student_fm"/>
<field name="student_memo"/>
<field name="student_obj"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_student_view" model="ir.actions.act_window">
<field name="name">學生基本資料</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.student</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_student_tree" model="ir.ui.view">
<field name="name">view.openacademy.student.tree</field>
<field name="model">openacademy.student</field>
<field name="arch" type="xml">
<tree string="">
<field name="student_no"/>
<field name="student_name"/>
<field name="student_contact"/>
<field name="student_class"/>
<field name="student_birthday"/>
<field name="student_fm"/>
<field name="student_memo"/>
</tree>
</field>
</record>
<record id="view_openacademy_student_form" model="ir.ui.view">
<field name="name">view.openacademy.student.form</field>
<field name="model">openacademy.student</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="student_no"/>
<field name="student_name"/>
<field name="student_contact"/>
<field name="student_class"/>
<field name="student_birthday"/>
<field name="student_fm"/>
<field name="student_memo"/>
<field name="student_obj"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_student_view" model="ir.actions.act_window">
<field name="name">學生基本資料</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.student</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
l 在views資料夾內創建openacademy_studentclass.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_studentclass_tree" model="ir.ui.view">
<field name="name">view.openacademy.studentclass.tree</field>
<field name="model">openacademy.studentclass</field>
<field name="arch" type="xml">
<tree string="">
<field name="studentclass_name"/>
<field name="studentclass_teacher"/>
</tree>
</field>
</record>
<record id="view_openacademy_studentclass_form" model="ir.ui.view">
<field name="name">view.openacademy.studentclass.form</field>
<field name="model">openacademy.studentclass</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="studentclass_name"/>
<field name="studentclass_teacher"/>
</group>
<!--顯示One2many字段所對應的欄位-->
<field name="studentclass_line" nolabel="1">
<tree editable="top" string="Student Info" widget="one2many">
<field name="student_no"/>
<field name="student_name"/>
<field name="student_contact"/>
<field name="student_class"/>
<field name="student_fm"/>
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_studentclass_view" model="ir.actions.act_window">
<field name="name">學員班級資訊</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.studentclass</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_studentclass_tree" model="ir.ui.view">
<field name="name">view.openacademy.studentclass.tree</field>
<field name="model">openacademy.studentclass</field>
<field name="arch" type="xml">
<tree string="">
<field name="studentclass_name"/>
<field name="studentclass_teacher"/>
</tree>
</field>
</record>
<record id="view_openacademy_studentclass_form" model="ir.ui.view">
<field name="name">view.openacademy.studentclass.form</field>
<field name="model">openacademy.studentclass</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="studentclass_name"/>
<field name="studentclass_teacher"/>
</group>
<!--顯示One2many字段所對應的欄位-->
<field name="studentclass_line" nolabel="1">
<tree editable="top" string="Student Info" widget="one2many">
<field name="student_no"/>
<field name="student_name"/>
<field name="student_contact"/>
<field name="student_class"/>
<field name="student_fm"/>
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_studentclass_view" model="ir.actions.act_window">
<field name="name">學員班級資訊</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.studentclass</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
l 在views資料夾內創建openacademy_score.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_score_tree" model="ir.ui.view">
<field name="name">view.openacademy.score.tree</field>
<field name="model">openacademy.score</field>
<field name="arch" type="xml">
<tree string="">
<field name="score_year"/>
<field name="score_student"/>
<field name="score_chinese"/>
<field name="score_math"/>
<field name="score_english"/>
<field name="score_total"/>
<field name="score_avg"/>
</tree>
</field>
</record>
<record id="view_openacademy_score_form" model="ir.ui.view">
<field name="name">view.openacademy.score.form</field>
<field name="model">openacademy.score</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="score_year"/>
<field name="score_student"/>
<field name="score_chinese"/>
<field name="score_math"/>
<field name="score_english"/>
<field name="score_total"/>
<field name="score_avg"/>
<field name="score_bank"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_score_view" model="ir.actions.act_window">
<field name="name">學期成績紀錄</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.score</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_score_tree" model="ir.ui.view">
<field name="name">view.openacademy.score.tree</field>
<field name="model">openacademy.score</field>
<field name="arch" type="xml">
<tree string="">
<field name="score_year"/>
<field name="score_student"/>
<field name="score_chinese"/>
<field name="score_math"/>
<field name="score_english"/>
<field name="score_total"/>
<field name="score_avg"/>
</tree>
</field>
</record>
<record id="view_openacademy_score_form" model="ir.ui.view">
<field name="name">view.openacademy.score.form</field>
<field name="model">openacademy.score</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="score_year"/>
<field name="score_student"/>
<field name="score_chinese"/>
<field name="score_math"/>
<field name="score_english"/>
<field name="score_total"/>
<field name="score_avg"/>
<field name="score_bank"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_score_view" model="ir.actions.act_window">
<field name="name">學期成績紀錄</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.score</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
l 在views資料夾內創建openacademy_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="menu_openacademy_root"
name="學生成績系統"
sequence="200"/>
<menuitem id="menu_openacademy_categ"
name="基礎資料"
parent="menu_openacademy_root"
sequence="10"/>
<menuitem id="menu_openacademy_student_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_student_view"
sequence="10"/>
<menuitem id="menu_openacademy_teacher_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_teacher_view"
sequence="20"/>
<menuitem id="menu_openacademy_studentclass_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_studentclass_view"
sequence="30"/>
<menuitem id="menu_openacademy_score_categ"
name="成績紀錄"
parent="menu_openacademy_root"
sequence="20"/>
<menuitem id="menu_openacademy_score_view"
name="" parent="menu_openacademy_score_categ"
action="action_openacademy_score_view"
sequence="10"/>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="menu_openacademy_root"
name="學生成績系統"
sequence="200"/>
<menuitem id="menu_openacademy_categ"
name="基礎資料"
parent="menu_openacademy_root"
sequence="10"/>
<menuitem id="menu_openacademy_student_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_student_view"
sequence="10"/>
<menuitem id="menu_openacademy_teacher_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_teacher_view"
sequence="20"/>
<menuitem id="menu_openacademy_studentclass_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_studentclass_view"
sequence="30"/>
<menuitem id="menu_openacademy_score_categ"
name="成績紀錄"
parent="menu_openacademy_root"
sequence="20"/>
<menuitem id="menu_openacademy_score_view"
name="" parent="menu_openacademy_score_categ"
action="action_openacademy_score_view"
sequence="10"/>
</data>
</odoo>
Openacademy模組範例三-接續前例,學習模型繼承、Many2many字段、filter
l 在models資料夾內創建openacademy_course.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademycourse(models.Model):
_name = 'openacademy.course'
course_no = fields.Char(string = '課程編號')
course_name = fields.Char(string = '課程名稱')
course_type = fields.Selection([('1', '必修'), ('2', '選修')], string = '課程型態')
@api.multi
def name_get(self):
result = []
for rec in self:
if rec.course_type == '1':
myclass = '必修'
elif rec.course_type == '2':
myclass = '選修'
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademycourse(models.Model):
_name = 'openacademy.course'
course_no = fields.Char(string = '課程編號')
course_name = fields.Char(string = '課程名稱')
course_type = fields.Selection([('1', '必修'), ('2', '選修')], string = '課程型態')
@api.multi
def name_get(self):
result = []
for rec in self:
if rec.course_type == '1':
myclass = '必修'
elif rec.course_type == '2':
myclass = '選修'
myname = '[%s]%s-%s' % (rec.course_no, rec.course_name, myclass)
result.append((rec.id, myname))
return result
result.append((rec.id, myname))
return result
l 在models資料夾內創建openacademy_student_inherit.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademystudentinherit(models.Model):
_inherit = 'openacademy.student'
# 繼承若創建欄位,會創建在被繼承資料表欄位的後方
# Many2many字段並不會在資料庫產生欄位、儲存資料
# 多選題的情況使用Many2many字段,選過的選項不能再選
# Many2many字段屬性說明:
# comodel-設置與本模型關聯的另一個模型的名稱,此例宣告參考的資料表為openacademy.course
# relation-設置自己命名的關聯表的名稱,此例宣告產生的資料表為openacademy_student_course_rel,用於記錄兩個資料表的關聯
# column1-定義了儲存本模型關聯字段的名稱,此例宣告產生的欄位為student_id,對應openacademy.student
# column2-定義了儲存關聯模型關聯字段的名稱,此例宣告產生的欄位為course_id,對應openacademy.course
# string-是在前端介面看到的名稱
student_course = fields.Many2many('openacademy.course', 'openacademy_student_course_rel', 'student_id', 'course_id', string = '選課內容')
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
class openacademystudentinherit(models.Model):
_inherit = 'openacademy.student'
# 繼承若創建欄位,會創建在被繼承資料表欄位的後方
# Many2many字段並不會在資料庫產生欄位、儲存資料
# 多選題的情況使用Many2many字段,選過的選項不能再選
# Many2many字段屬性說明:
# comodel-設置與本模型關聯的另一個模型的名稱,此例宣告參考的資料表為openacademy.course
# relation-設置自己命名的關聯表的名稱,此例宣告產生的資料表為openacademy_student_course_rel,用於記錄兩個資料表的關聯
# column1-定義了儲存本模型關聯字段的名稱,此例宣告產生的欄位為student_id,對應openacademy.student
# column2-定義了儲存關聯模型關聯字段的名稱,此例宣告產生的欄位為course_id,對應openacademy.course
# string-是在前端介面看到的名稱
student_course = fields.Many2many('openacademy.course', 'openacademy_student_course_rel', 'student_id', 'course_id', string = '選課內容')
l 在views資料夾內創建openacademy_course.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_course_tree" model="ir.ui.view">
<field name="name">view.openacademy.course.tree</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<tree string="">
<field name="course_no"/>
<field name="course_name"/>
<field name="course_type"/>
</tree>
</field>
</record>
<record id="view_openacademy_course_form" model="ir.ui.view">
<field name="name">view.openacademy.course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="course_no"/>
<field name="course_name"/>
<field name="course_type"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_course_view" model="ir.actions.act_window">
<field name="name">課程基本資料</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.course</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_openacademy_course_tree" model="ir.ui.view">
<field name="name">view.openacademy.course.tree</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<tree string="">
<field name="course_no"/>
<field name="course_name"/>
<field name="course_type"/>
</tree>
</field>
</record>
<record id="view_openacademy_course_form" model="ir.ui.view">
<field name="name">view.openacademy.course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="course_no"/>
<field name="course_name"/>
<field name="course_type"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_openacademy_course_view" model="ir.actions.act_window">
<field name="name">課程基本資料</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">openacademy.course</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create"></p>
<p></p>
</field>
</record>
</data>
</odoo>
l 在views資料夾內創建openacademy_student_inherit.xml;
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_data再按Tab鍵、
輸入odoo_form_inherit再按Tab鍵:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_inherit_openacademy_student_form" model="ir.ui.view">
<field name="name">view.inherit.openacademy.student.form</field>
<field name="model">openacademy.student</field>
<!--點選Settings、Activate the developer mode,到欲新增欄位的頁面,點選右上角Open Developer Tools、Edit View、External ID,將External ID複製並貼至ref-->
<!--另一種寫法為<field name="inherit_id" eval="ref('openacademy.view_openacademy_student_form')"/>-->
<field name="inherit_id" ref="openacademy.view_openacademy_student_form"/>
<field name="arch" type="xml">
<!--欲新增欄位的位置-->
<xpath expr="//field[@name='student_fm']" position="after">
<field name="student_course" widget="many2many_tags"/>
</xpath>
</field>
</record>
</data>
</odoo>
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_data再按Tab鍵、
輸入odoo_form_inherit再按Tab鍵:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_inherit_openacademy_student_form" model="ir.ui.view">
<field name="name">view.inherit.openacademy.student.form</field>
<field name="model">openacademy.student</field>
<!--點選Settings、Activate the developer mode,到欲新增欄位的頁面,點選右上角Open Developer Tools、Edit View、External ID,將External ID複製並貼至ref-->
<!--另一種寫法為<field name="inherit_id" eval="ref('openacademy.view_openacademy_student_form')"/>-->
<field name="inherit_id" ref="openacademy.view_openacademy_student_form"/>
<field name="arch" type="xml">
<!--欲新增欄位的位置-->
<xpath expr="//field[@name='student_fm']" position="after">
<field name="student_course" widget="many2many_tags"/>
</xpath>
</field>
</record>
</data>
</odoo>
l 關於模型繼承,新增One2many字段的欄位的寫法:
<xpath expr="//field[@name='studentclass_line']/tree/field[@name='student_class']" position="after">
<field name="student_birthday"/>
</xpath>
<xpath expr="//field[@name='studentclass_line']/tree/field[@name='student_class']" position="after">
<field name="student_birthday"/>
</xpath>
l 關於模型繼承,除了新增欄位,還可以隱藏欄位:
<!-- position: after|before|replace|attributes|inside -->
<!--抹去欄位方法-->
<xpath expr="//field[@name='student_memo']" position="replace">
</xpath>
<!--隱藏欄位方法一,1代表True-->
<xpath expr="//field[@name='student_memo']" position="attributes">
<attribute name = "attrs">{'invisible': 1}</attribute>
</xpath>
<!--隱藏欄位方法二-->
<xpath expr="//field[@name='student_memo']" position="attributes">
<attribute name = "invisible">1</attribute>
</xpath>
<!-- position: after|before|replace|attributes|inside -->
<!--抹去欄位方法-->
<xpath expr="//field[@name='student_memo']" position="replace">
</xpath>
<!--隱藏欄位方法一,1代表True-->
<xpath expr="//field[@name='student_memo']" position="attributes">
<attribute name = "attrs">{'invisible': 1}</attribute>
</xpath>
<!--隱藏欄位方法二-->
<xpath expr="//field[@name='student_memo']" position="attributes">
<attribute name = "invisible">1</attribute>
</xpath>
l 在views資料夾內修改openacademy_menu.xml:
<!--原程式碼增加以下程式碼-->
<menuitem id="menu_openacademy_course_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_course_view"
sequence="40"/>
<!--原程式碼增加以下程式碼-->
<menuitem id="menu_openacademy_course_view"
name="" parent="menu_openacademy_categ"
action="action_openacademy_course_view"
sequence="40"/>
l 學習filter,在views資料夾內修改openacademy_student.xml;
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_search再按Tab鍵:
<record id="search_openacademy_student_view" model="ir.ui.view">
<field name="name">search.openacademy.student.view</field>
<field name="model">openacademy.student</field>
<field name="arch" type="xml">
<search>
<!--Domain Operator說明-->
<!--要比較幾次,就需要幾個條件運算子'|', '|'以中間為中心做巢狀比較,沒設定時是'&'-->
<!--'ilike'代表模糊搜尋,self代表使用者輸入的字串-->
<!--顯示查詢field name,實際查詢filter_domain-->
<!--Domain Operator介紹-->
<!--<, >, <=, >=, =, !=, in, not in, &(AND), |(OR), !(NOT)-->
<!--=like匹配模式,_匹配任意單一字元,%匹配任意字串-->
<!--like匹配%value%模式-->
<!--ilike匹配不區分大小寫-->
<!--child of層級模型時才可使用,用以發現子節點的值-->
<field name="student_name" filter_domain="['|', '|', ('student_name', 'ilike', self), ('student_no', 'ilike', self), ('student_fm', 'ilike', self)]"/>
</search>
</field>
</record>
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_search再按Tab鍵:
<record id="search_openacademy_student_view" model="ir.ui.view">
<field name="name">search.openacademy.student.view</field>
<field name="model">openacademy.student</field>
<field name="arch" type="xml">
<search>
<!--Domain Operator說明-->
<!--要比較幾次,就需要幾個條件運算子'|', '|'以中間為中心做巢狀比較,沒設定時是'&'-->
<!--'ilike'代表模糊搜尋,self代表使用者輸入的字串-->
<!--顯示查詢field name,實際查詢filter_domain-->
<!--Domain Operator介紹-->
<!--<, >, <=, >=, =, !=, in, not in, &(AND), |(OR), !(NOT)-->
<!--=like匹配模式,_匹配任意單一字元,%匹配任意字串-->
<!--like匹配%value%模式-->
<!--ilike匹配不區分大小寫-->
<!--child of層級模型時才可使用,用以發現子節點的值-->
<field name="student_name" filter_domain="['|', '|', ('student_name', 'ilike', self), ('student_no', 'ilike', self), ('student_fm', 'ilike', self)]"/>
</search>
</field>
</record>
Odoo模型繼承的機制
l 經典繼承:
上例Openacademy模組範例三即為經典繼承。
上例Openacademy模組範例三即為經典繼承。
l 原型繼承:
如果在使用模型屬性_inherit的時候,還在子類別中使用_name屬性並且賦予一個與父類別不同的值,那麼我們將會得到一個具有新資料表的新模型;在使用原型繼承的時候,經常會發現其繼承自一個常規類別、一個抽象類別,其中抽象類別是為混和類別(Mixin Classes)所使用的,範例如下:
_inherit = ['bm.bug', 'mail.thread']
_name = 'bm.bug2'
如果在使用模型屬性_inherit的時候,還在子類別中使用_name屬性並且賦予一個與父類別不同的值,那麼我們將會得到一個具有新資料表的新模型;在使用原型繼承的時候,經常會發現其繼承自一個常規類別、一個抽象類別,其中抽象類別是為混和類別(Mixin Classes)所使用的,範例如下:
_inherit = ['bm.bug', 'mail.thread']
_name = 'bm.bug2'
l 委託繼承:
使用模型屬性_inherits來完成,紀錄會儲存在兩個地方,父類別模型的字段儲存在與父類別模型對應的資料表中,而新模型新增的字段則會儲存在新模型資料表中,範例如下(以Odoo原始碼中的res.users模型為例):
from odoo import fields, models
class User(models.Model):
_name = 'res.users'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner')
使用模型屬性_inherits來完成,紀錄會儲存在兩個地方,父類別模型的字段儲存在與父類別模型對應的資料表中,而新模型新增的字段則會儲存在新模型資料表中,範例如下(以Odoo原始碼中的res.users模型為例):
from odoo import fields, models
class User(models.Model):
_name = 'res.users'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner')
讚!
回覆刪除