實(shí)戰(zhàn):模型與視圖繼承機(jī)制詳解與避坑指南)
1. 從一個(gè)真實(shí)的業(yè)務(wù)需求說(shuō)起為什么我們總在“改”O(jiān)doo最近在給一個(gè)客戶做Odoo的二次開發(fā)他們提了一個(gè)很典型的需求現(xiàn)有的銷售訂單sale.order表單上客戶希望增加一個(gè)“項(xiàng)目緊急程度”的字段并且根據(jù)這個(gè)字段的值自動(dòng)高亮顯示訂單行。聽(tīng)起來(lái)很簡(jiǎn)單對(duì)吧但如果你直接去修改Odoo標(biāo)準(zhǔn)模塊sale里的views/sale_order_views.xml文件那就踩進(jìn)了第一個(gè)大坑。下次Odoo版本升級(jí)你的修改會(huì)被無(wú)情地覆蓋所有定制化工作付諸東流。這就是Odoo開發(fā)中永恒的核心命題如何在不動(dòng)原模塊“一磚一瓦”的前提下實(shí)現(xiàn)功能的擴(kuò)展、修改甚至重寫答案就是“繼承”Inheritance。Odoo的繼承機(jī)制是其模塊化架構(gòu)的基石它允許你像搭積木一樣在現(xiàn)有功能之上構(gòu)建新的功能而無(wú)需修改底層代碼。這不僅關(guān)乎代碼的整潔更關(guān)乎項(xiàng)目未來(lái)的可維護(hù)性和升級(jí)的平滑性。今天我們就拋開那些抽象的概念直接深入到代碼和視圖層面手把手拆解Odoo的繼承與擴(kuò)展。我會(huì)結(jié)合我這些年趟過(guò)的坑告訴你什么時(shí)候該用哪種繼承方式視圖繼承的xpath到底怎么寫才不報(bào)錯(cuò)以及如何讓你的新模塊既干凈又強(qiáng)大。2. 理解Odoo繼承的“道”與“術(shù)”模型、字段與方法的擴(kuò)展在動(dòng)手寫代碼之前我們必須先理解Odoo繼承的幾種類型。這就像木匠的工具箱你知道什么時(shí)候該用鋸子什么時(shí)候該用刨子。2.1 類繼承Classical Inheritance最直接的“是什么”類繼承也叫_inherit用于擴(kuò)展或修改一個(gè)現(xiàn)有的模型。你創(chuàng)建的新模塊模型直接聲明繼承自某個(gè)已存在的模型。這是最常用的一種。核心場(chǎng)景為現(xiàn)有模型添加新字段、覆蓋現(xiàn)有方法、添加新的約束或計(jì)算字段。讓我們用代碼說(shuō)話。假設(shè)我們要給標(biāo)準(zhǔn)的res.partner客戶/供應(yīng)商模型加一個(gè)“客戶等級(jí)”字段。錯(cuò)誤的做法直接修改原模塊找到odoo/addons/base/models/res_partner.py就開改。這是自殺式行為。正確的做法創(chuàng)建新模塊新建一個(gè)模塊目錄例如my_partner_extension。創(chuàng)建模型文件models/partner.py# models/partner.py from odoo import models, fields, api class ResPartner(models.Model): # 關(guān)鍵在這里_inherit 指定了要繼承的原始模型 _inherit res.partner # 添加新字段 customer_rank fields.Selection( selection[(basic, 普通), (vip, VIP), (vvip, 尊享VIP)], string客戶等級(jí), defaultbasic ) # 覆蓋重寫父類的方法 api.model def create(self, vals): # 在創(chuàng)建前做一些事情例如自動(dòng)根據(jù)公司名生成客戶等級(jí)邏輯示例 if vals.get(name) and 科技 in vals.get(name): vals[customer_rank] vip # 必須調(diào)用super()來(lái)執(zhí)行原始的邏輯 return super(ResPartner, self).create(vals) # 添加一個(gè)新的方法 def send_vip_greeting(self): self.ensure_one() # 發(fā)送VIP問(wèn)候郵件的邏輯 # ... return True關(guān)鍵點(diǎn)解析_inherit ‘res.partner’這行代碼告訴Odoo我這個(gè)ResPartner類不是全新的它是在原有res.partner模型基礎(chǔ)上的擴(kuò)展。Odoo會(huì)在運(yùn)行時(shí)將兩個(gè)類合并。super()的調(diào)用在重寫方法時(shí)幾乎總是需要調(diào)用super()。除非你的意圖是完全取代原方法的行為。不調(diào)用super()會(huì)導(dǎo)致原始邏輯丟失引發(fā)各種詭異問(wèn)題。字段添加直接像在普通模型中一樣定義字段即可Odoo會(huì)自動(dòng)將它們合并到原模型中。2.2 原型繼承Prototypal Inheritance創(chuàng)建一個(gè)“變種”原型繼承使用_inherit和_name的組合。它基于一個(gè)現(xiàn)有模型創(chuàng)建一個(gè)全新的模型。新模型擁有父模型的所有字段和方法但它們?cè)跀?shù)據(jù)庫(kù)中是兩個(gè)獨(dú)立的表。核心場(chǎng)景你需要一個(gè)和現(xiàn)有模型高度相似但又是獨(dú)立實(shí)體的模型。例如從product.template產(chǎn)品模板繼承出service.template服務(wù)模板。# models/service.py from odoo import models, fields class ServiceTemplate(models.Model): _name service.template # 新模型的唯一標(biāo)識(shí) _inherit product.template # 繼承自產(chǎn)品模板 _description 服務(wù)模板 # 可以添加服務(wù)特有的字段 service_duration fields.Float(string服務(wù)時(shí)長(zhǎng)小時(shí)) is_online_service fields.Boolean(string在線服務(wù)) # 可以覆蓋繼承來(lái)的字段屬性 # 例如所有服務(wù)類型的“產(chǎn)品類型”固定為‘service’ type fields.Selection(selection_add[(service, 服務(wù))], ondelete{service: set default})關(guān)鍵點(diǎn)解析_name和_inherit同時(shí)存在這告訴Odoo創(chuàng)建一個(gè)名為service.template的新模型并以product.template為藍(lán)本。獨(dú)立表數(shù)據(jù)庫(kù)中會(huì)有一張名為service_template的表它包含了product.template的所有字段通過(guò)Odoo的機(jī)制映射以及自己新增的字段。使用場(chǎng)景更特定當(dāng)你需要邏輯上的嚴(yán)格區(qū)分時(shí)使用。比如你不希望服務(wù)和實(shí)物產(chǎn)品在列表視圖、菜單或業(yè)務(wù)規(guī)則上混在一起。2.3 委托繼承Delegation Inheritance “我有一個(gè)…”委托繼承使用_inherits屬性。它實(shí)現(xiàn)的是對(duì)象組合“has-a”關(guān)系而非類繼承“is-a”。子模型實(shí)例“擁有”一個(gè)父模型實(shí)例并通過(guò)委托來(lái)訪問(wèn)父模型的字段。核心場(chǎng)景擴(kuò)展現(xiàn)有模型但希望保持?jǐn)?shù)據(jù)的獨(dú)立性。最經(jīng)典的例子是res.users對(duì)res.partner的繼承。每個(gè)用戶User都是一個(gè)伙伴Partner但用戶有自己額外的信息。# 這是一個(gè)概念示例Odoo標(biāo)準(zhǔn)模塊已實(shí)現(xiàn) # models/extended_user.py from odoo import models, fields class ExtendedUser(models.Model): _name extended.user _inherits {res.partner: partner_id} # 委托繼承 partner_id fields.Many2one(res.partner, string關(guān)聯(lián)伙伴, requiredTrue, ondeletecascade) # 添加用戶特有的字段 internal_phone fields.Char(string內(nèi)部分機(jī)號(hào)) department fields.Char(string部門)關(guān)鍵點(diǎn)解析_inherits是一個(gè)字典{‘父模型名’: ‘子模型中用于鏈接的Many2one字段名’}。數(shù)據(jù)存儲(chǔ)當(dāng)創(chuàng)建一個(gè)extended.user記錄時(shí)Odoo會(huì)同時(shí)創(chuàng)建一條res.partner記錄。extended.user記錄只存儲(chǔ)自己的字段和指向res.partner記錄的partner_id。字段訪問(wèn)你可以直接通過(guò)extended_user_record.name訪問(wèn)伙伴的姓名Odoo會(huì)自動(dòng)通過(guò)委托機(jī)制從關(guān)聯(lián)的res.partner記錄中獲取。何時(shí)使用當(dāng)你需要復(fù)用另一個(gè)模型的完整功能包括其所有視圖、權(quán)限、業(yè)務(wù)邏輯但又需要保持?jǐn)?shù)據(jù)實(shí)體分離時(shí)。不如類繼承常用但理解它有助于讀懂Odoo標(biāo)準(zhǔn)代碼。實(shí)操心得選擇繼承類型的“直覺(jué)”90%的情況下你用的是類繼承_inherit。當(dāng)你只是想給現(xiàn)有模型加點(diǎn)東西或改點(diǎn)東西時(shí)就用它。 當(dāng)你覺(jué)得“我需要一個(gè)和XX很像但完全是另一個(gè)東西”的時(shí)候考慮原型繼承_name_inherit。 委托繼承_inherits在標(biāo)準(zhǔn)模塊中很常見(jiàn)但在自定義開發(fā)中較少除非你在設(shè)計(jì)一個(gè)非常復(fù)雜的模型關(guān)系。拿不準(zhǔn)時(shí)先用類繼承。3. 視圖繼承的實(shí)戰(zhàn)精準(zhǔn)定位與優(yōu)雅修改模型繼承搞定了數(shù)據(jù)和邏輯但用戶是通過(guò)界面視圖來(lái)交互的。視圖繼承讓你可以修改任何現(xiàn)有視圖而無(wú)需復(fù)制整個(gè)視圖文件。Odoo的視圖繼承核心是inherit標(biāo)簽和xpath表達(dá)式。xpath是一種用于在XML中定位節(jié)點(diǎn)的查詢語(yǔ)言雖然聽(tīng)起來(lái)有點(diǎn)技術(shù)性但用起來(lái)就像“地圖坐標(biāo)”。3.1 視圖繼承的基本結(jié)構(gòu)首先在你的新模塊中創(chuàng)建視圖文件例如views/partner_view.xml。?xml version1.0 encodingutf-8? odoo data !-- 繼承 res.partner 的表單視圖 -- record idview_partner_form_inherit modelir.ui.view field namenameres.partner.form.inherit.my.module/field field namemodelres.partner/field field nameinherit_id refbase.view_partner_form/ !-- 關(guān)鍵指定繼承哪個(gè)視圖 -- field namearch typexml !-- 在這里使用 xpath 進(jìn)行修改 -- xpath expr//field[namename] positionafter field namecustomer_rank widgetradio/ /xpath !-- 更常見(jiàn)的簡(jiǎn)寫語(yǔ)法 -- field nameemail positionafter field nameinternal_phone/ /field !-- 在表單最底部添加一個(gè)新分組頁(yè)簽 -- xpath expr//sheet positioninside div classoe_button_box namebutton_box !-- 可以在這里添加按鈕 -- /div footer button namesend_vip_greeting string發(fā)送VIP問(wèn)候 typeobject classbtn-primary/ /footer /xpath /field /record /data /odoo關(guān)鍵點(diǎn)解析inherit_id通過(guò)ref屬性指向你要繼承的原始視圖的XML ID。這是視圖繼承的“錨點(diǎn)”。arch字段這里包含了所有你對(duì)原始視圖結(jié)構(gòu)的修改指令。xpathvs 簡(jiǎn)寫xpath expr”…”功能最強(qiáng)大可以定位到任何節(jié)點(diǎn)。//表示在整個(gè)文檔中查找[name‘xxx’]是屬性選擇器。field name”email” position”after”這是最常見(jiàn)的簡(jiǎn)寫。Odoo會(huì)將其解釋為xpath expr”//field[name’email’]”。僅當(dāng)目標(biāo)節(jié)點(diǎn)有唯一的name屬性時(shí)才適用。3.2position屬性的五種武器position屬性告訴Odoo找到節(jié)點(diǎn)后你想怎么“處置”它。這是視圖繼承的靈魂。inside默認(rèn)將內(nèi)容插入到目標(biāo)節(jié)點(diǎn)的內(nèi)部末尾。xpath expr//div[classoe_button_box] positioninside button namemy_action string自定義動(dòng)作/ /xpath用途向一個(gè)容器如group、div、sheet內(nèi)添加新元素。after將內(nèi)容插入到目標(biāo)節(jié)點(diǎn)之后作為兄弟節(jié)點(diǎn)。field namephone positionafter field namemobile/ /field用途在某個(gè)字段后面添加新字段。最常用。before將內(nèi)容插入到目標(biāo)節(jié)點(diǎn)之前。field namestreet positionbefore label forcountry_id string國(guó)家/ field namecountry_id/ /xpath用途在某個(gè)字段前面添加內(nèi)容。replace替換整個(gè)目標(biāo)節(jié)點(diǎn)。小心使用field namewebsite positionreplace field namewebsite readonly1/ !-- 將網(wǎng)站字段改為只讀 -- /field用途修改一個(gè)現(xiàn)有元素的屬性或者完全替換一個(gè)復(fù)雜的結(jié)構(gòu)。注意替換時(shí)新節(jié)點(diǎn)通常需要保持相同的核心屬性如name。move將目標(biāo)節(jié)點(diǎn)移動(dòng)到另一個(gè)xpath表達(dá)式定位的位置。xpath expr//field[namechild_ids] positionmove xpath expr//field[namecategory_id] positionafter/ /xpath用途調(diào)整界面元素的順序。比較進(jìn)階但非常強(qiáng)大。踩坑實(shí)錄xpath定位失敗的那些事兒視圖繼承90%的錯(cuò)誤來(lái)自于xpath寫錯(cuò)了找不到節(jié)點(diǎn)???name屬性不唯一。原視圖中有兩個(gè)field name”date”一個(gè)在抬頭一個(gè)在行內(nèi)。你的簡(jiǎn)寫field name”date” position”after”會(huì)作用于第一個(gè)可能不是你想要的。務(wù)必使用更精確的xpath例如//field[name‘date’ and ancestor::div[class‘oe_title’]]。坑2視圖結(jié)構(gòu)因模塊加載順序改變。模塊A修改了視圖模塊B又基于A修改后的視圖做繼承。如果B在A之前加載B的繼承就會(huì)失敗。解決方案在模塊的__manifest__.py中用‘depends’聲明依賴關(guān)系確保加載順序。坑3替換replace時(shí)改變了關(guān)鍵結(jié)構(gòu)。比如你把一個(gè)tree視圖的editable屬性去掉了但模型層沒(méi)有相應(yīng)調(diào)整可能導(dǎo)致界面錯(cuò)誤。替換前最好先看看原節(jié)點(diǎn)的完整結(jié)構(gòu)。3.3 繼承列表視圖Tree和搜索視圖Search原理和表單視圖一模一樣只是定位的目標(biāo)不同。繼承列表視圖添加一列record idview_partner_tree_inherit modelir.ui.view field nameinherit_id refbase.view_partner_tree/ field namearch typexml xpath expr//field[namephone] positionafter field namecustomer_rank/ /xpath /field /record繼承搜索視圖添加篩選條件record idview_partner_filter_inherit modelir.ui.view field nameinherit_id refbase.view_res_partner_filter/ field namearch typexml !-- 在搜索框的篩選條件區(qū)域添加 -- xpath expr//filter[namecompany] positionafter filter namefilter_by_rank stringVIP客戶 domain[(customer_rank, , vip)]/ /xpath !-- 在搜索框的搜索字段區(qū)域添加 -- field nameemail positionafter field namecustomer_rank/ /field /field /record4. 構(gòu)建一個(gè)完整的新模塊從理論到實(shí)踐理解了繼承的“零件”后我們來(lái)組裝一輛“車”。我們將創(chuàng)建一個(gè)完整的模塊my_partner_extension實(shí)現(xiàn)前面提到的所有功能。4.1 模塊結(jié)構(gòu)my_partner_extension/ ├── __init__.py ├── __manifest__.py ├── models/ │ ├── __init__.py │ └── partner.py # 包含我們擴(kuò)展的 ResPartner 類 └── views/ └── partner_view.xml # 包含所有視圖繼承的定義4.2 關(guān)鍵文件詳解__manifest__.py模塊的“身份證”和“說(shuō)明書”。{ name: 客戶擴(kuò)展模塊, version: 16.0.1.0.0, category: Sales, summary: 為合作伙伴模型添加客戶等級(jí)和自定義功能, description: 本模塊擴(kuò)展了Odoo標(biāo)準(zhǔn)的合作伙伴(res.partner)模型。 功能包括 - 添加客戶等級(jí)字段普通/VIP/尊享VIP - 在銷售訂單等相關(guān)表單中顯示該字段 - 提供發(fā)送VIP問(wèn)候郵件的功能 , author: 你的名字/公司, website: https://www.yourwebsite.com, depends: [base, sale], # 關(guān)鍵聲明依賴確保在base和sale模塊之后加載 data: [ views/partner_view.xml, # 聲明視圖文件 ], demo: [], installable: True, application: False, auto_install: False, license: LGPL-3, }depends至關(guān)重要。這里聲明了本模塊正常運(yùn)行所依賴的其他模塊。Odoo會(huì)根據(jù)這個(gè)順序加載模塊。因?yàn)槲覀兝^承了sale模塊的視圖所以必須依賴它。models/__init__.pyfrom . import partnermodels/partner.py內(nèi)容同2.1節(jié)略views/partner_view.xml綜合示例?xml version1.0 encodingutf-8? odoo data !-- 繼承合作伙伴表單視圖 -- record idview_partner_form_inherit modelir.ui.view field nameinherit_id refbase.view_partner_form/ field namearch typexml !-- 在“名稱”字段后添加“客戶等級(jí)”單選框 -- field namename positionafter field namecustomer_rank widgetradio/ /field !-- 在“電話”字段后添加“內(nèi)部分機(jī)號(hào)” -- field namephone positionafter field nameinternal_phone/ /field !-- 在表單底部添加一個(gè)自定義按鈕 -- xpath expr//sheet positionbefore div classoe_button_box namebutton_box button namesend_vip_greeting string發(fā)送問(wèn)候 typeobject classoe_stat_button iconfa-envelope field namecustomer_rank widgetstatinfo string等級(jí)/ /button /div /xpath /field /record !-- 繼承合作伙伴列表視圖 -- record idview_partner_tree_inherit modelir.ui.view field nameinherit_id refbase.view_partner_tree/ field namearch typexml field namephone positionafter field namecustomer_rank/ /field /field /record !-- 繼承合作伙伴搜索視圖 -- record idview_partner_filter_inherit modelir.ui.view field nameinherit_id refbase.view_res_partner_filter/ field namearch typexml xpath expr//filter[nameactive] positionafter filter namefilter_vip stringVIP客戶 domain[(customer_rank,,vip)]/ filter namefilter_vvip string尊享VIP domain[(customer_rank,,vvip)]/ /xpath field namephone positionafter field namecustomer_rank filter_domain[(customer_rank,ilike,self)]/ /field /field /record !-- 繼承銷售訂單表單視圖將客戶等級(jí)字段顯示在客戶信息附近 -- record idview_sale_order_form_inherit modelir.ui.view field nameinherit_id refsale.view_order_form/ field namearch typexml !-- 定位到銷售訂單的客戶信息區(qū)域 -- xpath expr//div[namepartner_shipping_id]/.. positionbefore label forpartner_id_customer_rank string客戶等級(jí)/ field namepartner_id.customer_rank readonly1 classoe_inline/ /xpath /field /record /data /odoo4.3 模塊的安裝與調(diào)試放置模塊將my_partner_extension文件夾放到Odoo的插件路徑下通常是addons/目錄。更新應(yīng)用列表在Odoo開發(fā)者模式下進(jìn)入“應(yīng)用” - “更新應(yīng)用列表”。搜索并安裝搜索“客戶擴(kuò)展模塊”并安裝。調(diào)試視圖如果視圖沒(méi)有按預(yù)期顯示進(jìn)入開發(fā)者模式?debug1然后在表單視圖上點(diǎn)擊“調(diào)試圖標(biāo)小蟲子” - “編輯視圖表單”。這會(huì)打開視圖結(jié)構(gòu)編輯器你可以看到最終渲染的視圖XML檢查你的xpath是否生效定位是否準(zhǔn)確。查看日志。Odoo服務(wù)端日志通常終端或日志文件會(huì)詳細(xì)記錄視圖加載時(shí)的錯(cuò)誤如xpath找不到節(jié)點(diǎn)。5. 進(jìn)階技巧與避坑指南掌握了基礎(chǔ)我們來(lái)看看那些能讓你的開發(fā)更高效、更穩(wěn)健的進(jìn)階知識(shí)。5.1 使用attrs屬性實(shí)現(xiàn)條件顯示/必填/只讀這是Odoo視圖中最強(qiáng)大的動(dòng)態(tài)特性之一。你可以讓一個(gè)字段的可見(jiàn)性、是否必填、是否只讀取決于另一個(gè)字段的值。field nameinternal_phone attrs{invisible: [(customer_rank, !, vip)], required: [(customer_rank, , vvip)]}/invisible當(dāng)customer_rank不是vip時(shí)該字段隱藏。required當(dāng)customer_rank是vvip時(shí)該字段必填。還可以用readonly。避坑點(diǎn)attrs中的域domain表達(dá)式其左值必須是當(dāng)前視圖所在模型的字段。如果你需要根據(jù)關(guān)聯(lián)模型的字段來(lái)控制通常需要在當(dāng)前模型中創(chuàng)建一個(gè)相關(guān)的計(jì)算字段related字段。5.2 繼承并修改ir.actions.act_window上下文或域有時(shí)你不僅想改視圖還想改打開這個(gè)視圖的“動(dòng)作”行為比如默認(rèn)的篩選條件。!-- 修改“客戶”菜單動(dòng)作默認(rèn)只顯示VIP客戶 -- record idaction_partner_form_inherit modelir.actions.act_window field namename客戶/field field nameres_modelres.partner/field field nameinherit_id refbase.action_partner_form/ field namecontext{search_default_filter_vip: 1}/field !-- 默認(rèn)啟用名為filter_vip的篩選器 -- !-- 或者使用 domain -- !-- field namedomain[(customer_rank, in, [vip, vvip])]/field -- /record5.3 處理多模塊繼承沖突當(dāng)多個(gè)模塊試圖繼承并修改同一個(gè)視圖的同一位置時(shí)會(huì)發(fā)生沖突。Odoo通過(guò)視圖的priority字段和模塊加載順序來(lái)決定誰(shuí)“勝出”。priority值越高優(yōu)先級(jí)越高。最佳實(shí)踐盡量避免直接競(jìng)爭(zhēng)。如果必須修改同一節(jié)點(diǎn)考慮通過(guò)更精確的xpath定位到不同子節(jié)點(diǎn)或者在你的模塊中創(chuàng)建一個(gè)更高優(yōu)先級(jí)的視圖。record idview_partner_form_inherit_high_priority modelir.ui.view field namepriority20/field !-- 默認(rèn)是16更高的值后加載會(huì)覆蓋先加載的 -- ... 其余繼承定義 ... /record5.4 模型繼承中的api.model與api.model_create_multi在重寫create方法時(shí)Odoo 13之后推薦使用api.model_create_multi裝飾器以支持批量創(chuàng)建但內(nèi)部邏輯要處理好。api.model_create_multi def create(self, vals_list): for vals in vals_list: # 你的預(yù)處理邏輯 if vals.get(name): vals.setdefault(customer_rank, basic) # 務(wù)必調(diào)用super return super(ResPartner, self).create(vals_list)5.5 視圖繼承的“核武器”直接替換整個(gè)視圖在極少數(shù)情況下原有視圖結(jié)構(gòu)過(guò)于復(fù)雜或不適合你的需求你可以選擇不繼承而是直接定義一個(gè)新的視圖并讓菜單動(dòng)作指向它。這相當(dāng)于放棄了繼承的優(yōu)雅換來(lái)了完全的控制權(quán)。不到萬(wàn)不得已不要用這招。!-- 1. 定義一個(gè)全新的視圖 -- record idview_partner_form_custom modelir.ui.view field namenameres.partner.form.custom/field field namemodelres.partner/field field namearch typexml form !-- 完全自定義的布局 -- /form /field /record !-- 2. 修改或創(chuàng)建一個(gè)動(dòng)作使用這個(gè)新視圖 -- record idaction_partner_custom modelir.actions.act_window field namename客戶自定義視圖/field field nameres_modelres.partner/field field nameview_modetree,form/field field nameview_id refview_partner_form_custom/ !-- 指定默認(rèn)表單視圖 -- ... /recordOdoo的繼承機(jī)制是其作為強(qiáng)大ERP框架的靈活性所在。它迫使開發(fā)者以一種可維護(hù)、可升級(jí)的方式進(jìn)行定制。核心思想永遠(yuǎn)是通過(guò)創(chuàng)建新的、獨(dú)立的模塊來(lái)擴(kuò)展而非修改原有模塊。從模型到視圖這條原則一以貫之。剛開始接觸xpath和繼承語(yǔ)法可能會(huì)覺(jué)得繁瑣但一旦掌握你會(huì)發(fā)現(xiàn)它是應(yīng)對(duì)千變?nèi)f化業(yè)務(wù)需求的瑞士軍刀。記住多利用開發(fā)者工具查看視圖結(jié)構(gòu)多查看Odoo標(biāo)準(zhǔn)模塊的源碼作為參考這是最快的學(xué)習(xí)路徑。