ODOO13之 八:Odoo 13開發之業務邏輯 – 業務流程的支持


 

在前面的文章中,我們學習了模型層、如何創建應用數據結構以及如何使用 ORM API 來存儲查看數據。本文中我們將利用前面所學的模型和記錄集知識實現應用中常用的業務邏輯模式。

本文的主要內容有:

  • 以單據為中心工作流的階段(stage)
  • onchange方法,與用戶即時交互
  • 使用 ORM 內置方法,如create, write 和 unlink
  • Mail 插件提供的消息和活動功能
  • 創建向導來幫助用戶執行復雜操作
  • 使用日志消息優化系統監測
  • 拋出異常以在出錯時給用戶反饋
  • 使用單元測試來進行代碼質量檢查
  • 開發工具,調試器等開發者工具

開發准備

本文中我們將創建一個依賴於之前文章創建的library_app和library_member模塊的library_checkout插件模塊。這些模塊的代碼請參見 GitHub 倉庫。這兩個插件模塊都應放置在add-ons路徑中(參見命令行–addons-path或~/.odoorc 配置文件中的addons_path),這樣我們才能安裝和使用。本文完成后的代碼請見 GitHub 倉庫。

學習項目 – library_checkout模塊

在前面章節的學習中,我們為圖書應用搭建了主數據結構。現在需要為圖書會員添加借書的功能了。也就是說需要追蹤圖書是否可借以及歸還的記錄。每本書的借閱都有一個生命周期,從圖書登記草稿到圖書被歸還。這是一個可通過看板視圖表示的簡單工作流,看板視圖中每個階段(stage)可展現為一列,工作項和借閱請求流從左側列到右側列,直至完成為止。

在本文中,我們集中學習實現這一功能的數據模型和業務邏輯。用戶界面部分的詳情將在第十章 Odoo 13開發之后台視圖 – 設計用戶界面和第十一章 Odoo 13開發之看板視圖和用戶端 QWeb中討論。

圖書借閱模型包含:

  • 借閱圖書的會員(必填)
  • 借閱請求日期(默認為當天)
  • 負責借閱請求的圖書管理員(默認為當前用戶)
  • 借閱路線,包含請求借閱的一本或多本圖書

要支持並存檔借閱生命周期,需要添加如下內容:

  • 請求的階段:草稿、開放、借出、歸還或取消
  • 借閱日期,圖書借出的日期
  • 關閉日期,圖書歸還的日期

我們將開始創建一個新的模塊library_checkout並實現圖書借閱模型的初始版本。與此前章節相比此處並沒有引入新的知識,用於提供一個基礎供本文后續創建新功能。

在其它圖書插件模塊的同級路徑下創建一個library_checkout目錄:

1、首先添加manifest.py文件並加入如下內容:

{
    'name': 'Library Book Borrowing', 'description': 'Members can borrow books from the library.', 'author': 'Alan Hou', 'depends': ['library_member'], 'data':[ 'security/ir.model.access.csv', 'views/library_menu.xml', 'views/checkout_view.xml', ], } 

2、在模塊目錄下創建init.py文件,並添加如下代碼:

from . import models 

3、創建models/init.py文件並添加:

from . import library_checkout 

4、在models/library_checkout.py中添加如下代碼:

from odoo import api, exceptions, fields, models class Checkout(models.Model): _name = 'library.checkout' _description = 'Checkout Request' member_id = fields.Many2one( 'library.member', required=True) user_id = fields.Many2one( 'res.users', 'Librarian', default=lambda s: s.env.uid) request_date = fields.Date( default=lambda s: fields.Date.today()) line_ids = fields.One2many( 'library.checkout.line', 'checkout_id', string='Borrowed Books',) class CheckoutLine(models.Model): _name = 'library.checkout.line' _description = 'Borrow Request Line' checkout_id = fields.Many2one('library.checkout') book_id = fields.Many2one('library.book') 

下面就要添加數據文件了,添加訪問規則、菜單項和一些基礎視圖,這樣模塊可以最小化的運行起來。

5、添加security/ir.model.access.csv文件:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
checkout_user,Checkout User,model_library_checkout,library_app.library_group_user,1,1,1,0 checkout_line_user,Checkout Line User ,model_library_checkout_line,library_app.library_group_user,1,1,1,1 checkout_manager,Checkout Manager,model_library_checkout,library_app.library_group_manager,1,1,1,1 

6、菜項項通過views/library_menu.xml實現:

<odoo>
    <act_window id="action_library_checkout" name="Checkouts" res_model="library.checkout" view_mode="tree,form" /> <menuitem id="menu_library_checkout" name="Checkout" action="action_library_checkout" parent="library_app.menu_library" /> </odoo> 

7、視圖通過views/checkout_view.xml文件實現:

<?xml version="1.0" ?> <odoo> <record id="view_tree_checkout" model="ir.ui.view"> <field name="name">Checkout Tree</field> <field name="model">library.checkout</field> <field name="arch" type="xml"> <tree> <field name="request_date" /> <field name="member_id" /> </tree> </field> </record> <record id="view_form_checkout" model="ir.ui.view"> <field name="name">Checkout Form</field> <field name="model">library.checkout</field> <field name="arch" type="xml"> <form> <sheet> <group> <field name="member_id" /> <field name="request_date" /> <field name="user_id" /> <field name="line_ids" /> </group> </sheet> </form> </field> </record> </odoo> 

現在就可以在我們的 Odoo 工作數據庫中安裝這個模塊,並准備開始添加更多功能了。

~/odoo-dev/odoo/odoo-bin -d dev13 -i library_checkout 

以文檔為中心工作流的階段(stage)

在 Odoo 中,我們可以實現以文檔(document)為中心的工作流。我們這里說的文檔包括銷售訂單、項目任務或人事申請。所有這些都遵循一個特定的生命周期,它們都在完成時才被創建。它們都被記錄在一個文檔中,按照一系列可能的階段推進,直至完成。

如果把各階段以列展示在面板中,把文檔作為這些列中的工作項,就可以得到一個看板(Kanban),一個快速查看工作進度的視圖。實現這些進度步驟有兩種方法,通常稱為狀態和階段。

  • 狀態通過預定義的閉合選項列表來實現。它便於實現業務規則,並且模型和視圖對 state 字段有特別的支持,根據當前狀態來帶有必填和隱藏屬性集。狀態列表有一個劣勢,就是它是預定義並且閉合的,因此無法按具體流程需求來做調整。

  • 階段通過關聯模型實現,階段列表是開放的,可被配置來滿足當前流程需求。可以輕易地修改引用階段列表:刪除、添加或渲染這些階段。它的劣勢是對流程自動化不可靠,因為階段列表可被修改,自動化規則就無法依賴於具體的階段 ID 或描述。

獲取兩種方法優勢的方式是將階段映射到狀態中。文檔組織到可配置的階段中,然后間接關聯到對於自動化業務邏輯可靠的狀態碼中。我們將在library_checkout/models/library_checkout_stage.py文件中實現library.checkout.stage模型,代碼如下:

from odoo import fields, models class CheckoutStage(models.Model): _name = 'library.checkout.stage' _description = 'Checkout Stage' _order = 'sequence,name' name = fields.Char() sequence = fields.Integer(default=10) fold = fields.Boolean() active = fields.Boolean(default=True) state = fields.Selection( [('new', 'New'), ('open', 'Borrowed'), ('done', 'Returned'), ('cancel', 'Cancelled')], default='new', ) 

這里我們可以看到 state 字段,允許每個階段與四個基本狀態映射。sequence字段很重要,要配置順序,階段應在看板和階段選擇列表中展示。fold 布爾字段是看板用於將一些列默認折疊,這樣其內容就不會馬上顯示出來。折疊通常用於已完成或取消的階段。新的代碼一定不要忘記加入到models/__init__.py文件中,當前內容為:

from . import library_checkout_stage from . import library_checkout 

下一步,我們需要向圖書借閱模型添加階段字段stage。編輯library_checkout/models/library_checkout.py文件,在 Checkout 類的最后面(line_ids 字段后)添加如下代碼:

    @api.model
    def _default_stage(self): Stage = self.env['library.checkout.stage'] return Stage.search([], limit=1) @api.model def _group_expand_stage_id(self, stages, domain, order): return stages.search([], order=order) stage_id = fields.Many2one( 'library.checkout.stage', default=_default_stage, group_expand='_group_expand_stage_id') state = fields.Selection(related='stage_id.state') 

stage_id是一個與階段模型的 many-to-one關聯。我們還添加了 state 字段,這是一個讓階段的 state 字段在當前模型中可用的關聯字段,這樣才能在視圖中使用。階段的默認值由_default_stage() 函數來計算,它返回階段模型的第一條記錄。因為階段模型已通過 sequence 排序,所以返回的是 sequence 值最小的一條記錄。

group_expand參數重載字段的分組方式,默認的分組操作行為是僅能看到使用過的階段,而不帶有借閱文檔的階段不會顯示。在我們的例子中,我們想要不同的效果:我們要看到所有的階段,哪怕它沒有文檔。_group_expand_stage_id() 幫助函數返回分組操作需使用組記錄列表。本例中返回所有已有階段,不論其中是否包含圖書借閱記錄。

ℹ️Odoo 10中的修改 group_expand字段在Odoo 10中引入,但在官方文檔中沒有介紹。使用示例在 Odoo 的源代碼中可以找到,比如在 Project 應用中:GitHub 倉庫。

既然我們添加了新模塊,就應該在security/ir.model.access.csv文件中加入對應的安全權限,代碼如下:

checkout_stage_user,Checkout Stage User,model_library_checkout_stage,library_app.library_group_user,1,0,0,0 checkout_stage_manager,Checkout Stage Manager,model_library_checkout_stage,library_app.library_group_manager,1,1,1,1 

我們需要一組階段來進行操作,所以下面來為模塊添加默認數據。創建data/library_checkout_stage.xml文件並加入如下代碼:

<odoo noupdate="1"> <record id="stage_10" model="library.checkout.stage"> <field name="name">Draft</field> <field name="sequence">10</field> <field name="state">new</field> </record> <record id="stage_20" model="library.checkout.stage"> <field name="name">Borrowed</field> <field name="sequence">20</field> <field name="state">open</field> </record> <record id="stage_90" model="library.checkout.stage"> <field name="name">Completed</field> <field name="sequence">90</field> <field name="state">done</field> </record> <record id="stage_95" model="library.checkout.stage"> <field name="name">Cacelled</field> <field name="sequence">95</field> <field name="state">cancel</field> </record> </odoo> 

要使文件生效,需先在library_checkout/__manifest__.py文件中添加該文件:

    'data':[ ... 'data/library_checkout_stage.xml', ], 

onchange 方法

onchange由用戶界面表單視圖觸發,當用戶編輯指定字段值時,立即執行一段業務邏輯。這可用於執行驗證,向用戶顯示消息或修改表單中的其它字段。支持該邏輯的方法就使用@api.onchange(‘fld1’, ‘fld2’, …)裝飾。裝飾器的參數是用戶界面通過編輯需觸發方法的字段名。

小貼士:通過為字段添加屬性on_change=”0″可在特定表單中關閉 on change 行為,比如<field name=”fld1″ on_change=”0″ />

在方法內,self 參數是帶有當前表單數據的一條虛擬記錄。如果在記錄上設置了值,就會在用戶界面表單中被修改。注意它並沒有向數據庫實際寫入記錄,而是提供信息來修改 UI表單中的數據。無需返回信息,但可以返回一個字典結構的警告信息來顯示在用戶界面中。

作為示例,我們可以使用它來執行借閱表單中的部分自動化:在圖書會員變更時,請求日期設置為當天,並且顯示一個警告信息告知用戶。下面我們就在library_checkout/models/library_checkout.py文件中添加如下代碼:

    @api.onchange('member_id') def onchange_member_id(self): today = fields.Date.today() if self.request_date != today: self.request_date = fields.Date.today() return { 'warning':{ 'title': 'Changed Request Date', 'message': 'Request date changed to today.' } } 

通過用戶界面修改member_id字段時,此處使用了@api.onchange裝飾器來觸發一些邏輯。實際方法不存在關聯,但按照慣例名稱應以onchange_開頭,方法中我們更新了request_date的值並返回警告信息。在onchange方法內,self 表示一條虛擬記錄,它包含當前正在編輯的記錄的所有字段,我們可以與這些字段進行交互。大多數情況下我們想要根據修改字段設置的值自動在其它字段填充值。本例中,我們將request_date更新為當天。

onchange 方法無需返回任何值,但可以返回一個包含警告或作用域鍵的字典:

  • 警告的鍵應描述顯示在對話框中的消息,如{‘title’: ‘Message Title’, ‘message’: ‘Message Body’}
  • 作用域鍵可設置或修改其它字段的域屬性。通過讓to-many字段僅展示在當下有意義的字段,會使得用戶界面更加友好。作用域鍵類似這樣:{‘user_id’: [(’email’, ‘!=’, False)]}

其它模型方法裝飾器

以下裝飾器也會經常使用到,它們與模型內部行為有關,在第六章 Odoo 13開發之模型 – 結構化應用數據中進行了詳細討論。羅列如下供您參考:

@api.depends(fld1,…)用於計算字段函數,來識別(重新)計算應觸發什么樣的修改。必須設置在計算字段值上,否則會報錯。 @api.constrains(fld1,…)用於模型驗證函數並在任意參數中包含的字段修改時執行檢查。它不應向數據庫寫入修改,如檢查失敗,則拋出異常。

使用 ORM 內置方法

上一部分討論的裝飾器允許我們為模型添加一些功能,如實施驗證或自動運算。

ORM 提供對模型數據執行增刪改查(CRUD)操作的方法。下面我們來探討如何擴展寫操作來支持自定義邏輯。讀取數據的主要方法search()和browse()在中第七章 Odoo 13開發之記錄集 – 使用模型數據已進行討論。

寫入模型數據的方法

ORM 為三種基本寫操作提供了三個方法,如下所示:

<Model>.create(values)在模型上創建新記錄,它返回所創建記錄。 <Recordset>.write(values) 更新記錄集中的字段值,它不返回值。 <Recordset>.unlink()從數據庫中刪除記錄,它不返回值。

values參數是一個字典,映射要寫入的字段名和值。

ℹ️Odoo 12中的修改

create()現在也可批量創建數據,這通過把單個字典對象修改為字典對象列表來傳參進行實現。這由帶有@api.model_create_multi裝飾器的create() 方法來進行支持。

有些情況下,我們需要擴展這些方法來添加一些業務邏輯,在這些操作執行時觸發。通過將邏輯放到自定義方法的適當位置,我們可以讓代碼在主操作執行之前或之后運行。

我們將使用借閱模型類創建一個示例:添加兩個日期字段來記錄進入 open 狀態的時間和進入 closed 狀態的時間。這是計算字段所無法實現的,我們還將添加一個檢查來阻止對已為 done 狀態的創建借閱。

因此我們應在 Checkout 類中添加兩個新字段,在library_checkout/models/library_checkout.py文件中添加如下代碼:

    checkout_date = fields.Date(readonly=True) closed_date = fields.Date(readonly=True) 

現在就可以創建自定義的create()方法來設置checkout_date了,如果狀態正確則創建,而如果已經是完成狀態則不予創建,代碼如下:

    @api.model
    def create(self, vals): # Code before create: should use the `vals` dict if 'stage_id' in vals: Stage = self.env['library.checkout.stage'] new_state = Stage.browse(vals['stage_id']).state if new_state == 'open': vals['checkout_date'] = fields.Date.today() new_record = super().create(vals) # Code after create: can use the `new_record` created if new_record.state == 'done': raise exceptions.UserError( 'Not allowed to create a checkout in the done state.') return new_record 

注意在實際新記錄創建之前,不存在其它記錄,僅帶有用於創建記錄的值的字典。這也就是我們使用browse()來獲取新記錄stage_id的原因,然后對值進行相應的檢查。作為對比,一旦創建了新記錄,相應的操作就變簡單了,使用對象的點號標記即可:new_record.state。在執行super().create(vals)命令之前可以對值字典進行修改,我們使用它在狀態合適的情況下寫入checkout_date。

ℹ️Odoo 11中的修改 Python 3中有一種super()的簡寫方式,我們上例中使用的就是這種方式。而在 Python 2中則寫成super(Checkout, self).create(vals),其中 Checkout 為代碼所在的 Python 類名。在 Python 3這種語法仍然可用,但同時帶有簡寫語法:super().create(vals)。

修改記錄時,如果訂閱進入的是合適的狀態我們需要更新checkout_date和closed_date。實現這一功能需要使用自定義的write() 方法,代碼如下:

    def write(self, vals): # Code before write: can use `self`, with the old values if 'stage_id' in vals: Stage = self.env['library.checkout.stage'] new_state = Stage.browse(vals['stage_id']).state if new_state == 'open' and self.state != 'open': vals['checkout_date'] = fields.Date.today() if new_state == 'done' and self.state != 'done': vals['closed_date'] = fields.Date.today() super().write(vals) # Code after write: can use `self`, with the updated values return True 

我們一般會盡量在super().write(vals)之前修改寫入的值。如果write()方法在同一模型中有其它的寫操作,會導致遞歸循環,它在工作進程資源耗盡后結束並報錯。請考慮是否需要這么做,如果需要,避免遞歸循環的一個技巧是在上下文中添加一個標記。作為示例,我們添加類似如下代碼:

if not self.env.context.get('_library_checkout_writing'): self.with_context(_library_checkout_writing=True).write(some_values) 

通過這個技巧,具體的邏輯受到 if 語句的保護,僅在上下文中出現指定標記時才會運行。再深入一步,self.write()操作應使用with_context來設置標記。這種組合確保 if 語句中自定義登錄(login)只執行一次,並且不會觸發更多的write()調用,避免進入無限循環。

在write()內運行write()方法會導致無限循環。要避免這一循環,我們需要在上下文中設置標記值來在代碼中進行檢查避免進入循環。

應仔細考慮是否需要對create或write方法進行擴展。大多數情況下我們只需要在保存記錄時執行一些驗證或自動計算某些值:

對於根據其它字段自動計算的字段值,我們應使用計算字段。這樣的例子有在各行值修改時對頭部匯總的計算。

要使字段默認值動態計算,我們可以將字段賦值的默認值修改為一個函數綁定。

要讓字段根據其它字段的修改來設置值,我們可以使用 onchange 函數。舉個例子,在選定客戶時,將用戶的幣種設置為文檔的幣種,但隨后可由用戶手動修改。記住 onchange 僅用於表單視圖的交互,不直接進行寫入調用。

對於驗證,我們應使用由@api.constraints(fld1,fld2,…)裝飾的約束函數。這和計算字段相似,但不同處在於它會拋出錯誤。

數據導入、導出方法

導入、導出操作在第五章 Odoo 13開發之導入、導出以及模塊數據已做討論,也可以通過 ORM API 中的如下方法操作:

  • load([fields], [data]) 用於導入從 CSV 文件中獲取的數據。第一個參數是導入的字段列表,與 CSV 的第一行對應。第二個參數是記錄列表,每條記錄是一個待解析和導入的字符串列表,與 CSV 數據中的行和列直接對應。它實現了 CSV 數據導入的功能,比如對外部標識符的支持。它用於網頁客戶端的導入函數。
  • export_data([fields], raw_data=False)用於網頁客戶端導出函數。它返回一個字典,帶有包含數據(一個行列表)的數據鍵。字段名可使用 CSV 文件使用的.id和/id后綴,數據格式與需導入的 CSV 文件兼容。可選raw_data參數讓數據值與 Python 類型一同導出,而不是 CSV 文件中的字符串形式。

用戶界面的支持方法

以下方法最常用於網頁客戶端中渲染用戶界面和執行基礎交互:

  • name_get()返回一個表示每條記錄的文本的元組(ID, name)列表。它默認用於計算display_name值,來提供關聯字段的文本表示。可擴展它來實現自定義的顯示方式,如將僅顯示名稱改為顯示記錄編號和名稱。
  • name_search(name=”, args=None, operator=’ilike’, limit=100)返回一個元組(ID, name)列表,其顯示名與 name 參數的文本相匹配。它用於 UI 中,在關聯字段中通過輸入來生成帶有匹配所輸入文本推薦記錄的列表。例如,它可用於在挑選產品的字段中輸入時,實現通過名稱和引用來查找產品。
  • name_create(name)創建一條僅帶有要使用的標題名的新記錄。它用於在 UI 中快速創建(quick-create)功能,這里我們可以僅提供名稱快速創建一條關聯記錄。可擴展來為通過此功能創建的新記錄提供指定默認值。
  • default_get([fields])返回一個帶有要創建的新記錄默認值的字典。默認值可使用變量,如當前用戶或會話上下文。
  • fields_get()用於描述模型字段的定義,在開發者菜單的View Fields選項中也可以看到。
  • fields_view_get()在網頁客戶端中用於獲取要渲染的 UI視圖的結構。可傳入視圖的 ID或想要使用的視圖類型(view_type=’form’)作為參數。例如可使用self.fields_view_get(view_type=’tree’)。

消息和活動(activity)功能

Odoo 自帶全局的消息和活動規划功能,由 Discuss 應用提供,技術名稱為 mail。mail 模塊提供包含mail.thread抽象類,它讓在任意模型中添加消息功能都變得很簡單。還提供mail.activity.mixin用於添加規划活動功能。在第四章 Odoo 13 開發之模塊繼承中已講解了如何從 mixin 抽象類中繼承功能。

要添加這些功能,我們需要在library_checkout中先添加對 mail 的依賴,然后在圖書借閱模型中繼承抽象類中提供的這些功能。編輯library_checkout/manifest.py文件,在 depends 鍵下添加 mail 模塊:

    'depends': ['library_member', 'mail'], 

然后編輯library_checkout/models/library_checkout.py文件來繼承 mixin 抽象模型,代碼如下:

class Checkout(models.Model): _name = 'library.checkout' _description = 'Checkout Request' _inherit = ['mail.thread', 'mail.activity.mixin'] 

然后我們的模型就會添加三個新字段,每條記錄(有時也稱文檔)都包含:

  • mail_follower_ids:存儲 followers 和相應的通知首選項
  • mail_message_ids:列出所有包含關聯活動規划的關聯messages.activity_id

follower 可以是伙伴(partner)或頻道(channel)。partner表示一個具體的人或組織,頻道不是具體的人,而是體現為訂閱列表。每個follower還有一個他們訂閱的消息類型列表,僅有已選消息類型才會發送通知。

消息子類型

一些消息類型稱為子類型,它們存儲在mail.message.subtype模型中,可通過Settings > Technical > Email > Subtypes菜單訪問。默認我們有如下三種消息子類型:

  • Discussions:帶有mail.mt_comment XML ID,用於創建帶有Send message鏈接的消息,默認會發送通知。
  • Activities:帶有mail.mt_activities XML ID,用於創建帶有Schedule activity鏈接的消息,默認不會發送通知。
  • Note:帶有mail.mt_note XML ID,用於創建帶有Log note鏈接的消息,默認不會發送通知。

子類型默認通知設置如上所述,但用戶可以就具體文檔來進行調整,比如關閉他們不感興趣的討論的通知。除內置子類型之外,我們還可以添加自己的子類型並在應用中自定義通知。子類型既可以是通用的也可以只針對具體模型。對於后者,我們應將其所作用的模型名填入子類型的res_model字段中。

發送消息

我們的業務邏輯可利用這個消息系統來向用戶發送通知。可使用message_post() 方法來發送通知,示例如下:

self.message_post('Hello!') 

這會添加一個普通文本消息,但不會向follower發送通知。這是因為默認由mail.mt_note子類型發送消息。但我們可以通過指定的子類型來發送消息。要添加一條向follower發送通知的消息,應使用mt_comment子類型。另一個可選屬性是消息標題,使用這兩項的示例如下:

self.message_post('Hello again!', subject='Hello', subtype='mail.mt_comment') 

消息體是HTML格式的,所以我們可以添加標記來實現文本效果,如<b>為加粗,<i>為斜體。

ℹ️出於安全原因消息體會被清洗,所以有些 HTML 元素可能最終無法出現在消息中。

添加 follower

從業務邏輯角度來看還有一個有意思的功能:可以向文檔添加 follower,這樣他們可以獲取相應的通知。我們有以下幾種方法來添加 follower:

  • message_subscribe(partner_ids=<整型 id 列表>)添加伙伴
  • message_subscribe(channel_ids=<整型 id 列表>) 添加頻道
  • message_subscribe_users(user_ids=<整型 id 列表>) 添加用戶

默認的子類型會作用於每個訂閱者。強制訂閱指定的子類型列表,可添加subtype_ids=<整型 id 列表>屬性,來列出在訂閱中使用指定子類型。

創建向導

假定我們的圖書館用戶需要向一組借閱者發送消息。比如他們可選擇某本書最早的借閱者,向他們發送消息要求歸還圖書。這可通過向導來實現。向導是接受用戶輸入的一系列表單,然后使用輸入來做進一步操作。

我們的用戶開始從借閱列表中選擇待使用的記錄,然后從視圖頂級菜單中選擇 wizard 選項。這會打開向導表單,可填入消息主題和內容。一旦點擊 Send 就將會向所有已選借閱者發送消息。

向導模型

向導對用戶顯示為一個表單視圖,通常是一個對話窗口,可填入一些字段。這些字段會隨后在向導邏輯中使用。這通過普通視圖同樣的模型/視圖結構實現,但支持的模型繼承的是models.TransientMode而不是models.Model。這種類型的模型也會在數據庫體現並存儲狀態,但數據僅在向導完成操作前有用。定時 job 會定期清除向導數據表中的老數據。

我們將使用wizard/checkout_mass_message.py 文件來定義與用戶交互的字段:通知的訂閱者列表,標題和消息體。

首先編輯library_checkout/__init__.py文件並導入wizard/子目錄:

from . import models from . import wizard 

添加wizard/__init__.py文件並加入如下代碼:

from . import checkout_mass_message 

然后創建實際的wizard/checkout_mass_message.py文件,內容如下:

from odoo import api, exceptions, fields, models  class CheckoutMassMessage(models.TransientModel): _name = 'library.checkout.massmessage' _description = 'Send Message to Borrowers' checkout_ids = fields.Many2many( 'library.checkout', string='Checkouts') message_subject = fields.Char() message_body = fields.Html() 

值得注意的是普通模型中的one-to-many關聯不能在臨時模型中使用。這是因為那樣就會要求普通模型中添加與臨時模型的反向many-to-one關聯。但這是不允許的,因為那樣普通記錄的已有引用會阻止對老的臨時記錄的清除。替代方案是使用many-to-many關聯。

ℹ️Many-to-many關聯存儲在獨立的表中,會在關聯任意一方被刪除時自動刪除表中對應行。

臨時模型無需安全規則 ,因為它們是用於輔助執行的一次性記錄。那么也就不需要添加ecurity/ir.model.access.csv權限控制列表文件。

向導表單

向導表單視圖與普通模型相同,只是它有兩個特定元素:

  • 可使用元素來替換操作按鈕
  • special=”cancel”按鈕用於中斷向導,不執行任何操作

wizard/checkout_mass_message_wizard.xml文件的內容如下:

<?xml version="1.0"?> <odoo> <record id="view_form_checkout_message" model="ir.ui.view"> <field name="name">Library Checkout Mass Message Wizard</field> <field name="model">library.checkout.massmessage</field> <field name="arch" type="xml"> <form> <group> <field name="message_subject" /> <field name="message_body" /> <field name="checkout_ids" /> </group> <footer> <button type="object" name="button_send" string="Send Message" /> <button special="cancel" string="Cancel" class="btn-secondary" /> </footer> </form> </field> </record> <act_window id="action_checkout_message" name="Send Messages" binding_model="library.checkout" res_model="library.checkout.massmessage" view_mode="form" target="new" /> </odoo> 

XML 中的窗口操作使用src_model屬性向圖書借閱的Action按鈕添加了一個選項。target=”new”屬性讓它以對話窗口形式打開。打開向導,我們可以從借閱列表中選擇一條或多條記錄,然后從Action菜單中選擇 Send Messages 選項,Action 菜單顯示在列表頂部的Filters菜單旁。

現在這會打開向導表單,但從列表中所選的記錄會被忽略。如果能在向導中任務列表中顯示預選的記錄會很棒。表單會調用default_get() 方法來計算要展示的默認值,這正是我們需要的功能。注意在打開向導表單時,有一條空記錄並且還沒有使用create()方法,該方法僅在點擊按鈕時才會觸發,所以暫不能滿足我們的需求。

Odoo 視圖向上下文字典添加一些元素,可在點擊操作或跳到其它視圖時使用。它們分別是:

  • active_model:帶有視圖模型的技術名
  • active_id:帶有表單活躍記錄或表中第一條記錄的 ID
  • active_ids:帶有一個列表中活躍記錄的列表(如果是表單則只有一個元素)
  • active_domain:如果在表單視圖中觸發了該操作

本例中,active_ids中保存任務列表中所選記錄的 ID,可使用這些 ID 作為向導task_ids字段的默認值,相關代碼如下(izard/checkout_mass_message.py):

    @api.model
    def default_get(self, field_names): defaults = super().default_get(field_names) checkout_ids = self.env.context.get('active_ids') defaults['checkout_ids'] = checkout_ids return defaults 

我們首先使用了super()來調用標准的default_get()運算,然后向默認值添加了一個checkout__id,而active_ids值從環境下文中讀取。

下面我們需要實現點擊表單中Send按鈕的操作。

向導業務邏輯 除了無需進行任何操作僅僅關閉表單的 Cancel 按鈕外,我們還有一個Send按鈕的操作需要實現。該按鈕調用的方法為button_send,需要在wizard/checkout_mass_message.py文件中使用如下代碼定義:

    def button_send(self): self.ensure_one() for checkout in self.checkout_ids: checkout.message_post( body=self.message_body, subject=self.message_subject, subtype='mail.mt_comment', ) return True 

我們的代碼一次僅需處理一個向導實例,所以這里通過self.ensure_one()以示清晰。這里的 self 表示向導表單里顯示的數據。以上方法遍歷已選借閱記錄並向其中的每個借閱者發送消息。這里使用mt_comment子類型,因此會向每個 follower 發送消息通知。

ℹ️讓方法至少返回一個 True 值是一個很好的編程實踐。主要是因為有些XML-RPC協議不支持 None 值,所以對於這些協議就用不了那些方法了。在實際工作中,我們可能不會遇到這個問題,因為網頁客戶端使用JSON-RPC而不是XML-RPC,但這仍是一個可遵循的良好實踐。

使用日志消息

向日志文件寫入消息有助於監控和審計運行的系統。它還有助於代碼維護,在無需修改代碼的情況下可以從運行的進程中輕松獲取調試信息。要讓我們的代碼能使用日志功能,首先要准備一個日志記錄器(logger),在library_checkout/wizard/checkout_mass_message.py文件的頭部添加如下代碼:

import logging _logger = logging.getLogger(__name__) 

這里使用了 Python標准庫logging模塊。_logger通過當前代碼文件名name來進行初始化。這樣日志信息就會帶有生成日志文件的信息。有以下幾種級別的日志信息:

  • _logger.debug(‘DEBUG調試消息’)
  • _logger.info(‘INFO信息日志’)
  • _logger.warning(‘WARNING警告消息’)
  • _logger.error(‘ERROR錯誤消息’)

現在就可以使用logger向日志中寫入消息了,讓我們為button_send向導方法來添加日志。在文件最后的return True前添加如下代碼:

        _logger.info(
            'Posted %d messages to Checkouts: %s', len(self.checkout_ids), str(self.checkout_ids), ) 

這樣在使用向導發送消息時,服務器日志中會出現類似如下消息:

INFO dev13 odoo.addons.library_checkout.wizard.checkout_mass_message: Posted 1 messages to Checkouts: library.checkout(30,) 

注意我們沒有在日志消息中使用 Python 內插字符串。我們沒使用_logger.info(‘Hello %s’ % ‘World’),而是使用了類似_logger.info(‘Hello %s’, ‘World’)。不使用內插使我們的代碼少執行一個任務,讓日志記錄更為高效。因此我們應一直為額外的日志參數傳入變量。

ℹ️服務器日志的時間戳總是使用 UTC 時間。因此打印的日志消息中也是 UTC 時間。你可能會覺得意外 ,但 Odoo服務內部都是使用 UTC 來處理日期的。

對於調試級別日志,我們使用_logger.debug()。例如,可以在checkout.message_post() 命令后添加如下調試日志消息:

        _logger.debug(
            'Message on %d to followers: %s', checkout.id, checkout.message_follower_ids) 

這不會在服務器日志中顯示任何消息,因為默認的日志級別是INFO。需要將日志級別設置為DEBUG才會輸出調試日志消息。

DEBUG dev13 odoo.api: call library.checkout(30,).read(['request_date', 'member_id', 'checkout_date', 'stage_id']) 

Odoo 命令行選項–log-level=debug可用於設置通用日志級別。我們還可以對指定模塊設置日志級別。我們的向導的 Python 模塊是odoo.addons.library_checkout.wizard.checkout_mass_message,這在 INFO 日志消息中也可以看到。要開啟向導的調試消息,使用–loghandler 選項,該選項還可重復多次來對多個模塊設置日志級別,示例如下:

--loghandler=odoo.addons.library_checkout.wizard.checkout_mass_message:DEBUG 

有關 Odoo 服務器日志選項的完整手冊可參見官方文檔。如果想要了解原始的 Python 日志細節,可參見Python 官方文檔。

拋出異常

在操作和預期不一致時,我們可能需要通知用戶並中斷程序,顯示錯誤信息。這可通過拋出異常來實現。Odoo 中提供了一些異常類供我們使用。插件模塊中最常用的 Odoo 異常有:

from odoo import exceptions raise exceptions.ValidationError('驗證失敗') raise exceptions.UserError('業務邏輯錯誤') 

ValidationError異常用於 Python 代碼中的驗證,比如使用@api.constrains裝飾的方法。UserError應該用在其它所有操作不被允許的情況,因為這不符合業務邏輯。

ℹ️Odoo 9中的修改 引用了UserError異常來替換掉Warning異常,淘汰掉 Warning 異常的原因是因為它與 Python 內置異常沖突,但 Odoo 保留了它以保持向后兼容性。

通常所有在方法執行期間的數據操縱在數據庫事務中,發生異常時會進行回滾。也就是說在拋出異常時,所有此前對數據的修改都會被取消。

下面就使用本例向導button_send方法來進行舉例說明。試想一下如果執行發送消息邏輯時沒有選中任何借閱文檔是不是不合邏輯?同樣如果沒有消息體就發送消息也不合邏輯。下面就來在發生這些情況時向用戶發出警告。

編輯button_send()方法,在self.ensure_one()一行后加入如下代碼:

        if not self.checkout_ids: raise exceptions.UserError( '請至少選擇一條借閱記錄來發送消息!') if not self.message_body: raise exceptions.UserError( '請填寫要發送的消息體!') 

補充:經測試發現消息體不填內容並不會拋出異常,因為默認的會發送<p><br></p>這段 html 標簽

單元測試

自動化測試是廣泛接受的軟件開發最佳實踐。不僅可以幫助我們確保代碼正確實施,更重要的為我們未來的代碼修改和重寫提供了一個安全保障。對於 Python 這樣的動態編程語言,因為沒有編譯這一步,語法錯誤經常不容易注意到。這也使得單元測試愈發重要,覆蓋的代碼行數越多越好。

以上兩個目標是我們編寫測試時的燈塔。測試的第一個目標應是提供更好的測試覆蓋:設置測試用例運行所有代碼行。單單這個就會為第二個目標邁出很大一步:顯示代碼有無功能性錯誤,因為在這之后,我們一定可以很好地開始為不顯著的使用特例添加測試用例。

ℹ️Odoo 12中的修改 在該版本之前,Odoo 還支持通過 YAML格式的數據文件進行測試。Odoo 12中刪除了YAML數據文件引擎,不再支持該格式,有關該格式的最后一個文檔請見官方網站。

添加單元測試

Python 測試文件添加在模塊的tests/子目錄下,測試執行器會自動在該目錄下查找測試文件。為測試library_checkout模塊向導邏輯,我們可以創建tests/test_checkout_mass_message.py,老規矩,需要添加tests/init.py文件,內容如下:

from . import test_checkout_mass_message 

tests/test_checkout_mass_message.py代碼的基礎框架如下:

from odoo.tests.common import TransactionCase class TestWizard(TransactionCase): def setUp(self, *args, **kwargs): super(TestWizard, self).setUp(*args, **kwargs) # Add test setup code here... def test_button_send(self): """Send button should create messages on Checkouts""" # Add test code 

Odoo 提供了一些供測試使用的類:

  • TransactionCase測試為每個測試使用不同的事務,在測試結束時自動回滾。
  • SingleTransactionCase將所有測試放在一個事務中運行,在最后一條測試結束后才進行回滾。在每條測試的最終狀態需作為下一條測試的初始狀態時這會非常有用。

setUp()方法用於准備數據以及待使用的變量。通常我們將數據和變量存放在類屬性中,這樣就可在測試方法中進行使用。測試應使用類方法實現,如test_button_send()。測試用例方法名必須以test_為前綴。這些方法被自動發現,該前綴就是用於辨別是否為實施測試用例的方法。根據測試方法名的順序來運行。

在使用TransactionCase類時,在每個測試用例運行完后都會進行回滾。在測試運行時會顯示方法的文檔字符串(docstring),因此可以使用它來作為所執行測試的簡短描述。

ℹ️這些測試類是對Python 標准庫中unittest測試用例的封裝。有關unittest詳細內容,請參見官方文檔。

運行測試

下面就來運行已書寫的測試。我們僅需在安裝或升級(-i或-u)模塊時在 Odoo 服務啟動命令中添加– test-enable選項即可。具體命令如下:

~/odoo-dev/odoo/odoo-bin --test-enable -u library_checkout --stop-after-init 

僅在安裝或升級模塊時才會運行測試,這也就是為會什么添加了-u 選項。如果需要安裝一些依賴,它的測試也會運行。想要避免這一情況,可以像平常那樣測試安裝模塊,然后在升級(-u)模塊時運行測試。以上測試中實際沒有做任何測試,但應該可以正常運行。仔細查看服務器日志可看到報告測試運行的INFO信息,例如:

INFO dev13 odoo.modules.module: odoo.addons.library_checkout.tests.test_checkout_mass_message running tests. 

配置測試

我們應開始在setUp方法中准備測試中將使用的數據。這里我們要創建一條在向導中使用的借閱記錄。使用指定用戶執行測試操作會很便捷,這樣可以同時測試權限控制是否正常配置。這通過sudo(<user>)模型方法來實現。記錄集中攜帶這一信息,因此在使用 sudo()創建后,相同記錄集后續的操作都會使用相同上下文執行。以下是setUp方法中的代碼:

class TestWizard(TransactionCase): def setUp(self, *args, **kwargs): super(TestWizard, self).setUp(*args, **kwargs) # Setup test data admin_user = self.env.ref('base.user_admin') self.Checkout = self.env['library.checkout'].with_user(admin_user) self.Wizard = self.env['library.checkout.massmessage'].with_user(admin_user) a_member = self.env['library.member'].create({'name': 'John'}) self.checkout0 = self.Checkout.create({ 'member_id': a_member.id}) 

此時我們就可以在測試中使用self.checkout0記錄和self.Wizard模型了。

編寫測試用例

現在讓我們來擴展一下初始框架中的test_button_test()方法吧。最簡單的測試是運行測試對象中的部分代碼,獲取結果,然后使用斷言語句來與預期結果進行對比。

要測試發送消息的方法,測試計算向導運行前后的消息條數來確定有沒有增加新消息。要運行向導,需要在上下文中設置active_ids,像 UI 表單一樣,創建帶有填寫向導表單(至少是消息體)的向導記錄,然后運行button_send方法。完整代碼如下:

    def test_button_send(self): """Send button should create messages on Checkouts""" # Add test code msgs_before = len(self.checkout0.message_ids) Wizard0 = self.Wizard.with_context(active_ids=self.checkout0.ids) wizard0 = Wizard0.create({'message_body': 'Hello'}) wizard0.button_send() msgs_after = len(self.checkout0.message_ids) self.assertEqual( msgs_after, msgs_before+1, 'Expected on additional message in the Checkout.') 

這一檢測在self.assertEqual語句中驗證測試成功還是失敗。它對比運行向導前后的消息數,預期會比運行前多一條消息。最后一個參數在測試失敗時作為信息提示,它是可選項,但推薦使用。

assertEqual方法僅是斷言方法的一種,我們應根據具體用例選擇合適的斷言方法,這樣才更易於理解導致測試錯誤的原因。單元測試文檔提供對所有這些方法的說明,參見 Python 官方文檔。

要添加新的測試用例,在類中添加另一個實現方法。要記住TransactionCase測試,每次測試結束后都會回滾。因此,前一次測試的操作會被撤銷,我需要重新打開向導表單。然后模擬用戶填寫消息內容,執行消息發送。最后檢測消息條數來進行驗證。

測試異常

有時我們需要測試來檢查是否生成了異常,常用的情況是測試是否正確地進行了驗證。本例中,我們可以測試向導的一些驗證。例如,我們可以測試空消息體拋出錯誤。要檢查是否拋出異常,我們將相應代碼放在self.assertRaises()代碼塊中。

首先在文件頂部導入 Odoo 的異常類:

from odoo import exceptions 

然后,在測試類中添加含有測試用例的另一個方法:

    def test_button_send_empty_body(self): "Send button errors on empty body message" wizard0 = self.Wizard.create({}) with self.assertRaises(exceptions.UserError) as e: wizard0.button_send() 

如果button_send()沒有拋出異常,則檢測失敗。如果拋出了異常,檢測成功並將異常存儲在 e 變量中,我們可以使用它來做進一步的檢測。

開發工具

開發者應學習一些技巧有協助開發工作。本系列曾介紹過用戶界面的開發者模式。也可以在服務端使用該選項來提供對開發者更友好的功能。這一部分就來進行詳細說明。然后我們會討論另一個開發者相關話題:如何對服務端代碼進行調試。

服務端開發選項

Odoo服務提供一個–dev選項來開啟開發者功能、加速開發流程,比如:

  • 在發現插件模塊中有異常時進入調試器
  • Python 文件保存時自動重新加載代碼,避免反復手動重啟服務
  • 直接從 XML 文件中讀取視圖定義,無需手動更新模塊

–dev參數接收一個逗號分隔列表選項,通常 all 選項可適用大多數情況。我們可以指定想要用的調試器。默認使用Python 調試器pdb,有些人可能喜歡安裝使用其它調試器,Odoo 對ipdb和pudb都予以支持。

ℹ️Odoo 9中的修改 Odoo 9之前的版本中,可使用–debug 選項來對某一模塊異常打開調試器。從Odoo 9開始不再支持改選項,改用–dev=all選項了。

在使用 Python 代碼時,每次代碼修改都需重啟服務來重新加載代碼。–dev命令選項會處理重新加載,在服務監測到 Python 代碼被修改時,自動重復服務加載序列,讓代碼修改立即生效。使用它僅需在服務命令后添加–dev=all 選項:

~/odoo-dev/odoo/odoo-bin --dev=all 

要正常運行,要求安裝watchdog Python包,可通過 pip 命令來執行安裝:

pip install watchdog

注意這僅對 Python 代碼和 XML 文件中視圖結構的修改有益。對於其它修改,如模型數據結構,需要進行模塊升級,僅僅重新加載是不夠的。

調試

我們都知道開發者的大部分工作是調試代碼。我們通常使用代碼編輯器打斷點,運行程序來進行單步調試。如果使用 Windows 系統來開發,配置可運行 Odoo 源碼的環境可不是個簡單的工作。 Odoo是一個等待客戶端調用的服務,然后才進行操作,這一事實讓 Odoo 的調試與客戶端程序截然不同。

Python 調試器

對於初學者可能有點高山仰止的感覺,最實際的方法是使用 Pyhton 集成的調試器pdb來對 Odoo 進行調試。我們會介紹它的擴展,會提供豐富的用戶界面,類似於高級 IDE那樣。

要使用調試器,最好的方法是在需要查看的代碼(通常是模型方法)處插入斷點。這通過在具體位置添加如下行來實現:

import pdb; pdb.set_trace() 

現在重啟服務來加載修改代碼。一旦程序運行到該行,服務運行窗口就會進入一個(pdb)Python 命令對話框,等待輸入。這個對話框和 Python shell 一樣,你可以輸入當前執行上下文的任何表達式或命令來運行。這意味着可以檢查甚至修改當前變量,以下是最常用的快捷命令:

  • h (help) 顯示可用 pdb 命令的匯總
  • p (print) 運行並打印表達式
  • pp (pretty print) 有助於打印數據結構,如字典或列表
  • l (list) 列出下一步要執行的代碼及周邊代碼
  • n (next) 進入下一條命令
  • s (step) 進入當前命令
  • c (continue)繼續正常執行
  • u (up) 在執行棧中上移
  • d (down)在執行棧中下移
  • bt (backtrace)顯示當前執行棧

如果啟動服務時使用了dev=all選項,拋出異常時服務在對應行進行后驗模式。這是一個pdb對話框,和前述的一樣,允許我們檢查在發現錯誤那一刻的程序狀態。

示例調試會話

讓我們來看看一個簡單調試會長什么樣。可以在button_send向導方法的第一行添加調試器斷點:

    def button_send(self): import pdb; pdb.set_trace() self.ensure_one() ... 

現在重啟服務,打開一個發送消息向導表單並點擊 Send 按鈕。這會觸發服務器上的button_send ,客戶端會保持在Still loading…的狀態,等待服務端響應。查看運行服務的終端窗口,可看到類似如下信息:

> /home/vagrant/odoo-dev/custom-addons/library_checkout/wizard/checkout_mass_message.py(24)button_send() -> self.ensure_one() (Pdb) 

這是pdb調試器對話框,第一行告訴我們 Python 代碼執行的位置以及所在的函數名,第二行是要運行的下一行代碼。在調試會話中,可能會跳出一些日志消息。這對於調試沒有傷害,但會打擾到我們。可以通過減少日志輸出來避免這一情況。大多數據情況下日志消息都來自werkzeug模塊。我們可通過–log-handler=werkzeug:CRITICAL 選項來停止日志輸出。如果這還不夠,可以使用–log-level=warn來降低通用日志級別。另一種方法是啟用–logfile=/path/to/log選項,這樣會將日志消息從標准輸出重定向到文件中。

小貼士:如果終端不響應,在終端中的輸入不被顯示,這可能與終端會話的顯示問題有關,通過輸入<enter>reset<enter>有可能解決這一問題。

此時輸入 h,可以看到可用命令的一個快速指南。輸入 l 顯示當前行代碼,以及其周邊的代碼。輸入 n 會運行當前行代碼並進入下一行。如果只按下 Enter,會重復上一條命令。執行三次應該就可以進入方法的 return 語句。我們可以查看任意變量或屬性的內容,如向導中使用的checkout_ids字段:

(Pdb) p self.checkout_ids library.checkout(30,) 

它允許使用任何 Python 表達式,甚至是分配變量。我們可以逐行調試,在任意時刻按下 c 繼續正常運行。

其它 Python 調試器

pdb 的優勢是“開箱即用”,它簡單但粗暴,還有一些使用上更舒適的選擇。

ipdb(Iron Python debugger)是一個常用的選擇,它使用和 pdb 一樣的命令,但做了一些改進,比如添加 Tab 補全和語法高亮來讓使用更舒適。可通過如下命令安裝:

sudo pip install ipdb 

使用如下行添加斷點:

*import ipdb; ipdb.set_trace()

另一個可選調試器是pudb,它也支持和pdb相同的命令,僅用在文本終端中,但使用了類似 IDE 調試器的圖形化顯示。當前上下文的變量及值這類有用信息,在屏幕上它自己的窗口中顯示。可通過系統包管理器或 pip 來進行安裝:

sudo apt-get install python-pudb # 使用Debian系統包 sudo pip install pudb # 使用 pip,可在虛擬環境中 

添加pudb斷點和其它調試器沒什么分別:

import pudb; pudb.set_trace() 

也可以使用更短更易於記憶的方式:

import pudb; pu.db 

打印消息和日志

有時我們只需要查看一些變量的值或者檢查一些代碼段是否被執行。Python的print()函數可以在不中斷執行流的情況下完美解決這些問題。因為我們在服務器窗口中運行,打印的內容會顯示在標准輸出中,但如果日志是寫入文件的打印內容不會存儲到服務器日志中。

print()僅用於開發輔助,不應出現最終部署的代碼中。如果你可能需要代碼執行的更多細節,請使用debug 級別日志消息。在代碼敏感點添加調試級別日志消息讓我們可以在部署實例中調查問題。只需將服務器日志級別提升為 debug,然后查看日志文件。

查看和關閉運行進程

還有一些查看 Odoo 運行進程的小技巧。首先我們需要找到相應的進程ID (PID),要找到 PID,再打開一個終端窗口並輸入如下命令:

ps ax | grep odoo-bin 

輸入的第一列是進程的PID,記錄下要查看進程的 PID,在下面會使用到。現在,我們要向進程發送信號。使用的命令是 kill,默認它發送一個終止進程的信號,但也可以發送其它更友好的信號。知道了運行中的 Odoo 服務進程 PID,我們可以通過向進程發送SIGQUIT信號打印正在執行的代碼蹤跡,命令如下:

kill -3 <PID> 

然后如果我們查看終端窗口或寫入服務輸出的日志文件,就可以看到正常運行的一些線程的信息,以及它們運行所在行代碼的細節棧蹤跡。這用於一些代碼性能分析中,追蹤服務時間消耗在何處,來將代碼執行性能歸類。有關代碼profiling的資料可參見官方文檔。其它可向 Odoo 服務器進程發送的信號有:HUP來重新加載服務,INT或TERM強制服務關閉:

kill -HUP <PID> kill -TERM <PID> 

總結

我們詳細解釋了ORM API 的功能,以及如何使用這些功能來創建動態應用與用戶互動,這可以幫助用戶避免錯誤並自動化一些單調的任務。

模型驗證和計算字段可以處理很多用例,但並不是所有的。我們學習了如何繼承API的create, write和unlink 方法來處理更多用例。

對更豐富的用戶交互,我們使用了 mail 內核插件 mixin 來為用戶添加功能,方便他們圍繞文檔和活動規則進行交流。向導讓應用可以與用戶對話,收集所需數據來運行具體進程。異常允許應用終止錯誤操作,通知用戶存在的問題並回滾中間的修改,保持系統的一致性。

我們還討論了開發者可使用來創建和維護應用的工具:記錄日志消息、調試工具和單元測試。

在下一篇文章中,我們還將使用 ORM,但會從外部應用的視角來操作,將 Odoo 服務器作為存儲數據和運行業務進程的后端。

☞☞☞ 第九章 Odoo 13開發之外部 API – 集成第三方系統


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM