Odoo 開發(fā)手冊(cè)連載八 業(yè)務(wù)邏輯 - 業(yè)務(wù)流程的支持

本文為最好用的免費(fèi)ERP系統(tǒng)Odoo 12開發(fā)手冊(cè)系列文章第八篇。

在前面的文章中缨历,我們學(xué)習(xí)了模型層赡磅、如何創(chuàng)建應(yīng)用數(shù)據(jù)結(jié)構(gòu)以及如何使用 ORM API 來存儲(chǔ)查看數(shù)據(jù)。本文中我們將利用前面所學(xué)的模型和記錄集知識(shí)實(shí)現(xiàn)應(yīng)用中常用的業(yè)務(wù)邏輯模式。

本文的主要內(nèi)容有:

  • 以文件為中心工作流的階段(stage)
  • ORM 方法裝飾器:@api.multi, @api.one和@api.model
  • onchange方法嗓蘑,與用戶即時(shí)交互
  • 使用 ORM 內(nèi)置方法泄隔,如create, write 和 unlink
  • Mail 插件提供的消息和活動(dòng)功能
  • 創(chuàng)建向?qū)韼椭脩魣?zhí)行復(fù)雜操作
  • 使用日志消息優(yōu)化系統(tǒng)監(jiān)測(cè)
  • 拋出異常以在出錯(cuò)時(shí)給用戶反饋
  • 使用單元測(cè)試來進(jìn)行代碼質(zhì)量檢查
  • 開發(fā)工具闸天,調(diào)試器等開發(fā)者工具

開發(fā)準(zhǔn)備

本文中我們將創(chuàng)建一個(gè)依賴于之前文章創(chuàng)建的library_app和library_member模塊的library_checkout插件模塊。這些模塊的代碼請(qǐng)參見 GitHub 倉庫霸旗。這兩個(gè)插件模塊都應(yīng)放置在add-ons路徑中(參見命令行--addons-path或~/.odoorc 配置文件中的addons_path)民晒,這樣我們才能安裝和使用箱蟆。本文完成后的代碼請(qǐng)見 GitHub 倉庫辈毯。

學(xué)習(xí)項(xiàng)目 – library_checkout模塊

在前面章節(jié)的學(xué)習(xí)中仪芒,我們?yōu)閳D書應(yīng)用搭建了主數(shù)據(jù)結(jié)構(gòu)。現(xiàn)在需要為圖書會(huì)員添加借書的功能了。也就是說需要追蹤圖書是否可借以及歸還的記錄。每本書的借閱都有一個(gè)生命周期,從圖書登記選中到圖書被歸還匣屡。這是一個(gè)可通過看板視圖表示的簡單工作流鹅士,看板視圖中每個(gè)階段(stage)可展現(xiàn)為一列趾痘,工作項(xiàng)和借閱請(qǐng)求流從左側(cè)列到右側(cè)列,直至完成為止。

在本文中世分,我們集中學(xué)習(xí)實(shí)現(xiàn)這一功能的數(shù)據(jù)模型和業(yè)務(wù)邏輯瓢阴。用戶界面部分的詳情將在第十章 Odoo 12開發(fā)之后臺(tái)視圖 - 設(shè)計(jì)用戶界面和第十一章 Odoo 12開發(fā)之看板視圖和用戶端 QWeb中討論炫掐。

圖書借閱模型包含:

  • 借閱圖書的會(huì)員(必填)
  • 借閱請(qǐng)求日期(默認(rèn)為當(dāng)天)
  • 負(fù)責(zé)借閱請(qǐng)求的圖書管理員(默認(rèn)為當(dāng)前用戶)
  • 借閱路線,包含請(qǐng)求借閱的一本或多本圖書

要支持并存檔借閱生命周期睬涧,需要添加如下內(nèi)容:

  • 請(qǐng)求的階段:已選中募胃、可借閱、已借出痹束、已歸還或已取消
  • 借閱日期,圖書借出的日期
  • 關(guān)閉日期焙矛,圖書歸還的日期

我們將開始創(chuàng)建一個(gè)新的模塊library_checkout并實(shí)現(xiàn)圖書借閱模型的初始版本。與此前章節(jié)相比此處并沒有引入新的知識(shí),用于提供一個(gè)基礎(chǔ)供本文后續(xù)創(chuàng)建新功能姆蘸。

在其它圖書插件模塊的同級(jí)路徑下創(chuàng)建一個(gè)library_checkout目錄:

1、首先添加manifest.py文件并加入如下內(nèi)容:

{
    '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缴川、在模塊目錄下創(chuàng)建init.py文件涧偷,并添加如下代碼:

from . import models

3、創(chuàng)建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')

下面就要添加數(shù)據(jù)文件了炸卑,添加訪問規(guī)則、菜單項(xiàng)和一些基礎(chǔ)視圖,這樣模塊可以最小化的運(yùn)行起來。

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谭胚、菜項(xiàng)項(xiàng)通過views/library_menu.xml實(shí)現(xiàn):

<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文件實(shí)現(xiàn):

<?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>

現(xiàn)在就可以在我們的 Odoo 工作數(shù)據(jù)庫中安裝這個(gè)模塊未玻,并準(zhǔn)備開始添加更多功能了灾而。

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

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

在 Odoo 中,我們可以實(shí)現(xiàn)以文檔(document)為中心的工作流扳剿。我們這里說的文檔包括銷售訂單旁趟、項(xiàng)目任務(wù)或人事申請(qǐng)。所有這些都遵循一個(gè)特定的生命周期庇绽,它們都在完成時(shí)才被創(chuàng)建锡搜。它們都被記錄在一個(gè)文檔中,按照一系列可能的階段推進(jìn)瞧掺,直至完成耕餐。

如果把各階段以列展示在面板中,把文檔作為這些列中的工作項(xiàng)辟狈,就可以得到一個(gè)看板(Kanban)肠缔,一個(gè)快速查看工作進(jìn)度的視圖。實(shí)現(xiàn)這些進(jìn)度步驟有兩種方法哼转,通常稱為狀態(tài)和階段明未。

狀態(tài)通過預(yù)定義的閉合選項(xiàng)列表來實(shí)現(xiàn)。它便于實(shí)現(xiàn)業(yè)務(wù)規(guī)則壹蔓,并且模型和視圖對(duì) state 字段有特別的支持趟妥,根據(jù)當(dāng)前狀態(tài)來帶有必填和隱藏屬性集。狀態(tài)列表有一個(gè)劣勢(shì)佣蓉,就是它是預(yù)定義并且閉合的披摄,因此無法按具體流程需求來做調(diào)整亲雪。

階段通過關(guān)聯(lián)模型實(shí)現(xiàn),階段列表是開放的行疏,可被配置來滿足當(dāng)前流程需求匆光。可以輕易地修改引用階段列表:刪除酿联、添加或渲染這些階段终息。它的劣勢(shì)是對(duì)流程自動(dòng)化不可靠,因?yàn)殡A段列表可被修改贞让,自動(dòng)化規(guī)則就無法依賴于具體的階段 ID 或描述周崭。

獲取兩種方法優(yōu)勢(shì)的方式是將階段映射到狀態(tài)中。文檔組織到可配置的階段中喳张,然后間接關(guān)聯(lián)到對(duì)于自動(dòng)化業(yè)務(wù)邏輯可靠的狀態(tài)碼中续镇。我們將在library_checkout/models/library_checkout_stage.py文件中實(shí)現(xiàn)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 字段销部,允許每個(gè)階段與四個(gè)基本狀態(tài)映射摸航。sequence字段很重要,要配置順序舅桩,階段應(yīng)在看板和階段選擇列表中展示酱虎。fold 布爾字段是看板用于將一些列默認(rèn)折疊,這樣其內(nèi)容就不會(huì)馬上顯示出來擂涛。折疊通常用于已完成或取消的階段读串。新的代碼一定不要忘記加入到models/init.py文件中,當(dāng)前內(nèi)容為:

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是一個(gè)與階段模型的 many-to-one關(guān)聯(lián)狰右。我們還添加了 state 字段杰捂,這是一個(gè)讓階段的 state 字段在當(dāng)前模型中可用的關(guān)聯(lián)字段,這樣才能在視圖中使用棋蚌。階段的默認(rèn)值由_default_stage() 函數(shù)來計(jì)算琼娘,它返回階段模型的第一條記錄。因?yàn)殡A段模型已通過 sequence 排序附鸽,所以返回的是 sequence 值最小的一條記錄脱拼。

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

??Odoo 10中的修改
group_expand字段在Odoo 10中引入跷乐,但在官方文檔中沒有介紹。使用示例在 Odoo 的源代碼中可以找到趾浅,比如在 Project 應(yīng)用中:GitHub 倉庫愕提。

既然我們添加了新模塊,就應(yīng)該在security/ir.model.access.csv文件中加入對(duì)應(yīng)的安全權(quán)限皿哨,代碼如下:

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

我們需要一組階段來進(jìn)行操作浅侨,所以下面來為模塊添加默認(rèn)數(shù)據(jù)。創(chuàng)建data/library_checkout_stage.xml文件并加入如下代碼:


    <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',
    ],
Odoo 12圖書項(xiàng)目 stages

備注:上圖為通過開發(fā)者菜單中Edit View: Form編輯添加了 stage_id 后的效果如输。

ORM 方法裝飾器

就我們目前碰到的 Odoo 中 Python 代碼,裝飾器央勒,如@api.multi通常用于模型方法中不见。這對(duì) ORM 非常重要,允許它給這些方法特殊用法崔步。下面就來看看有哪些 ORM 裝飾器以及如何使用稳吮。

記錄集方法:@api.multi

大多數(shù)情況下,我們需要一個(gè)自定義方法來對(duì)記錄集執(zhí)行一些操作刷晋。此時(shí)就需要使用@api.multi盖高,并且此處self參數(shù)就是要操作的記錄集慎陵。方法的邏輯通常會(huì)包含對(duì) self 的遍歷眼虱。@api.multi是最常用的裝飾器。

小貼士:如果模型方法沒有添加裝飾器席纽,默認(rèn)就使用@api.multi捏悬。

單例記錄方法:@api.one

有些情況下方法用于操作單條記錄(單例),此時(shí)可使用@api.one裝飾器∪筇荩現(xiàn)在仍可使用@api.one过牙,但在 Odoo 9中已聲明為棄用。它包裹裝飾的方法纺铭,進(jìn)行 for 循環(huán)遍歷寇钉,它調(diào)用裝飾方法,一次一條記錄舶赔,然后返回一個(gè)結(jié)果列表扫倡。因此在@api.one裝飾的方法內(nèi),self 一定是單例竟纳。

小貼士:@api.one的返回值有些搞怪撵溃,它返回一個(gè)列表疚鲤,而不實(shí)際方法返回的數(shù)據(jù)結(jié)構(gòu)。比如方法代碼如果返回字典缘挑,實(shí)際返回值是一個(gè)字典值列表集歇。這種誤導(dǎo)性也是該方法被棄用的主要原因。

對(duì)于要操作單條記錄的方法语淘,我們應(yīng)還是使用@api.multi诲宇,在代碼頂部添加一行self.ensure_one(),來確保操作的是單條記錄亏娜。

類靜態(tài)方法:@api.model

有時(shí)方法需要在類級(jí)別而不是具體記錄上操作焕窝。面向?qū)ο缶幊陶Z言中,這稱之為靜態(tài)方法维贺。這些類級(jí)別的靜態(tài)方法應(yīng)由@api.model裝飾它掂。在這些情況下,self 應(yīng)作為模型的引用 溯泣,無需包含實(shí)際記錄虐秋。

??@api.model裝飾的方法無法用于用戶界面按鈕,在這種情況下垃沦,應(yīng)使用@api.multi客给。

onchange 方法

onchange由用戶界面表單視圖觸發(fā),當(dāng)用戶編輯指定字段值時(shí)肢簿,立即執(zhí)行一段業(yè)務(wù)邏輯靶剑。這可用于執(zhí)行驗(yàn)證,向用戶顯示消息或修改表單中的其它字段池充。支持該邏輯的方法就使用@api.onchange('fld1', 'fld2', ...)裝飾桩引。裝飾器的參數(shù)是用戶界面通過編輯需觸發(fā)方法的字段名。

小貼士:通過為字段添加屬性on_change="0"可在特定表單中關(guān)閉 on change 行為收夸,比如<field name="fld1" on_change="0" />

在方法內(nèi)坑匠,self 參數(shù)是帶有當(dāng)前表單數(shù)據(jù)的一條虛擬記錄。如果在記錄上設(shè)置了值卧惜,就會(huì)在用戶界面表單中被修改厘灼。注意它并沒有向數(shù)據(jù)庫實(shí)際寫入記錄,而是提供信息來修改 UI表單中的數(shù)據(jù)咽瓷。無需返回信息设凹,但可以返回一個(gè)字典結(jié)構(gòu)的警告信息來顯示在用戶界面中。

作為示例茅姜,我們可以使用它來執(zhí)行借閱表單中的部分自動(dòng)化:在圖書會(huì)員變更時(shí)闪朱,請(qǐng)求日期設(shè)置為當(dāng)天,并且顯示一個(gè)警告信息告知用戶。下面我們就在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字段時(shí)监透,此處使用了@api.onchange裝飾器來觸發(fā)一些邏輯桶错。實(shí)際方法不存在關(guān)聯(lián),但按照慣例名稱應(yīng)以onchange_開頭胀蛮,方法中我們更新了request_date的值并返回警告信息院刁。在onchange方法內(nèi),self 表示一條虛擬記錄粪狼,它包含當(dāng)前正在編輯的記錄的所有字段退腥,我們可以與這些字段進(jìn)行交互。大多數(shù)情況下我們想要根據(jù)修改字段設(shè)置的值自動(dòng)在其它字段填充值再榄。本例中狡刘,我們將request_date更新為當(dāng)天。

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

  • 警告的鍵應(yīng)描述顯示在對(duì)話框中的消息嗅蔬,如{'title': 'Message Title', 'message': 'Message Body'}
  • 作用域鍵可設(shè)置或修改其它字段的域?qū)傩浴Mㄟ^讓to-many字段僅展示在當(dāng)下有意義的字段疾就,會(huì)使得用戶界面更加友好澜术。作用域鍵類似這樣:{'user_id': [('email', '!=', False)]}

其它模型方法裝飾器

以下裝飾器也會(huì)經(jīng)常使用到,它們與模型內(nèi)部行為有關(guān)猬腰,在第六章 Odoo 12開發(fā)之模型 - 結(jié)構(gòu)化應(yīng)用數(shù)據(jù)中進(jìn)行了詳細(xì)討論鸟废。羅列如下供您參考:

  • @api.depends(fld1,...)用于計(jì)算字段函數(shù),來識(shí)別(重新)計(jì)算應(yīng)觸發(fā)什么樣的修改姑荷。必須設(shè)置在計(jì)算字段值上盒延,否則會(huì)報(bào)錯(cuò)。
  • @api.constrains(fld1,...)用于模型驗(yàn)證函數(shù)并在任意參數(shù)中包含的字段修改時(shí)執(zhí)行檢查鼠冕。它不應(yīng)向數(shù)據(jù)庫寫入修改添寺,如檢查失敗,則拋出異常供鸠。

使用 ORM 內(nèi)置方法

上一部分討論的裝飾器允許我們?yōu)槟P吞砑右恍┕δ芷杳常鐚?shí)施驗(yàn)證或自動(dòng)運(yùn)算陨闹。

ORM 提供對(duì)模型數(shù)據(jù)執(zhí)行增刪改查(CRUD)操作的方法楞捂。下面我們來探討如何擴(kuò)展寫操作來支持自定義邏輯。讀取數(shù)據(jù)的主要方法search()和browse()在中第七章 Odoo 12開發(fā)之記錄集 - 使用模型數(shù)據(jù)已進(jìn)行討論趋厉。

寫入模型數(shù)據(jù)的方法

ORM 為三種基本寫操作提供了三個(gè)方法寨闹,如下所示:

  • <Model>.create(values)在模型上創(chuàng)建新記錄,它返回所創(chuàng)建記錄君账。
  • <Recordset>.write(values) 更新記錄集中的字段值繁堡,它不返回值。
  • <Recordset>.unlink()從數(shù)據(jù)庫中刪除記錄,它不返回值椭蹄。

values參數(shù)是一個(gè)字典闻牡,映射要寫入的字段名和值。這些方法由@api.multi裝飾绳矩,除create()方法使用@api.model裝飾器外罩润。

??Odoo 12中的修改
create()現(xiàn)在也可批量創(chuàng)建數(shù)據(jù),這通過把單個(gè)字典對(duì)象修改為字典對(duì)象列表來傳參進(jìn)行實(shí)現(xiàn)翼馆。這由帶有@api.model_create_multi裝飾器的create() 方法來進(jìn)行支持割以。

有些情況下,我們需要擴(kuò)展這些方法來添加一些業(yè)務(wù)邏輯应媚,在這些操作執(zhí)行時(shí)觸發(fā)严沥。通過將邏輯放到自定義方法的適當(dāng)位置,我們可以讓代碼在主操作執(zhí)行之前或之后運(yùn)行中姜。

我們將使用借閱模型類創(chuàng)建一個(gè)示例:添加兩個(gè)日期字段來記錄進(jìn)入 open 狀態(tài)的時(shí)間和進(jìn)入 closed 狀態(tài)的時(shí)間消玄。這是計(jì)算字段所無法實(shí)現(xiàn)的,我們還將添加一個(gè)檢查來阻止對(duì)已為 done 狀態(tài)的創(chuàng)建借閱丢胚。

因此我們應(yīng)在 Checkout 類中添加兩個(gè)新字段莱找,在library_checkout/models/library_checkout.py文件中添加如下代碼:

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

現(xiàn)在就可以創(chuàng)建自定義的create()方法來設(shè)置checkout_date了,如果狀態(tài)正確則創(chuàng)建嗜桌,而如果已經(jīng)是完成狀態(tài)則不予創(chuàng)建奥溺,代碼如下:

    @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

注意在實(shí)際新記錄創(chuàng)建之前,不存在其它記錄骨宠,僅帶有用于創(chuàng)建記錄的值的字典浮定。這也就是我們使用browse()來獲取新記錄stage_id的原因,然后對(duì)值進(jìn)行相應(yīng)的檢查层亿。作為對(duì)比桦卒,一旦創(chuàng)建了新記錄,相應(yīng)的操作就變簡單了匿又,使用對(duì)象的點(diǎn)號(hào)標(biāo)記即可:new_record.state方灾。在執(zhí)行super().create(vals)命令之前可以對(duì)值字典進(jìn)行修改,我們使用它在狀態(tài)合適的情況下寫入checkout_date碌更。

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

修改記錄時(shí)鸟妙,如果訂閱進(jìn)入的是合適的狀態(tài)我們需要更新checkout_date和closed_date焦人。實(shí)現(xiàn)這一功能需要使用自定義的write() 方法,代碼如下:

    @api.multi
    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

我們一般會(huì)盡量在super().write(vals)之前修改寫入的值重父。如果write()方法在同一模型中有其它的寫操作花椭,會(huì)導(dǎo)致遞歸循環(huán),它在工作進(jìn)程資源耗盡后結(jié)束并報(bào)錯(cuò)房午。請(qǐng)考慮是否需要這么做个从,如果需要,避免遞歸循環(huán)的一個(gè)技巧是在上下文中添加一個(gè)標(biāo)記歪沃。作為示例嗦锐,我們添加類似如下代碼:

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

通過這個(gè)技巧,具體的邏輯受到 if 語句的保護(hù)沪曙,僅在上下文中出現(xiàn)指定標(biāo)記時(shí)才會(huì)運(yùn)行奕污。再深入一步,self.write()操作應(yīng)使用with_context來設(shè)置標(biāo)記液走。這種組合確保 if 語句中自定義登錄(login)只執(zhí)行一次碳默,并且不會(huì)觸發(fā)更多的write()調(diào)用,避免進(jìn)入無限循環(huán)缘眶。

在write()內(nèi)運(yùn)行write()方法會(huì)導(dǎo)致無限循環(huán)嘱根。要避免這一循環(huán)傲茄,我們需要在上下文中設(shè)置標(biāo)記值來在代碼中進(jìn)行檢查避免進(jìn)入循環(huán)竖哩。

應(yīng)仔細(xì)考慮是否需要對(duì)create或write方法進(jìn)行擴(kuò)展。大多數(shù)情況下我們只需要在保存記錄時(shí)執(zhí)行一些驗(yàn)證或自動(dòng)計(jì)算某些值:

  • 對(duì)于根據(jù)其它字段自動(dòng)計(jì)算的字段值策严,我們應(yīng)使用計(jì)算字段顶燕。這樣的例子有在各行值修改時(shí)對(duì)頭部匯總的計(jì)算凑保。
  • 要使字段默認(rèn)值動(dòng)態(tài)計(jì)算,我們可以將字段賦值的默認(rèn)值修改為一個(gè)函數(shù)綁定涌攻。
  • 要讓字段根據(jù)其它字段的修改來設(shè)置值欧引,我們可以使用 onchange 函數(shù)。舉個(gè)例子恳谎,在選定客戶時(shí)芝此,將用戶的幣種設(shè)置為文檔的幣種,但隨后可由用戶手動(dòng)修改因痛。記住 onchange 僅用于表單視圖的交互婚苹,不直接進(jìn)行寫入調(diào)用。
  • 對(duì)于驗(yàn)證婚肆,我們應(yīng)使用由@api.constraints(fld1,fld2,...)裝飾的約束函數(shù)租副。這和計(jì)算字段相似坐慰,但不同處在于它會(huì)拋出錯(cuò)誤较性。

數(shù)據(jù)導(dǎo)入用僧、導(dǎo)出方法

導(dǎo)入、導(dǎo)出操作在第五章 Odoo 12開發(fā)之導(dǎo)入赞咙、導(dǎo)出以及模塊數(shù)據(jù)已做討論责循,也可以通過 ORM API 中的如下方法操作:

  • load([fields], [data]) 用于導(dǎo)入從 CSV 文件中獲取的數(shù)據(jù)。第一個(gè)參數(shù)是導(dǎo)入的字段列表攀操,與 CSV 的第一行對(duì)應(yīng)院仿。第二個(gè)參數(shù)是記錄列表,每條記錄是一個(gè)待解析和導(dǎo)入的字符串列表速和,與 CSV 數(shù)據(jù)中的行和列直接對(duì)應(yīng)歹垫。它實(shí)現(xiàn)了 CSV 數(shù)據(jù)導(dǎo)入的功能,比如對(duì)外部標(biāo)識(shí)符的支持颠放。它用于網(wǎng)頁客戶端的導(dǎo)入函數(shù)排惨。
  • export_data([fields], raw_data=False)用于網(wǎng)頁客戶端導(dǎo)出函數(shù)。它返回一個(gè)字典碰凶,帶有包含數(shù)據(jù)(一個(gè)行列表)的數(shù)據(jù)鍵暮芭。字段名可使用 CSV 文件使用的.id和/id后綴,數(shù)據(jù)格式與需導(dǎo)入的 CSV 文件兼容欲低≡辏可選raw_data參數(shù)讓數(shù)據(jù)值與 Python 類型一同導(dǎo)出,而不是 CSV 文件中的字符串形式砾莱。

用戶界面的支持方法

以下方法最常用于網(wǎng)頁客戶端中渲染用戶界面和執(zhí)行基礎(chǔ)交互:

  • name_get()返回一個(gè)表示每條記錄的文本的元組(ID, name)列表瑞筐。它默認(rèn)用于計(jì)算display_name值,來提供關(guān)聯(lián)字段的文本表示腊瑟∶婧撸可擴(kuò)展它來實(shí)現(xiàn)自定義的顯示方式,如將僅顯示名稱改為顯示記錄編號(hào)和名稱扫步。
  • name_search(name='', args=None, operator='ilike', limit=100)返回一個(gè)元組(ID, name)列表魔策,其顯示名與 name 參數(shù)的文本相匹配。它用于 UI 中河胎,在關(guān)聯(lián)字段中通過輸入來生成帶有匹配所輸入文本推薦記錄的列表闯袒。例如,它可用于在挑選產(chǎn)品的字段中輸入時(shí)游岳,實(shí)現(xiàn)通過名稱和引用來查找產(chǎn)品政敢。
  • name_create(name)創(chuàng)建一條僅帶有要使用的標(biāo)題名的新記錄。它用于在 UI 中快速創(chuàng)建(quick-create)功能胚迫,這里我們可以僅提供名稱快速創(chuàng)建一條關(guān)聯(lián)記錄喷户。可擴(kuò)展來為通過此功能創(chuàng)建的新記錄提供指定默認(rèn)值访锻。
  • default_get([fields])返回一個(gè)帶有要?jiǎng)?chuàng)建的新記錄默認(rèn)值的字典褪尝。默認(rèn)值可使用變量闹获,如當(dāng)前用戶或會(huì)話上下文。
  • fields_get()用于描述模型字段的定義河哑,在開發(fā)者菜單的View Fields選項(xiàng)中也可以看到避诽。
  • fields_view_get()在網(wǎng)頁客戶端中用于獲取要渲染的 UI視圖的結(jié)構(gòu)×Ы鳎可傳入視圖的 ID或想要使用的視圖類型(view_type='form')作為參數(shù)沙庐。例如可使用self.fields_view_get(view_type='tree')。

消息和活動(dòng)(activity)功能

Odoo 自帶全局的消息和活動(dòng)規(guī)劃功能佳吞,由 Discuss 應(yīng)用提供拱雏,技術(shù)名稱為 mail。mail 模塊提供包含mail.thread抽象類底扳,它讓在任意模型中添加消息功能都變得很簡單古涧。還提供mail.activity.mixin用于添加規(guī)劃活動(dòng)功能。在第四章 Odoo 12 開發(fā)之模塊繼承中已講解了如何從 mixin 抽象類中繼承功能花盐。

要添加這些功能羡滑,我們需要在library_checkout中先添加對(duì) 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']

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

  • mail_follower_ids:存儲(chǔ) followers 和相應(yīng)的通知首選項(xiàng)
  • mail_message_ids:列出所有包含關(guān)聯(lián)活動(dòng)規(guī)劃的關(guān)聯(lián)messages.activity_id

follower 可以是伙伴(partner)或頻道(channel)职祷。partner表示一個(gè)具體的人或組織,頻道不是具體的人届囚,而是體現(xiàn)為訂閱列表有梆。每個(gè)follower還有一個(gè)他們訂閱的消息類型列表,僅有已選消息類型才會(huì)發(fā)送通知意系。

消息子類型

一些消息類型稱為子類型泥耀,它們存儲(chǔ)在mail.message.subtype模型中,可通過Settings > Technical > Email > Subtypes菜單訪問蛔添。默認(rèn)我們有如下三種消息子類型:

  • Discussions:帶有mail.mt_comment XML ID痰催,用于創(chuàng)建帶有Send message鏈接的消息,默認(rèn)會(huì)發(fā)送通知迎瞧。
  • Activities:帶有mail.mt_activities XML ID夸溶,用于創(chuàng)建帶有Schedule activity鏈接的消息,默認(rèn)不會(huì)發(fā)送通知凶硅。
  • Note:帶有mail.mt_note XML ID缝裁,用于創(chuàng)建帶有Log note鏈接的消息,默認(rèn)不會(huì)發(fā)送通知足绅。

子類型默認(rèn)通知設(shè)置如上所述捷绑,但用戶可以就具體文檔來進(jìn)行調(diào)整韩脑,比如關(guān)閉他們不感興趣的討論的通知。除內(nèi)置子類型之外胎食,我們還可以添加自己的子類型并在應(yīng)用中自定義通知扰才。子類型既可以是通用的也可以只針對(duì)具體模型允懂。對(duì)于后者厕怜,我們應(yīng)將其所作用的模型名填入子類型的res_model字段中。

Odoo 12消息子類型

發(fā)送消息

我們的業(yè)務(wù)邏輯可利用這個(gè)消息系統(tǒng)來向用戶發(fā)送通知蕾总≈嗪剑可使用message_post() 方法來發(fā)送通知,示例如下:

self.message_post('Hello!')

這會(huì)添加一個(gè)普通文本消息生百,但不會(huì)向follower發(fā)送通知递雀。這是因?yàn)槟J(rèn)由mail.mt_note子類型發(fā)送消息。但我們可以通過指定的子類型來發(fā)送消息蚀浆。要添加一條向follower發(fā)送通知的消息缀程,應(yīng)使用mt_comment子類型。另一個(gè)可選屬性是消息標(biāo)題市俊,使用這兩項(xiàng)的示例如下:

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

消息體是HTML格式的杨凑,所以我們可以添加標(biāo)記來實(shí)現(xiàn)文本效果,如<b>為加粗摆昧,<i>為斜體撩满。

??出于安全原因消息體會(huì)被清洗,所以有些 HTML 元素可能最終無法出現(xiàn)在消息中绅你。

添加 follower

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

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

默認(rèn)的子類型會(huì)作用于每個(gè)訂閱者忌锯。強(qiáng)制訂閱指定的子類型列表伪嫁,可添加subtype_ids=<整型 id 列表>屬性,來列出在訂閱中使用指定子類型偶垮。

創(chuàng)建向?qū)?/h2>

假定我們的圖書館用戶需要向一組借閱者發(fā)送消息礼殊。比如他們可選擇某本書最早的借閱者,向他們發(fā)送消息要求歸還圖書针史。這可通過向?qū)韺?shí)現(xiàn)晶伦。向?qū)墙邮苡脩糨斎氲囊幌盗斜韱危缓笫褂幂斎雭碜鲞M(jìn)一步操作啄枕。

我們的用戶開始從借閱列表中選擇待使用的記錄婚陪,然后從視圖頂級(jí)菜單中選擇 wizard 選項(xiàng)。這會(huì)打開向?qū)П韱纹底#商钊胂⒅黝}和內(nèi)容泌参。一旦點(diǎn)擊 Send 就將會(huì)向所有已選借閱者發(fā)送消息脆淹。

向?qū)P?/h3>

向?qū)?duì)用戶顯示為一個(gè)表單視圖,通常是一個(gè)對(duì)話窗口沽一,可填入一些字段盖溺。這些字段會(huì)隨后在向?qū)н壿嬛惺褂谩_@通過普通視圖同樣的模型/視圖結(jié)構(gòu)實(shí)現(xiàn)铣缠,但支持的模型繼承的是models.TransientMode而不是models.Model烘嘱。這種類型的模型也會(huì)在數(shù)據(jù)庫體現(xiàn)并存儲(chǔ)狀態(tài),但數(shù)據(jù)僅在向?qū)瓿刹僮髑坝杏没韧堋6〞r(shí) job 會(huì)定期清除向?qū)?shù)據(jù)表中的老數(shù)據(jù)蝇庭。

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

首先編輯library_checkout/init.py文件并導(dǎo)入wizard/子目錄:

from . import models
from . import wizard

添加wizard/init.py文件并加入如下代碼:

from . import checkout_mass_message

然后創(chuàng)建實(shí)際的wizard/checkout_mass_message.py文件哮内,內(nèi)容如下:

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關(guān)聯(lián)不能在臨時(shí)模型中使用。這是因?yàn)槟菢泳蜁?huì)要求普通模型中添加與臨時(shí)模型的反向many-to-one關(guān)聯(lián)壮韭。但這是不允許的北发,因?yàn)槟菢悠胀ㄓ涗浀囊延幸脮?huì)阻止對(duì)老的臨時(shí)記錄的清除。替代方案是使用many-to-many關(guān)聯(lián)喷屋。

??Many-to-many關(guān)聯(lián)存儲(chǔ)在獨(dú)立的表中琳拨,會(huì)在關(guān)聯(lián)任意一方被刪除時(shí)自動(dòng)刪除表中對(duì)應(yīng)行。

臨時(shí)模型無需安全規(guī)則 逼蒙,因?yàn)樗鼈兪怯糜谳o助執(zhí)行的一次性記錄从绘。那么也就不需要添加ecurity/ir.model.access.csv權(quán)限控制列表文件。

向?qū)П韱?/h3>

向?qū)П韱我晥D與普通模型相同是牢,只是它有兩個(gè)特定元素:

  • 可使用<footer>元素來替換操作按鈕
  • special="cancel"按鈕用于中斷向?qū)Ы┚粓?zhí)行任何操作

wizard/checkout_mass_message_wizard.xml文件的內(nèi)容如下:

<?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"
            src_model="library.checkout"
            res_model="library.checkout.massmessage"
            view_type="form"
            view_mode="form"
            target="new"
            multi="True"
            />
</odoo>

XML 中的窗口操作使用src_model屬性向圖書借閱的Action按鈕添加了一個(gè)選項(xiàng)。target="new"屬性讓它以對(duì)話窗口形式打開驳棱。打開向?qū)玻覀兛梢詮慕栝喠斜碇羞x擇一條或多條記錄,然后從Action菜單中選擇 Send Messages 選項(xiàng)社搅,Action 菜單顯示在列表頂部的Filters菜單旁驻债。

Odoo 12圖書項(xiàng)目發(fā)送消息菜單

現(xiàn)在這會(huì)打開向?qū)П韱危珡牧斜碇兴x的記錄會(huì)被忽略形葬。如果能在向?qū)е腥蝿?wù)列表中顯示預(yù)選的記錄會(huì)很棒合呐。表單會(huì)調(diào)用default_get() 方法來計(jì)算要展示的默認(rèn)值,這正是我們需要的功能笙以。注意在打開向?qū)П韱螘r(shí)淌实,有一條空記錄并且還沒有使用create()方法,該方法僅在點(diǎn)擊按鈕時(shí)才會(huì)觸發(fā),所以暫不能滿足我們的需求拆祈。

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

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

本例中放坏,active_ids中保存任務(wù)列表中所選記錄的 ID咙咽,可使用這些 ID 作為向?qū)ask_ids字段的默認(rèn)值,相關(guān)代碼如下(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()來調(diào)用標(biāo)準(zhǔn)的default_get()運(yùn)算淤年,然后向默認(rèn)值添加了一個(gè)checkout__id钧敞,而active_ids值從環(huán)境下文中讀取。

下面我們需要實(shí)現(xiàn)點(diǎn)擊表單中Send按鈕的操作互亮。

向?qū)I(yè)務(wù)邏輯

除了無需進(jìn)行任何操作僅僅關(guān)閉表單的 Cancel 按鈕外犁享,我們還有一個(gè)Send按鈕的操作需要實(shí)現(xiàn)余素。該按鈕調(diào)用的方法為button_send豹休,需要在wizard/checkout_mass_message.py文件中使用如下代碼定義:

    @api.multi
    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

我們的代碼一次僅需處理一個(gè)向?qū)?shí)例,所以這里通過self.ensure_one()以示清晰桨吊。這里的 self 表示向?qū)П韱卫镲@示的數(shù)據(jù)威根。以上方法遍歷已選借閱記錄并向其中的每個(gè)借閱者發(fā)送消息。這里使用mt_comment子類型视乐,因此會(huì)向每個(gè) follower 發(fā)送消息通知洛搀。

??讓方法至少返回一個(gè) True 值是一個(gè)很好的編程實(shí)踐。主要是因?yàn)橛行ML-RPC協(xié)議不支持 None 值佑淀,所以對(duì)于這些協(xié)議就用不了那些方法了留美。在實(shí)際工作中,我們可能不會(huì)遇到這個(gè)問題伸刃,因?yàn)榫W(wǎng)頁客戶端使用JSON-RPC而不是XML-RPC谎砾,但這仍是一個(gè)可遵循的良好實(shí)踐。

消息發(fā)送對(duì)話框

使用日志消息

向日志文件寫入消息有助于監(jiān)控和審計(jì)運(yùn)行的系統(tǒng)捧颅。它還有助于代碼維護(hù)景图,在無需修改代碼的情況下可以從運(yùn)行的進(jìn)程中輕松獲取調(diào)試信息。要讓我們的代碼能使用日志功能碉哑,首先要準(zhǔn)備一個(gè)日志記錄器(logger)挚币,在library_checkout/wizard/checkout_mass_message.py文件的頭部添加如下代碼:

import logging
_logger = logging.getLogger(__name__)

這里使用了 Python標(biāo)準(zhǔn)庫logging模塊。_logger通過當(dāng)前代碼文件名name來進(jìn)行初始化扣典。這樣日志信息就會(huì)帶有生成日志文件的信息妆毕。有以下幾種級(jí)別的日志信息:

_logger.debug('DEBUG調(diào)試消息')
_logger.info('INFO信息日志')
_logger.warning('WARNING警告消息')
_logger.error('ERROR錯(cuò)誤消息')

現(xiàn)在就可以使用logger向日志中寫入消息了,讓我們?yōu)閎utton_send向?qū)Х椒▉硖砑尤罩局狻T谖募詈蟮膔eturn True前添加如下代碼:

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

這樣在使用向?qū)Оl(fā)送消息時(shí)笛粘,服務(wù)器日志中會(huì)出現(xiàn)類似如下消息:

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

注意我們沒有在日志消息中使用 Python 內(nèi)插字符串。我們沒使用_logger.info('Hello %s' % 'World'),而是使用了類似_logger.info('Hello %s', 'World')闰蛔。不使用內(nèi)插使我們的代碼少執(zhí)行一個(gè)任務(wù)痕钢,讓日志記錄更為高效。因此我們應(yīng)一直為額外的日志參數(shù)傳入變量序六。

??服務(wù)器日志的時(shí)間戳總是使用 UTC 時(shí)間任连。因此打印的日志消息中也是 UTC 時(shí)間。你可能會(huì)覺得意外 例诀,但 Odoo服務(wù)內(nèi)部都是使用 UTC 來處理日期的随抠。

對(duì)于調(diào)試級(jí)別日志,我們使用_logger.debug()繁涂。例如拱她,可以在checkout.message_post() 命令后添加如下調(diào)試日志消息:

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

這不會(huì)在服務(wù)器日志中顯示任何消息,因?yàn)槟J(rèn)的日志級(jí)別是INFO扔罪。需要將日志級(jí)別設(shè)置為DEBUG才會(huì)輸出調(diào)試日志消息秉沼。

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

Odoo 命令行選項(xiàng)--log-level=debug可用于設(shè)置通用日志級(jí)別。我們還可以對(duì)指定模塊設(shè)置日志級(jí)別矿酵。我們的向?qū)У?Python 模塊是odoo.addons.library_checkout.wizard.checkout_mass_message唬复,這在 INFO 日志消息中也可以看到。要開啟向?qū)У恼{(diào)試消息全肮,使用--loghandler 選項(xiàng)敞咧,該選項(xiàng)還可重復(fù)多次來對(duì)多個(gè)模塊設(shè)置日志級(jí)別,示例如下:

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

有關(guān) Odoo 服務(wù)器日志選項(xiàng)的完整手冊(cè)可參見官方文檔辜腺。如果想要了解原始的 Python 日志細(xì)節(jié)休建,可參見Python 官方文檔

拋出異常

在操作和預(yù)期不一致時(shí)评疗,我們可能需要通知用戶并中斷程序测砂,顯示錯(cuò)誤信息。這可通過拋出異常來實(shí)現(xiàn)壤巷。Odoo 中提供了一些異常類供我們使用邑彪。插件模塊中最常用的 Odoo 異常有:

from odoo import exceptions
raise exceptions.ValidationError('驗(yàn)證失敗')
raise exceptions.UserError('業(yè)務(wù)邏輯錯(cuò)誤')

ValidationError異常用于 Python 代碼中的驗(yàn)證,比如使用@api.constrains裝飾的方法胧华。UserError應(yīng)該用在其它所有操作不被允許的情況寄症,因?yàn)檫@不符合業(yè)務(wù)邏輯。

??Odoo 9中的修改
引用了UserError異常來替換掉Warning異常矩动,淘汰掉 Warning 異常的原因是因?yàn)樗c Python 內(nèi)置異常沖突有巧,但 Odoo 保留了它以保持向后兼容性。

通常所有在方法執(zhí)行期間的數(shù)據(jù)操縱在數(shù)據(jù)庫事務(wù)中悲没,發(fā)生異常時(shí)會(huì)進(jìn)行回滾篮迎。也就是說在拋出異常時(shí),所有此前對(duì)數(shù)據(jù)的修改都會(huì)被取消。

下面就使用本例向?qū)utton_send方法來進(jìn)行舉例說明甜橱。試想一下如果執(zhí)行發(fā)送消息邏輯時(shí)沒有選中任何借閱文檔是不是不合邏輯逊笆?同樣如果沒有消息體就發(fā)送消息也不合邏輯。下面就來在發(fā)生這些情況時(shí)向用戶發(fā)出警告岂傲。

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

        if not self.checkout_ids:
            raise exceptions.UserError(
                '請(qǐng)至少選擇一條借閱記錄來發(fā)送消息!')
        if not self.message_body:
            raise exceptions.UserError(
                '請(qǐng)?zhí)顚懸l(fā)送的消息體!')

補(bǔ)充:經(jīng)測(cè)試發(fā)現(xiàn)消息體不填內(nèi)容并不會(huì)拋出異常,因?yàn)槟J(rèn)的會(huì)發(fā)送<p>
</p>這段 html 標(biāo)簽

Odoo 12圖書項(xiàng)目異常測(cè)試

單元測(cè)試

自動(dòng)化測(cè)試是廣泛接受的軟件開發(fā)最佳實(shí)踐镊掖。不僅可以幫助我們確保代碼正確實(shí)施乃戈,更重要的為我們未來的代碼修改和重寫提供了一個(gè)安全保障。對(duì)于 Python 這樣的動(dòng)態(tài)編程語言亩进,因?yàn)闆]有編譯這一步症虑,語法錯(cuò)誤經(jīng)常不容易注意到。這也使得單元測(cè)試愈發(fā)重要归薛,覆蓋的代碼行數(shù)越多越好谍憔。

以上兩個(gè)目標(biāo)是我們編寫測(cè)試時(shí)的燈塔。測(cè)試的第一個(gè)目標(biāo)應(yīng)是提供更好的測(cè)試覆蓋:設(shè)置測(cè)試用例運(yùn)行所有代碼行苟翻。單單這個(gè)就會(huì)為第二個(gè)目標(biāo)邁出很大一步:顯示代碼有無功能性錯(cuò)誤韵卤,因?yàn)樵谶@之后骗污,我們一定可以很好地開始為不顯著的使用特例添加測(cè)試用例崇猫。

??Odoo 12中的修改
在該版本之前,Odoo 還支持通過 YAML格式的數(shù)據(jù)文件進(jìn)行測(cè)試需忿。Odoo 12中刪除了YAML數(shù)據(jù)文件引擎诅炉,不再支持該格式,有關(guān)該格式的最后一個(gè)文檔請(qǐng)見官方網(wǎng)站屋厘。

添加單元測(cè)試

Python 測(cè)試文件添加在模塊的tests/子目錄下涕烧,測(cè)試執(zhí)行器會(huì)自動(dòng)在該目錄下查找測(cè)試文件。為測(cè)試library_checkout模塊向?qū)н壿嫼谷鳎覀兛梢詣?chuàng)建tests/test_checkout_mass_message.py议纯,老規(guī)矩,需要添加tests/init.py文件溢谤,內(nèi)容如下:

from . import test_checkout_mass_message

tests/test_checkout_mass_message.py代碼的基礎(chǔ)框架如下:

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 提供了一些供測(cè)試使用的類:

  • TransactionCase測(cè)試為每個(gè)測(cè)試使用不同的事務(wù)瞻凤,在測(cè)試結(jié)束時(shí)自動(dòng)回滾。
  • SingleTransactionCase將所有測(cè)試放在一個(gè)事務(wù)中運(yùn)行世杀,在最后一條測(cè)試結(jié)束后才進(jìn)行回滾阀参。在每條測(cè)試的最終狀態(tài)需作為下一條測(cè)試的初始狀態(tài)時(shí)這會(huì)非常有用。

setUp()方法用于準(zhǔn)備數(shù)據(jù)以及待使用的變量瞻坝。通常我們將數(shù)據(jù)和變量存放在類屬性中蛛壳,這樣就可在測(cè)試方法中進(jìn)行使用。測(cè)試應(yīng)使用類方法實(shí)現(xiàn),如test_button_send()衙荐。測(cè)試用例方法名必須以test_為前綴捞挥。這些方法被自動(dòng)發(fā)現(xiàn),該前綴就是用于辨別是否為實(shí)施測(cè)試用例的方法忧吟。根據(jù)測(cè)試方法名的順序來運(yùn)行树肃。

在使用TransactionCase類時(shí),在每個(gè)測(cè)試用例運(yùn)行完后都會(huì)進(jìn)行回滾瀑罗。在測(cè)試運(yùn)行時(shí)會(huì)顯示方法的文檔字符串(docstring)胸嘴,因此可以使用它來作為所執(zhí)行測(cè)試的簡短描述。

??這些測(cè)試類是對(duì)Python 標(biāo)準(zhǔn)庫中unittest測(cè)試用例的封裝斩祭。有關(guān)unittest詳細(xì)內(nèi)容劣像,請(qǐng)參見官方文檔

運(yùn)行測(cè)試

下面就來運(yùn)行已書寫的測(cè)試摧玫。我們僅需在安裝或升級(jí)(-i或-u)模塊時(shí)在 Odoo 服務(wù)啟動(dòng)命令中添加-- test-enable選項(xiàng)即可耳奕。具體命令如下:

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

僅在安裝或升級(jí)模塊時(shí)才會(huì)運(yùn)行測(cè)試,這也就是為會(huì)什么添加了-u 選項(xiàng)诬像。如果需要安裝一些依賴屋群,它的測(cè)試也會(huì)運(yùn)行。想要避免這一情況坏挠,可以像平常那樣測(cè)試安裝模塊芍躏,然后在升級(jí)(-u)模塊時(shí)運(yùn)行測(cè)試。以上測(cè)試中實(shí)際沒有做任何測(cè)試降狠,但應(yīng)該可以正常運(yùn)行对竣。仔細(xì)查看服務(wù)器日志可看到報(bào)告測(cè)試運(yùn)行的INFO信息,例如:

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

配置測(cè)試

我們應(yīng)開始在setUp方法中準(zhǔn)備測(cè)試中將使用的數(shù)據(jù)榜配。這里我們要?jiǎng)?chuàng)建一條在向?qū)е惺褂玫慕栝営涗浄裎场J褂弥付ㄓ脩魣?zhí)行測(cè)試操作會(huì)很便捷,這樣可以同時(shí)測(cè)試權(quán)限控制是否正常配置蛋褥。這通過sudo(<user>)模型方法來實(shí)現(xiàn)临燃。記錄集中攜帶這一信息,因此在使用 sudo()創(chuàng)建后烙心,相同記錄集后續(xù)的操作都會(huì)使用相同上下文執(zhí)行膜廊。以下是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'].sudo(admin_user)
        self.Wizard = self.env['library.checkout.massmessage'].sudo(admin_user)

        a_member = self.env['library.member'].create({'name': 'John'})
        self.checkout0 = self.Checkout.create({
            'member_id': a_member.id})

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

編寫測(cè)試用例

現(xiàn)在讓我們來擴(kuò)展一下初始框架中的test_button_test()方法吧弃理。最簡單的測(cè)試是運(yùn)行測(cè)試對(duì)象中的部分代碼溃论,獲取結(jié)果,然后使用斷言語句來與預(yù)期結(jié)果進(jìn)行對(duì)比痘昌。

要測(cè)試發(fā)送消息的方法钥勋,測(cè)試計(jì)算向?qū)н\(yùn)行前后的消息條數(shù)來確定有沒有增加新消息炬转。要運(yùn)行向?qū)В枰谏舷挛闹性O(shè)置active_ids算灸,像 UI 表單一樣扼劈,創(chuàng)建帶有填寫向?qū)П韱危ㄖ辽偈窍Ⅲw)的向?qū)в涗洠缓筮\(yùn)行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.')

這一檢測(cè)在self.assertEqual語句中驗(yàn)證測(cè)試成功還是失敗荐吵。它對(duì)比運(yùn)行向?qū)昂蟮南?shù),預(yù)期會(huì)比運(yùn)行前多一條消息赊瞬。最后一個(gè)參數(shù)在測(cè)試失敗時(shí)作為信息提示先煎,它是可選項(xiàng),但推薦使用巧涧。

assertEqual方法僅是斷言方法的一種薯蝎,我們應(yīng)根據(jù)具體用例選擇合適的斷言方法,這樣才更易于理解導(dǎo)致測(cè)試錯(cuò)誤的原因谤绳。單元測(cè)試文檔提供對(duì)所有這些方法的說明占锯,參見 Python 官方文檔

要添加新的測(cè)試用例缩筛,在類中添加另一個(gè)實(shí)現(xiàn)方法消略。要記住TransactionCase測(cè)試,每次測(cè)試結(jié)束后都會(huì)回滾瞎抛。因此艺演,前一次測(cè)試的操作會(huì)被撤銷,我需要重新打開向?qū)П韱涡鍪АH缓竽M用戶填寫消息內(nèi)容钞艇,執(zhí)行消息發(fā)送。最后檢測(cè)消息條數(shù)來進(jìn)行驗(yàn)證豪硅。

補(bǔ)充:此處原文已慘不忍睹,通篇是任務(wù)清單項(xiàng)目的描述挺物,筆者自行做了對(duì)應(yīng)的調(diào)整懒浮。

測(cè)試異常

有時(shí)我們需要測(cè)試來檢查是否生成了異常,常用的情況是測(cè)試是否正確地進(jìn)行了驗(yàn)證识藤。本例中砚著,我們可以測(cè)試向?qū)У囊恍?yàn)證。例如痴昧,我們可以測(cè)試空消息體拋出錯(cuò)誤稽穆。要檢查是否拋出異常,我們將相應(yīng)代碼放在self.assertRaises()代碼塊中赶撰。

首先在文件頂部導(dǎo)入 Odoo 的異常類:

from odoo import exceptions

然后舌镶,在測(cè)試類中添加含有測(cè)試用例的另一個(gè)方法:

    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()沒有拋出異常柱彻,則檢測(cè)失敗。如果拋出了異常餐胀,檢測(cè)成功并將異常存儲(chǔ)在 e 變量中哟楷,我們可以使用它來做進(jìn)一步的檢測(cè)。

Odoo 12圖書項(xiàng)目單元測(cè)試

開發(fā)工具

開發(fā)者應(yīng)學(xué)習(xí)一些技巧有協(xié)助開發(fā)工作否灾。本系列曾介紹過用戶界面的開發(fā)者模式卖擅。也可以在服務(wù)端使用該選項(xiàng)來提供對(duì)開發(fā)者更友好的功能。這一部分就來進(jìn)行詳細(xì)說明墨技。然后我們會(huì)討論另一個(gè)開發(fā)者相關(guān)話題:如何對(duì)服務(wù)端代碼進(jìn)行調(diào)試惩阶。

服務(wù)端開發(fā)選項(xiàng)

Odoo服務(wù)提供一個(gè)--dev選項(xiàng)來開啟開發(fā)者功能、加速開發(fā)流程扣汪,比如:

  • 在發(fā)現(xiàn)插件模塊中有異常時(shí)進(jìn)入調(diào)試器
  • Python 文件保存時(shí)自動(dòng)重新加載代碼琳猫,避免反復(fù)手動(dòng)重啟服務(wù)
  • 直接從 XML 文件中讀取視圖定義,無需手動(dòng)更新模塊

--dev參數(shù)接收一個(gè)逗號(hào)分隔列表選項(xiàng)私痹,通常 all 選項(xiàng)可適用大多數(shù)情況脐嫂。我們可以指定想要用的調(diào)試器。默認(rèn)使用Python 調(diào)試器pdb紊遵,有些人可能喜歡安裝使用其它調(diào)試器账千,Odoo 對(duì)ipdb和pudb都予以支持。

??Odoo 9中的修改
Odoo 9之前的版本中暗膜,可使用--debug 選項(xiàng)來對(duì)某一模塊異常打開調(diào)試器匀奏。從Odoo 9開始不再支持改選項(xiàng),改用-- dev=all選項(xiàng)了学搜。

在使用 Python 代碼時(shí)娃善,每次代碼修改都需重啟服務(wù)來重新加載代碼。--dev命令選項(xiàng)會(huì)處理重新加載瑞佩,在服務(wù)監(jiān)測(cè)到 Python 代碼被修改時(shí)镀岛,自動(dòng)重復(fù)服務(wù)加載序列,讓代碼修改立即生效家浇。使用它僅需在服務(wù)命令后添加--dev=all 選項(xiàng):

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

要正常運(yùn)行初嘹,要求安裝watchdog Python包,可通過 pip 命令來執(zhí)行安裝:

pip install watchdog

注意這僅對(duì) Python 代碼和 XML 文件中視圖結(jié)構(gòu)的修改有益稠炬。對(duì)于其它修改焕阿,如模型數(shù)據(jù)結(jié)構(gòu),需要進(jìn)行模塊升級(jí)首启,僅僅重新加載是不夠的暮屡。

調(diào)試

我們都知道開發(fā)者的大部分工作是調(diào)試代碼。我們通常使用代碼編輯器打斷點(diǎn)毅桃,運(yùn)行程序來進(jìn)行單步調(diào)試褒纲。如果使用 Windows 系統(tǒng)來開發(fā)准夷,配置可運(yùn)行 Odoo 源碼的環(huán)境可不是個(gè)簡單的工作。 Odoo是一個(gè)等待客戶端調(diào)用的服務(wù)外厂,然后才進(jìn)行操作冕象,這一事實(shí)讓 Odoo 的調(diào)試與客戶端程序截然不同。

Python 調(diào)試器

對(duì)于初學(xué)者可能有點(diǎn)高山仰止的感覺汁蝶,最實(shí)際的方法是使用 Pyhton 集成的調(diào)試器pdb來對(duì) Odoo 進(jìn)行調(diào)試渐扮。我們會(huì)介紹它的擴(kuò)展,會(huì)提供豐富的用戶界面掖棉,類似于高級(jí) IDE那樣墓律。

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

import pdb; pdb.set_trace()

現(xiàn)在重啟服務(wù)來加載修改代碼耻讽。一旦程序運(yùn)行到該行,服務(wù)運(yùn)行窗口就會(huì)進(jìn)入一個(gè)(pdb)Python 命令對(duì)話框帕棉,等待輸入针肥。這個(gè)對(duì)話框和 Python shell 一樣,你可以輸入當(dāng)前執(zhí)行上下文的任何表達(dá)式或命令來運(yùn)行香伴。這意味著可以檢查甚至修改當(dāng)前變量慰枕,以下是最常用的快捷命令:

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

如果啟動(dòng)服務(wù)時(shí)使用了dev=all選項(xiàng)即纲,拋出異常時(shí)服務(wù)在對(duì)應(yīng)行進(jìn)行后驗(yàn)?zāi)J骄甙铩_@是一個(gè)pdb對(duì)話框,和前述的一樣低斋,允許我們檢查在發(fā)現(xiàn)錯(cuò)誤那一刻的程序狀態(tài)蜂厅。

Odoo 12調(diào)試 pdb

示例調(diào)試會(huì)話

讓我們來看看一個(gè)簡單調(diào)試會(huì)長什么樣〔渤耄可以在button_send向?qū)Х椒ǖ牡谝恍刑砑诱{(diào)試器斷點(diǎn):

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

現(xiàn)在重啟服務(wù)掘猿,打開一個(gè)發(fā)送消息向?qū)П韱尾Ⅻc(diǎn)擊 Send 按鈕。這會(huì)觸發(fā)服務(wù)器上的button_send 巴比,客戶端會(huì)保持在Still loading...的狀態(tài)术奖,等待服務(wù)端響應(yīng)。查看運(yùn)行服務(wù)的終端窗口轻绞,可看到類似如下信息:

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

這是pdb調(diào)試器對(duì)話框,第一行告訴我們 Python 代碼執(zhí)行的位置以及所在的函數(shù)名佣耐,第二行是要運(yùn)行的下一行代碼政勃。在調(diào)試會(huì)話中,可能會(huì)跳出一些日志消息兼砖。這對(duì)于調(diào)試沒有傷害奸远,但會(huì)打擾到我們既棺。可以通過減少日志輸出來避免這一情況懒叛。大多數(shù)據(jù)情況下日志消息都來自werkzeug模塊丸冕。我們可通過--log-handler=werkzeug:CRITICAL 選項(xiàng)來停止日志輸出。如果這還不夠薛窥,可以使用--log-level=warn來降低通用日志級(jí)別胖烛。另一種方法是啟用--logfile=/path/to/log選項(xiàng),這樣會(huì)將日志消息從標(biāo)準(zhǔn)輸出重定向到文件中诅迷。

小貼士:如果終端不響應(yīng)佩番,在終端中的輸入不被顯示,這可能與終端會(huì)話的顯示問題有關(guān)罢杉,通過輸入<enter>reset<enter>有可能解決這一問題趟畏。

此時(shí)輸入 h,可以看到可用命令的一個(gè)快速指南滩租。輸入 l 顯示當(dāng)前行代碼赋秀,以及其周邊的代碼。輸入 n 會(huì)運(yùn)行當(dāng)前行代碼并進(jìn)入下一行律想。如果只按下 Enter猎莲,會(huì)重復(fù)上一條命令。執(zhí)行三次應(yīng)該就可以進(jìn)入方法的 return 語句蜘欲。我們可以查看任意變量或?qū)傩缘膬?nèi)容益眉,如向?qū)е惺褂玫腸heckout_ids字段:

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

它允許使用任何 Python 表達(dá)式,甚至是分配變量姥份。我們可以逐行調(diào)試郭脂,在任意時(shí)刻按下 c 繼續(xù)正常運(yùn)行。

其它 Python 調(diào)試器

pdb 的優(yōu)勢(shì)是“開箱即用”澈歉,它簡單但粗暴展鸡,還有一些使用上更舒適的選擇。

ipdb(Iron Python debugger)是一個(gè)常用的選擇埃难,它使用和 pdb 一樣的命令莹弊,但做了一些改進(jìn),比如添加 Tab 補(bǔ)全和語法高亮來讓使用更舒適涡尘∪坛冢可通過如下命令安裝:

sudo pip install ipdb

使用如下行添加斷點(diǎn):

import ipdb; ipdb.set_trace()
Odoo 12調(diào)試工具 ipdb

另一個(gè)可選調(diào)試器是pudb,它也支持和pdb相同的命令考抄,僅用在文本終端中细疚,但使用了類似 IDE 調(diào)試器的圖形化顯示。當(dāng)前上下文的變量及值這類有用信息川梅,在屏幕上它自己的窗口中顯示疯兼∪欢簦可通過系統(tǒng)包管理器或 pip 來進(jìn)行安裝:

sudo apt-get install python-pudb # 使用Debian系統(tǒng)包
sudo pip install pudb # 使用 pip,可在虛擬環(huán)境中

添加pudb斷點(diǎn)和其它調(diào)試器沒什么分別:

import pudb; pudb.set_trace()

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

import pudb; pu.db
image.png

打印消息和日志

有時(shí)我們只需要查看一些變量的值或者檢查一些代碼段是否被執(zhí)行吧彪。Python的print()函數(shù)可以在不中斷執(zhí)行流的情況下完美解決這些問題待侵。因?yàn)槲覀冊(cè)诜?wù)器窗口中運(yùn)行,打印的內(nèi)容會(huì)顯示在標(biāo)準(zhǔn)輸出中姨裸,但如果日志是寫入文件的打印內(nèi)容不會(huì)存儲(chǔ)到服務(wù)器日志中秧倾。

print()僅用于開發(fā)輔助,不應(yīng)出現(xiàn)最終部署的代碼中啦扬。如果你可能需要代碼執(zhí)行的更多細(xì)節(jié)中狂,請(qǐng)使用debug 級(jí)別日志消息。在代碼敏感點(diǎn)添加調(diào)試級(jí)別日志消息讓我們可以在部署實(shí)例中調(diào)查問題扑毡。只需將服務(wù)器日志級(jí)別提升為 debug胃榕,然后查看日志文件。

查看和關(guān)閉運(yùn)行進(jìn)程

還有一些查看 Odoo 運(yùn)行進(jìn)程的小技巧瞄摊。首先我們需要找到相應(yīng)的進(jìn)程ID (PID)勋又,要找到 PID,再打開一個(gè)終端窗口并輸入如下命令:

ps ax | grep odoo-bin

輸入的第一列是進(jìn)程的PID换帜,記錄下要查看進(jìn)程的 PID楔壤,在下面會(huì)使用到。現(xiàn)在惯驼,我們要向進(jìn)程發(fā)送信號(hào)蹲嚣。使用的命令是 kill,默認(rèn)它發(fā)送一個(gè)終止進(jìn)程的信號(hào)祟牲,但也可以發(fā)送其它更友好的信號(hào)隙畜。知道了運(yùn)行中的 Odoo 服務(wù)進(jìn)程 PID,我們可以通過向進(jìn)程發(fā)送SIGQUIT信號(hào)打印正在執(zhí)行的代碼蹤跡说贝,命令如下:

kill -3 <PID>

然后如果我們查看終端窗口或?qū)懭敕?wù)輸出的日志文件议惰,就可以看到正常運(yùn)行的一些線程的信息,以及它們運(yùn)行所在行代碼的細(xì)節(jié)棧蹤跡乡恕。這用于一些代碼性能分析中言询,追蹤服務(wù)時(shí)間消耗在何處,來將代碼執(zhí)行性能歸類傲宜。有關(guān)代碼profiling的資料可參見官方文檔运杭。其它可向 Odoo 服務(wù)器進(jìn)程發(fā)送的信號(hào)有:HUP來重新加載服務(wù),INT或TERM強(qiáng)制服務(wù)關(guān)閉:

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

總結(jié)

我們?cè)敿?xì)解釋了ORM API 的功能函卒,以及如何使用這些功能來創(chuàng)建動(dòng)態(tài)應(yīng)用與用戶互動(dòng)县习,這可以幫助用戶避免錯(cuò)誤并自動(dòng)化一些單調(diào)的任務(wù)。

模型驗(yàn)證和計(jì)算字段可以處理很多用例谆趾,但并不是所有的躁愿。我們學(xué)習(xí)了如何繼承API的create, write和unlink 方法來處理更多用例。

對(duì)更豐富的用戶交互沪蓬,我們使用了 mail 內(nèi)核插件 mixin 來為用戶添加功能彤钟,方便他們圍繞文檔和活動(dòng)規(guī)則進(jìn)行交流。向?qū)ё寫?yīng)用可以與用戶對(duì)話跷叉,收集所需數(shù)據(jù)來運(yùn)行具體進(jìn)程逸雹。異常允許應(yīng)用終止錯(cuò)誤操作,通知用戶存在的問題并回滾中間的修改云挟,保持系統(tǒng)的一致性梆砸。

我們還討論了開發(fā)者可使用來創(chuàng)建和維護(hù)應(yīng)用的工具:記錄日志消息、調(diào)試工具和單元測(cè)試园欣。

在下一篇文章中帖世,我們還將使用 ORM,但會(huì)從外部應(yīng)用的視角來操作沸枯,將 Odoo 服務(wù)器作為存儲(chǔ)數(shù)據(jù)和運(yùn)行業(yè)務(wù)進(jìn)程的后端日矫。

??? 第九章 Odoo 12開發(fā)之外部 API - 集成第三方系統(tǒng)

擴(kuò)展閱讀

以下是本文所討論的內(nèi)容相關(guān)參考材料如下:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绑榴,隨后出現(xiàn)的幾起案子哪轿,更是在濱河造成了極大的恐慌,老刑警劉巖翔怎,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窃诉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赤套,警方通過查閱死者的電腦和手機(jī)飘痛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來于毙,“玉大人敦冬,你說我怎么就攤上這事∥ň冢” “怎么了脖旱?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長介蛉。 經(jīng)常有香客問我萌庆,道長,這世上最難降的妖魔是什么币旧? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任践险,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巍虫。我一直安慰自己彭则,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布占遥。 她就那樣靜靜地躺著俯抖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓦胎。 梳的紋絲不亂的頭發(fā)上芬萍,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音搔啊,去河邊找鬼柬祠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛负芋,可吹牛的內(nèi)容都是我干的漫蛔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼示罗,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼惩猫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚜点,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤轧房,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后绍绘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奶镶,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年陪拘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厂镇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡左刽,死狀恐怖捺信,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欠痴,我是刑警寧澤迄靠,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站喇辽,受9級(jí)特大地震影響掌挚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜菩咨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一吠式、第九天 我趴在偏房一處隱蔽的房頂上張望陡厘。 院中可真熱鬧,春花似錦特占、人聲如沸糙置。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罢低。三九已至,卻和暖如春胖笛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宜岛。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工长踊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萍倡。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓身弊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親列敲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阱佛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354