結(jié)構(gòu)化應(yīng)用數(shù)據(jù)
在前面的章節(jié)中,我們基本接觸了建造Odoo后臺應(yīng)用的所有層面。下面我們就要具體分析‘model,views,business logic’這些組成Odoo應(yīng)用的不同層面的細(xì)節(jié)。
首先我們來學(xué)習(xí)如何設(shè)計支持一個應(yīng)用的數(shù)據(jù)的結(jié)構(gòu)組成以及如何表達(dá)這些數(shù)據(jù)之間的聯(lián)系。
組織應(yīng)用的功能到模塊中
首先,我們需要使用一個例子來幫我們理解這個概念。
Odoo的繼承功能提供了有效的拓展機(jī)制。它允許我們不直接修改源代碼來拓展已經(jīng)存在的第三方應(yīng)用。這個可組合性還確保了面向?qū)ο蟮拈_發(fā)模式,大的應(yīng)用能夠被分成各種小功能。這種避免復(fù)雜性的方法從技術(shù)角度跟用戶體驗角度看來都是非常有用的。所以我們通過添加額外的功能模塊來改進(jìn)我們的To-Do 應(yīng)用。最后讓它變成一個具有豐富功能的大應(yīng)用。
介紹我們將要創(chuàng)建的 todo_ui 模塊
在前面的章節(jié)中,我們首先創(chuàng)建了一個個人版的to-do task模塊,然后通過繼承把它擴(kuò)展為多用戶可以共享的應(yīng)用。
現(xiàn)在,我們想要提升用戶界面,增加一個看板板塊,看板板塊是一個簡單的工作流工具,它把主題放在一個個列中,我們可以把這些主題從左邊的列一步步傳入右邊的列,知道它們完成了整個工作流?,F(xiàn)在,我們組織我們的任務(wù)放入以Waiting,Ready,Started,Done這樣的階段(Stages)為列名的視圖中。
我們首先通過添加數(shù)據(jù)結(jié)構(gòu)來保證這個視覺效果的底層實(shí)現(xiàn)。添加階段(Stages),讓這個階段模型支持標(biāo)簽,以便我們能夠根據(jù)主題來對tasks進(jìn)行分類。我們在本章中只關(guān)注模型的數(shù)據(jù)結(jié)構(gòu)。關(guān)于用戶界面的討論會放到下一章來講解。
在設(shè)計支持模型時,首要的事就是理解數(shù)據(jù)的結(jié)構(gòu)組成。
我們現(xiàn)在已經(jīng)有了最中心的模型實(shí)體—— To-do Task
然后需要創(chuàng)建一個Stage模型跟一個tag模型。它們與我們的task模型之間有如下關(guān)系:
- 一個task任務(wù)只能在一個stage中,而一個stage里可以同時存在多個task任務(wù) ‘Many2one’
- 一個task任務(wù)能有許多tags,而一個標(biāo)簽也能匹配到多個tasks ‘Many2Many’
下面通過創(chuàng)建新的‘todo_ui’模塊來添加 To-do Stages 跟To-do Tags 模型。還是用'custom-addons'文件夾來存放我們的'todo_ui'
mkdir todo_ui
cd todo_ui
touch _init__.py
vim __manifest__.py
添加下面的內(nèi)容
{
'name': 'User interface improvements tp the To-Do app',
'description': 'User friendly features.',
'author': 'xer',
'depends': ['todo_user'],
}
在Odoo中安裝這個新模塊
創(chuàng)建新模型
為了讓我們的to-do tasks擁有看板板塊,我們需要使用Stages。Stages 代表了板塊中的列。每個task都會填充到這些列里面。
- 編輯
__init__.py來導(dǎo)入models包
`from . import models - 創(chuàng)建
todo_ui/models文件夾,在其下添加init.py,讓它變?yōu)橐粋€python包文件.from . import todo_model` - 然后創(chuàng)建
todo_model.py來定義我們的新模型
from odoo import api,models,fields
class Tag(models.Model):
_name = 'todo.task.tag'
_description = 'To-do Tag'
name = fields.Char('Name',40, translate=True)
class Stage(models.Model):
_name = 'todo.task.stage'
_description = 'To-do Stage'
_order = 'sequence,name'
name = fields.Char('Name',40, translate=True)
sequence = fields.Integer('Sequence')
上面的代碼我們定義2個與to-do tasks相關(guān)聯(lián)的新模型。
注意到 task stages, 我們有個繼承models.Model的python類==>Stage.這定義了一個新的名為todo.task.stage的Odoo模型.
模型屬性
模型類中可以使用附加的屬性來控制模型的行為,下面是那些最常使用的屬性:
-
_name:是一個用來標(biāo)識我們新創(chuàng)建的Odoo模型的標(biāo)識符 -
_description: 不是必須的,它表示用戶在用戶界面看到的用來描述我們模型的標(biāo)題記錄. -
_order: 設(shè)置了當(dāng)模型的記錄被瀏覽時的默認(rèn)順序。就是在底層數(shù)據(jù)庫對模型中的記錄排序時添加了一條order by的SQL子句。 -
_rec_name: 在關(guān)聯(lián)關(guān)系中,指定了用來描述記錄的字段.默認(rèn)在Many2one關(guān)系中使用了name字段的值顯示記錄 -
_table: Odoo數(shù)據(jù)庫會自動生成相關(guān)的數(shù)據(jù)表名來存放我們定義的模型,默認(rèn)是通過把.改為_來作為數(shù)據(jù)庫表名的,通過這個屬性可以自定義數(shù)據(jù)庫表名. -
_inherit跟_inherits屬性.作為繼承用法.詳請參見第三章.
模型跟Python類.
Odoo模型被存放在中心注冊處,在老版本的Odoo中,可以通過pool來獲取到.pool是一個字典,通過模型的名稱保存著與所有Odoo模型實(shí)例之間的聯(lián)系.我們可以使用self.env['x']來獲取到‘x’這個模型代表類。
模型的名稱是十分重要的因為它們用作鍵來獲取在中心注冊處注冊過的模型。模型的起名規(guī)范是使用一個以.為分隔符的小寫字符串,例如todo.task.stage.
Python類的命名并無嚴(yán)格限制,符合PEP8,按照駝峰命名法即可.
臨時跟抽象模型
在大多數(shù)情況中,我們編寫的python模型類都是繼承自O(shè)doo的models.Model.Odoo另外提供了2個模型
- Transient models:這個模型基于
models.TransientModel類,一般使用在導(dǎo)航形式的用戶體驗類中.臨時模型的數(shù)據(jù)是臨時存儲在數(shù)據(jù)庫中的.舊的數(shù)據(jù)會被新的數(shù)據(jù)直接取代。(這是提高效率的做法)舉例來說,Settings | Translation | Load a Language對話窗口,使用了臨時模型來存儲用戶的語言選擇。 - Abstract models:基于
models.AbstractModel.它沒有存儲數(shù)據(jù)。在Odoo繼承機(jī)制中作為一個混合類來增加新的功能。例如我們使用過的mail.thread模型.
檢查已存在的模型
打開 Settings |Technical | Database Structure | Models。這里保存了所有的‘models’。

創(chuàng)建字段
Oodd中的字段主要分為基礎(chǔ)字段跟關(guān)系型字段
-
基礎(chǔ)字段類型
我們在我們剛創(chuàng)立的‘Stage’模型中添加如下字段
class Stage(models.Model):
_name = 'todo.task.stage'
_description = 'To-do Stage'
_order = 'sequence,name'
name = fields.Char('Name')
desc = fields.Text('Description')
state = fields.Selection([('draft', 'New'), ('open', 'Started'), ('done', 'Closed')], 'State')
docs = fields.Html('Documentation')
sequence = fields.Integer('Sequence')
perc_complete = fields.Float('% Complete', (3, 2))
date_effective = fields.Date('Effective Date')
date_changed = fields.Date('Last Changed')
fold = fields.Boolean('Folded?')
image = fields.Binary('Image')
- Char : 字符型,size屬性定義長度
- Text :文本型,沒有長度限制
-
Selection : 下拉列表,在繼承中,可以使用
selection_add來擴(kuò)展已經(jīng)存在的下拉列表 - Html : 文本型,在用戶層面可以對其做特殊處理
- Integer:整數(shù)型
- Float: 浮點(diǎn)型,屬性digits是一個元組(x,y)。x定義總長度,y定義小數(shù)部分位數(shù)
- Date:短日期,年月日,在vies層通過日歷選擇框顯示。
- Datetime :時間戳
- Boolean : 布爾類型
- Binary :二進(jìn)制數(shù)據(jù),在視圖層顯示為一個文件上傳按鈕,可以把圖片,音頻,視頻,文檔以二進(jìn)制形式保存。
字段常見屬性:
- string :在視圖層運(yùn)用的字段的標(biāo)注,是第一個可選參數(shù),所有一般可以直接傳值,不需要使用字典參數(shù)。
- defalut :設(shè)置了一個字段默認(rèn)值,可以是一個固定的值,一個引用,一個函數(shù)名甚至一個簡單的lambda函數(shù)
- translate: 只在Char,Text,Html字段中使用。進(jìn)行翻譯。
- help:在用戶界面顯示幫助信息
- readonly=True:設(shè)置只讀屬性。
- required=True :必須屬性,相當(dāng)于在數(shù)據(jù)庫中添加了NOT NULL的限制
- index=True:創(chuàng)建索引
- copy=False:默認(rèn)的非關(guān)系型字段都是可以復(fù)制的
- groups:設(shè)置權(quán)限,使用groups的XML IDs作為值。
-
states:一個字典映射關(guān)系,依賴state字段的值,能使用的屬性為
readonly,required,invisible舉例:
states = {'done':[('readonly',True)]}
特殊名字的字段
- Odoo自動生成的字段名
- id: 自動生成的數(shù)字標(biāo)識,作為數(shù)據(jù)庫主鍵
- create_uid: 創(chuàng)建用戶
- create_data: 創(chuàng)建日期
- write_uid: 修改用戶
-
write_date: 修改日期
- Odoo中避免使用的字段名
- name:默認(rèn)用來展示模型名稱的。通常是Char字段。我們可以通過_rec_name這個模型屬性來使用其他字段作為顯示
- Active:是一個Boolean字段,用來在視圖層展示記錄。
- Sequence: Integer字段,在試圖層進(jìn)行排序用
- State: Selection字段,代表了記錄的狀態(tài)。可以用來動態(tài)的改變視圖層的展示
- parent_id, parent_left,parent_right: Integer字段,在父子繼承關(guān)系中有特殊意義.
-
關(guān)系型字段
以我們的模塊為例,我們有如下的模型之間的關(guān)系:
- 每個任務(wù)有一個階段: 多對一關(guān)系
-
每個任務(wù)有多個標(biāo)簽: 多對多關(guān)系
模型之間的關(guān)系
添加代碼:
class TodoTask(models.Model):
_inherit = 'todo.task'
stage_id = fields.Many2one('todo.task.stage', 'Stage')
tag_ids = fields.Many2many('todo.task.tag', string='Tags')
Many-to-one 關(guān)系:
Many2one的主要屬性:
- comodel:關(guān)聯(lián)的其他模型名稱
- string: 字符串名稱
-
ondelete: 定義了當(dāng)關(guān)聯(lián)模型記錄被刪除后的操作。默認(rèn)是
set null操作。restrict表示拋異常,防止誤刪,cascade表示本記錄也一同刪除. - context: 是一個字典,表示在視圖層傳遞信息。通??梢杂脕碓O(shè)置默認(rèn)值。
- domain:domain規(guī)則進(jìn)行過濾。
- aut_join=True: 允許使用SQL層面的join子句.
Many-to-many關(guān)系:
創(chuàng)立了第三張表來表示兩個多對多關(guān)系模型之間的具體映射,中間表的命名是自動的,規(guī)則是兩張模型表名之后加入'_rel'.但是當(dāng)表名太長超過psql規(guī)定的63個字符時,我們需要自己來定義表名.
我們有2中方法定義Many2many字段.通常使用字典參數(shù),因為這樣容易理解。舉例:
Task跟Tag模型之間創(chuàng)立關(guān)系:
在Task模型中.
tag_ids = fields.Many2many(
comodel_name='todo.task.tag',
relation='todo_task_tag_rel',
column1='task_id',
column2='tag_id',
string='Tags')
在Tag模型中.
task_ids = fields.Many2many('todo.task',string='Tasks')
One-to-many 關(guān)系。
Odoo中的One2many不能單獨(dú)存在, 是于Many2one相對應(yīng)的。實(shí)際上并沒什么意義,但是對于反向遍歷多對一關(guān)系時很有用處。
在Stage模型中,我們這樣定義一對多的存在:
tasks = fields.One2many('todo.task', 'stage_id', 'Tasks in this stage')
One2many字段接收3個可選參數(shù):
- 關(guān)聯(lián)模型----'todo.task'
- 關(guān)聯(lián)模型中的相關(guān)字段-----'stage_id' 表示與task中的'stage_id'字段互相對于
- 字符串標(biāo)題名稱
分層關(guān)系
父子樹關(guān)系結(jié)構(gòu)也可以看做是Many2one
關(guān)聯(lián)字段使用動態(tài)關(guān)系
在task模型中
如下:
refers_to = fields.Reference([('res.user', 'User'), ('res.partner', 'Partner')], 'Refers to')
我們可以直接通過Reference字段來讓task跟‘User’,‘Partner’建立關(guān)聯(lián)。Reference字段的定義與Selection比較相似。
計算字段
字段的值可以通過函數(shù)計算得到。計算字段的定義很簡單,跟普通字段一樣,只是在屬性中添加一個compute屬性.
舉例:
Stages模型中有一個fold字段,這個字段是一個布爾值,用來標(biāo)識是否折疊。在我們的TodoTask模型中對它的值進(jìn)行計算獲取。
stage_fold = fields.Boolean('Stage Folded?', compute='_compute_stage_fold',)
@api.depends('stage_id.fold')
def _compute_stage_fold(self):
for task in self:
task.stage_fold = task.stage_id.fold
上面的代碼在Task模型中添加了一個stage_fold字段,并且使用了_compute_stage_fold這個函數(shù)來對其計算取值.
@api.depends裝飾器中需要放入我們計算取值需要的字段.可以認(rèn)為是計算函數(shù)需要的參數(shù).
搜索跟編輯計算字段
我們剛才創(chuàng)建的計算字段并不能被搜索或者寫入數(shù)據(jù),只是一個可讀字段.為了增加這兩個操作.我們使用
- search : 為計算字段進(jìn)行搜索
- inverse : 為計算字段進(jìn)行寫入數(shù)據(jù).
stage_fold = fields.Boolean('Stage Folded?', compute='_compute_stage_fold',
search='_search_stage_fold',
inverse='_write_stage_fold'
)
def _search_stage_fold(self, operator, value):
return [('stage_id.fold', operator, value)]
def _write_stage_fold(self):
self.stage_id.fold = self.stage_fold
上面代碼中,search代表的函數(shù)表示該字段可以被搜索。inverse代表的函數(shù)表示改變stage_fold的值可以反向?qū)懭氲絪tage_id.fold字段中.
存儲計算字段.
在計算字段定義時添加屬性store = True即可.當(dāng)計算字段所依賴的數(shù)據(jù)改變時,它也會相應(yīng)的進(jìn)行更新.
依賴字段
剛才我們定義的計算字段其實(shí)就是在Task模型中把Stage的fold字段給復(fù)制了出來.我們使用依賴字段也可以實(shí)現(xiàn)這一操作.
定義這樣的字段跟普通字段一樣,唯一不同就是添加一個related屬性,related屬性的值通過'.'操作符來直接獲取我們需要的目標(biāo)字段.
stage_state = fields.Selection(related='stage_id.state', string='Stage State',store=True, inverse='_write_state')
就像上面的代碼。我們直接在Task模型中定義了依賴字段。直接獲取到了Stage中的state值。
模型限制。
為了防止非法數(shù)據(jù),Odoo支持2種模型數(shù)據(jù)限制:
- SQL限制:SQL限制的原理是通過Psql數(shù)據(jù)庫的數(shù)據(jù)庫表定義來防止非法數(shù)據(jù)的產(chǎn)生。
使用_sql_constraints這個類屬性來進(jìn)行定義。
它是一個元組列表,格式為 限制名稱,SQL限制條件,錯誤信息

- Python限制:
我們使用@api.constraints裝飾器來對需要限制的字段進(jìn)行規(guī)范限制。
舉例,我們的Task名字至少是要5個字符:
@api.constrains('name')
def _check_name_size(self):
for task in self:
if len(task.name)< 5:
raise ValidationError('Must have 5 chars!')
上面的代碼表示當(dāng)task的名字小于5個字符時,會拋出狀態(tài)異常。Odoo會直接在視圖界面發(fā)出警告。
