odoo 開發入門教學系列-繼承(Inheritance)

2023-04-15 06:01:34

繼承(Inheritance)

Odoo的一個強大方面是它的模組化。模組專用於業務需求,但模組也可以相互互動。這對於擴充套件現有模組的功能非常有用。例如,在我們的房地產場景中,我們希望在常規使用者檢視中直接顯示銷售人員的財產列表。

在介紹特定的Odoo模組繼承之前,讓我們看看如何更改標準CRUD(建立、檢索,更新或刪除)方法的行為

Python繼承(Python Inheritance)

目標:

在我們的房地產模組中,我們從不需要開發任何特定的東西來執行標準的CRUD操作。Odoo框架提供了實現這些操作的必要工具。事實上,多虧經典的Python繼承,我們的模型中已經包含了這樣的操作:

from odoo import fields, models

class TestModel(models.Model):
    _name = "test.model"
    _description = "Test Model"

    ...

我們的 TestModel 類繼承與Model,該Model類提供了 create(), read(), write()unlink()方法。

這些方法(和其它在Model中定義的任何方法)可被擴充套件以新增指定業務邏輯:

from odoo import fields, models

class TestModel(models.Model):
    _name = "test.model"
    _description = "Test Model"

    ...

    @api.model
    def create(self, vals):
        # Do some business logic, modify vals...
        ...
        # Then call super to execute the parent method
        return super().create(vals)

model()裝飾器對於create() 方法來說是必需的,因為結果集self的內容和建立(creation)的上下文無關,但該裝飾器對於其它CRUD方法來說不是必需的。

Python 3中, super() 等價於 super(TestModel, self)。當你需要使用一條被修改後的結果集呼叫父方法時,可能需要使用後者。

危險提示

  • 總是呼叫 super()以避免中斷流非常重要。只有少數非常特殊的情況才無需呼叫它。
  • 總是返回和父方法一致的資料。例如父方法返回一個dict(),你重寫父方法時也要返回一個dict()

練習--新增業務邏輯到CRUD方法

  • 如果房產記錄狀態不是NewCanceled,則不讓刪除

提示:重寫unlink() ,並記住self可以是一個包含多條記錄的結果集。

  • 建立報價時,設定房產狀態為‘Offer Received’,如果使用者試圖以低於已存在報價的金額建立報價時丟擲錯誤。

提示: 可在vals中獲取property_id 欄位,但是它是一個int型。要範例化一個estate.property 物件,請使用self.env[model_name].browse(value) (範例)

    @api.model
    def create(self, vals):
        self.env['gamification.badge'].browse(vals['badge_id']).check_granting()
        return super(BadgeUser, self).create(vals)

修改odoo14\custom\estate\views\estate_property_views.xml 去掉estate_property_view_tree<tree>元素的editable="top"屬性(說明:為了方便執行報價建立操作)

修改odoo14\custom\estate\models\estate_property.py

    @api.constrains('selling_price', 'expected_price')
    def _check_selling_price(self):
        # if record.selling_price < self.expected_price * 0.9:
        #     raise ValidationError("selling price can`t not lower then 90 percent of expected price")
        pass

說明:為了方便實踐操作,暫且不做售價校驗

最末尾新增以下程式碼

    def unlink(self):
        for record in self:
            if record.state not in ['New', 'Canceled']:
                raise UserError('can`t delete property which status is New or Canceled')
        return super().unlink()

修改odoo14\custom\estate\models\estate_property_offer.py,匯入UserError

from odoo.exceptions import UserError

最末尾新增一下程式碼

    @api.model
    def create(self, vals):
        property = self.env['estate.property'].browse(vals['property_id'])
        
        if vals.get('price') < property.best_price:
            raise  UserError('不能低於現有報價')
        property.state = 'Offer Received'
        return super().create(vals)

重啟服務,重新整理瀏覽器驗證

刪除非NewCanceled狀態的房產,提示如下:

模組繼承(Model Inheritance)

參照: 檢視主題相關檔案繼承和擴充套件

我們希望在「Settings/Users & Companies/Users」表單檢視中直接顯示與銷售人員關聯的房產列表。為此,我們需要向res.users模型新增一個欄位,並調整其檢視以顯示它。

Odoo提供了兩種繼承機制來以模組化的方式擴充套件現有模型。

第一繼承機制允許模組通過以下方式修改在另一個模組中定義的模型的行為:

  • 向模型新增欄位

  • 覆蓋模型中欄位的定義

  • 給模型新增約束

  • 給模型新增方法

  • 重寫模型中的現有方法

第二種繼承機制(委託)允許將模型的每個記錄連結到父模型的記錄,並提供對該父記錄的欄位的透明存取。

odoo中,第一種機制最常用。在我們的例子中,我們希望向現有模型新增一個欄位,這意味著我們將使用第一種機制。例如:

from odoo import fields, models

class InheritedModel(models.Model):
    _inherit = "inherited.model"

    new_field = fields.Char(string="New Field")

這裡可以找到將兩個欄位新增到模型中的範例

class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    vehicle_id = fields.Many2one('fleet.vehicle', string='Vehicle')
    need_vehicle = fields.Boolean(compute='_compute_need_vehicle',
        help="Technical field to decide whether the vehicle_id field is editable")

    def _compute_need_vehicle(self):
        self.need_vehicle = False

按照慣例,每個繼承的模型都在其自己的Python檔案中定義。在我們的範例中為「models/inherited_model.py」。

練習--新增欄位到使用者模型

  • 新增一下欄位到res.users:
Field Type
property_ids One2many inverse of salesman_id to estate.property
  • 新增一個domain到該欄位,這樣以便僅顯示可獲取房產。

新增odoo14\custom\estate\models\estate_res_user.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from odoo import models, fields

class EstateResUser(models.Model):
    _inherit = 'res.users'

    property_ids = fields.One2many('estate.property', 'salesman_id', domain="[('salesman_id', '=', active_id)]")

修改odoo14\custom\estate\models\__init__.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import estate_property
from . import estate_res_user # 本次新增

檢視繼承(View Inheritance)

參考: 主題關聯檔案可檢視Inheritance.

目標: 在使用者表單檢視中顯示與銷售人員關聯的avaliable房產列表其使用者表單檢視

Odoo提供了檢視繼承,其中子「擴充套件」檢視應用於根檢視之上,而不是就地修改現有檢視(通過重寫它們)。這些擴充套件既可以新增內容,也可以從父檢視中刪除內容。

擴充套件檢視使用inherit_id欄位參照其父檢視。它的arch欄位包含多個xpath元素,用於選擇和更改父檢視的內容,而不是單個檢視:

<record id="inherited_model_view_form" model="ir.ui.view">
    <field name="name">inherited.model.form.inherit.test</field>
    <field name="model">inherited.model</field>
    <field name="inherit_id" ref="inherited.inherited_model_view_form"/>
    <field name="arch" type="xml">
        <!-- find field description and add the field
             new_field after it -->
        <xpath expr="//field[@name='description']" position="after">
          <field name="new_field"/>
        </xpath>
    </field>
</record>
  • expr

    一個用於選擇父檢視中單個元素的XPath表示式。如果不匹配任何元素或者匹配多個元素,則丟擲錯誤

  • position

    應用於匹配元素的操作:

    inside

    xpath的主體附加到匹配元素的末尾(個人理解,新增為匹配元素的子元素)

    replace

    將匹配元素替換為xpath的主體,將新主體中出現的任何$0節點替換為原始元素

    before

    在匹配元素之前插入xpath的主體作為同級元素

    after

    在匹配的元素之後插入xpaths的主體,作為同級元素

    attributes

    使用xpath主體中的特定屬性元素更改匹配元素的屬性

當匹配單個元素時,可以直接在要查詢的元素上設定position屬性。以下兩種繼承都有相同的結果

<xpath expr="//field[@name='description']" position="after">
    <field name="idea_ids" />
</xpath>

<field name="description" position="after">
    <field name="idea_ids" />
</field>

這裡可以找到檢視繼承擴充套件的範例

<?xml version='1.0' encoding='utf-8'?>
<odoo>
    <record id="view_move_form" model="ir.ui.view">
        <field name="name">account.move.form</field>
        <field name="model">account.move</field>
        <field name="inherit_id" ref="account.view_move_form"/>
        <field name="arch" type="xml">
            <xpath expr="//field[@name='line_ids']//field[@name='account_id']" position="after">
                <field name='need_vehicle' invisible='1'/>
                <field name='vehicle_id' attrs="{'required': [('need_vehicle', '=', True), ('parent.move_type', '=', 'in_invoice')], 'column_invisible': [('parent.move_type', '!=', 'in_invoice')]}" optional='hidden'/>
            </xpath>
            <xpath expr="//field[@name='invoice_line_ids']//field[@name='account_id']" position="after">
                <field name='need_vehicle' invisible='1'/>
                <field name='vehicle_id' attrs="{'required': [('need_vehicle', '=', True), ('parent.move_type', '=', 'in_invoice')], 'column_invisible': [('parent.move_type', '!=', 'in_invoice')]}" optional='hidden'/>
            </xpath>
        </field>
    </record>
</odoo>

練習--新增欄位到使用者檢視

新增property_ids欄位到 base.view_users_form 中新建的notebook

提示: 可以在 這裡找到繼承使用者檢視的範例。

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>

    <record id="res_users_view_form" model="ir.ui.view">
        <field name="name">res.users.view.form.inherit.gamification</field>
        <field name="model">res.users</field>
        <field name="inherit_id" ref="base.view_users_form"/>
        <field name="arch" type="xml">
            <group name="messaging" position="inside">
                <field name="karma"/>
            </group>
        </field>
    </record>

</data>
</odoo>

新增odoo14\custom\estate\views\estate_res_users_views.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
    <record id="estate_res_users_view_form" model="ir.ui.view">
        <field name="name">estate.res.users.view.form</field>
        <field name="model">res.users</field>
        <field name="inherit_id" ref="base.view_users_form"/>
        <field name="arch" type="xml">
            <xpath expr="//page[@name='references']" position="after">
                <page string="Real Estate Properties" name="RealEstateProperties">
                    <field name='property_ids'/>
                </page>
            </xpath>
        </field>
    </record>
</data>
</odoo>

修改odoo14\custom\estate\__manifest__.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
    'name': 'estate',
    'depends': ['base'],
    'data':['security/ir.model.access.csv',
            'views/estate_property_views.xml',
            'views/estate_property_type_views.xml',
            'views/estate_property_tag_views.xml',
            'views/estate_property_offer_views.xml',
            'views/estate_menus.xml',
            'views/estate_res_users_views.xml' # 本次新增
            ]
}

重啟服務,驗證效果