2020年2月27日 星期四

Odoo教育訓練學習筆記 (3)

  由於轉換職場跑道的緣故,許久沒有撰寫學習筆記或書摘心得等文章,原本自學內容以Python為核心,著重資料科學的技術與應用,但由於新工作的需求,必須轉而學習開放原始碼ERP系統-Odoo的開發,對於僅有類似系統分析經驗的我而言,這是非常難得的機會,可以讓我真正跨入系統工程師的領域。

  由於自己並非資訊相關科系畢業,基礎知識與技能相當貧乏,透過此次學習Odoo系統基礎建置與開發,我才初次接觸了如何在Windows系統透過VM來使用Linux系統,以及終端機指令的操作,來一步一步建置Odoo系統,包括所需的Python套件、PostgreSQL資料庫系統、pgAdmin資料庫管理工具、Odoo系統資料夾結構、Odoo系統設定文件、PyCharm整合開發環境、系統更新與維護方式等等,需要了解與學習的技術非常非常的多,自己也僅學習使用了一小部分的工具與技巧而已。

  系統開發由於牽涉到許多資料夾結構與不同文件如csvpyxml等,因此未來使用Git做為示範檔案存放空間的需求或許也將越來越迫切,現階段仍純粹在網誌整理、發布文章;本系列一共六篇學習筆記,為花費大量時間綜合整理Peter Wu老師課堂教學、《Odoo快速入門與實戰》簡體書,以及零散的網路教學文章而得,前五篇的學習筆記完全操作使用Odoo社群版,第六篇的學習筆記則加入「康虎雲報表」的安裝與教學,可至「康虎軟件工作室」官方網站下載相關軟體,配合學習筆記的說明來安裝使用。

  自學Odoo系統的建置開發相當不容易:在台灣,Odoo系統的知名度與普及性遠不如SAP、鼎新、Oracle等大廠,資訊廠商與開發人員的數量也相對稀少,就連參考書籍也完全沒有繁體中文的版本,僅有少數英文書與簡體書可以購買(而且這些書都寫得很糟),加上這幾年Odoo系統的快速發展,不同的版本之間有不小的變動,學會的技術可能過幾年就完全不能用了…。綜合上述許多原因,即便本系列學習筆記已經整理很清楚了,對於完全零經驗的開發者而言,相信仍舊難以閱讀、理解。

  目前Odoo系統在歐美與中國大陸的資源、社群與討論,都比台灣頻繁、密集得多,想要入門Odoo系統的學習者建議還是需要老師來帶領,才有辦法掌握操作、管理、開發的各項眉眉角角。

Openacademy模組範例二-學習api裝飾器、操作函式、One2many字段
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
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
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
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})
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>
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>
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>
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>
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>

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 = '
選修'
            myname = '[%s]%s-%s' % (rec.course_no, rec.course_name, myclass)
            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 = '
選課內容')
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>
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>
            <!--
點選SettingsActivate the developer mode,到欲新增欄位的頁面,點選右上角Open Developer ToolsEdit ViewExternal 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>
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>
l   views資料夾內修改openacademy_menu.xml
<!--
原程式碼增加以下程式碼-->
<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>

Odoo模型繼承的機制
l   經典繼承:
上例Openacademy模組範例三即為經典繼承。
l   原型繼承:
如果在使用模型屬性_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')

1 則留言: