odoo12版本學習
一·odoo簡介
odoo是快速開發ERP系統的框架,適合商用. 內置crud,豐富的組件:看板,日歷,圖表.
odoo采用mvc架構模式. m即model,數據層, v及view,視圖層(展示層),c即controller,邏輯層
odoo結構:
數據層:
持久化層,負責存儲.odoo借用PostgreSQL來實現. 不支持MySQL數據(可借用的第三方集成MySQL)
文件附件,圖片一類的二進制存儲在filestore目錄下
邏輯層
負責與數據層交互 . odoo核心代碼種,提供了ORM引擎,ORM提供插件模塊與數據交互的API
展示層
展示數據與用戶交互, 自帶web客戶端.
包含CMS框架,支持靈活創建網頁
二·odoo環境配置
本實驗采用ubuntu系統
步驟一
安裝PostgreSQL數據庫
sudo apt update
sudo apt install postgresql -y # 安裝PostgreSQL
sudo su -c "createuser -s $USER" postgres # 創建數據庫超級用戶
步驟二
安裝python3環境,以及其他依賴
sudo apt update
sudo apt upgrade
# 安裝Git
sudo apt install git -y
# 安裝python3
sudo apt install python3-dev python3-pip -y # Python 3 for dev
# 安裝依賴
sudo apt install build-essential libxslt-dev libzip-dev libldap2-dev libsasl2-dev libssl-dev -y
# 安裝Node.js和包管理器
sudo apt install npm
sudo ln -s /usr/bin/nodejs /usr/bin/node # 通過node運行Node.js
sudo npm install -g less less-plugin-clean-css # 安裝less
步驟三
安裝odoo源碼
1.在 Home 目錄創建工作目錄
# 創建工作目錄
mkdir ~/odoo-dev
# 進入工作目錄
cd ~/odoo-dev
2.克隆odoo12版本源碼
git clone https://github.com/odoo/odoo.git -b 12.0 --depth=1 # 獲取 Odoo 源碼
3.安裝odoo所需的依賴
pip3 install -r ~/odoo-dev/odoo/requirements.txt
4.安裝odoo其他依賴包
pip3 install num2words phonenumbers psycopg2-binary watchdog xlwt
步驟四
啟動odoo服務
~/odoo-dev/odoo/odoo-bin
# 默認監聽端口8069 ,訪問 http://localhost:8069
步驟五
管理odoo數據庫
# 由於采用的是psql數據庫,需要手動創建
1.創建PostgreSQL數據庫
createdb MyDB
2.創建odoo數據庫
createdb --template=MyDB MyDB2
3.刪除數據庫 # dropdb是不可撤銷的,永久性刪除
dropdb MyDB2
# 訪問odoo客戶端 頁面會出現以下內容
Database Name:數據庫的標識名稱,在同一台服務器上可以有多個數據庫
Email:管理員的登錄用戶名,可以不是 email 地址
Password:管理員登錄的密碼
Language:數據庫的默認語言
Country:數據庫中公司數據所使用的國家,這個是可選項,與發票和財務等帶有本地化特征的應用中會用到
Demo data:勾選此選項會在數據庫中創建演示數據,通常在開發和測試環境中可以勾選
## 服務端添加了master password , 要求輸入密碼,目的是阻止未經授權的管理員操作
odoo其他配置
### 修改監聽的端口 , 即可運行多個odoo實例
~/odoo-dev/odoo/odoo-bin --http-port=8070
~/odoo-dev/odoo/odoo-bin --http-port=8071
### 數據庫選項
Odoo 開發時,經常會使用多個數據庫,有時還會用到不同版本。在同一端口上停止、啟動不同服務實例,或在不同數據庫間切換,會導致網頁客戶端會話異常。因為瀏覽器會存儲會話的 Cookie。
# 它接收一個正則表達式來過濾可用數據庫名,要精確匹配一個名稱,表達式需要以^開頭並以$結束
~/odoo-dev/odoo/odoo-bin --db-filter=^testdb$
### 管理服務器日志消息
–log-level=debug參數
參數如下:
debug_sql:查看服務中產生的 SQL 查詢
debug_rpc:服務器接收到的請求詳情
debug_rpc_answer:服務器發送的響應詳情
安裝第三方插件
# Odoo應用商店可以下載一系列模塊安裝到系統中, 為 Odoo 添加模塊,僅需將其拷貝到官方插件的 addons 文件夾中即可
例子:
# 1.拷貝library模塊
cd ~/odoo-dev
git clone https://github.com/alanhou/odoo12-development.git library
# 2.配置插件(add-ons)路徑
"""
Odoo 服務有一個addons_path參數可設置查找插件的路徑,默認指向Odoo 服務所運行處的/addons文件夾。我們可以指定多個插件目錄,這樣就可以把自定義模塊放到另一個目錄下,無需與官方插件混到一起。
"""
cd ~/odoo-dev/odoo
./odoo-bin -d 12-library --addons-path="../library,./addons"
激活開發者模式
# 方式一 在地址欄添加參數
未添加: http://127.0.0.1:8069/web#
添加: http://127.0.0.1:8069/web?debug=1#
# 方式二
找到設置(Settings),最底下有激活開發者模式選項
三·odoo模塊結構
# 原博客地址:
https://alanhou.org/odoo12-first-application/
#### 在odoo開發中,一個應用就相當於是一個模塊
分析模塊結構
# odoo 的結構
- modelname
- controllers
- __init__.py
- main.py
- i18n #翻譯文件
- zh-cn.po # po翻譯文件
- data # 數據文件
- book_demo.xml
- library.book.csv
- models # 模型
- __init__.py
- library_book.py
- reports # 報表
- __init__.py
- library_book_report.py
- library_book_report.xml
- security # 安全 權限
- ir.model.access.csv
- library_security.xml
- static # 靜態資源
- src
- js
- css
- tests # 測試
- __init__.py
- test_book.py
- views # 展示視圖
- book_list_template.xml
- __init__.py
- __manifest__.py # 模塊信息描述文件
四·創建應用
添加頂級菜單
菜單項是使用 XML 文件中添加的視圖組件,通過創建views/library_menu.xml來定義菜單項:
<?xml version="1.0"?>
<odoo>
<!-- Library App Menu -->
<menuitem id="menu_library" name="Library" />
</odoo>
需要在__manifest__.py
中使用 data 屬性來添加安裝或更新時需要加載的模塊列表
'data': [
'views/library_menu.xml',
],
添加權限組
Odoo 中使用安全組來實現,權限授予組,組中分配用戶。Odoo 應用通常有兩個組:針對普通用戶的用戶組,包含額外應用配置權限的管理員組。
權限安全相關的文件通常放在模塊下/security子目錄中,創建security/library_security.xml 文件來進行權限定義
<?xml version="1.0" ?>
<odoo>
<record id="module_library_category" model="ir.module.category">
<field name="name">Library</field>
</record>
</odoo>
添加兩個安全組,首先添加用戶組
<?xml version="1.0" ?>
<odoo>
<record id="module_library_category" model="ir.module.category">
<field name="name">Library</field>
</record>
<!-- 加入安全組 Library User Group -->
<record id="library_group_user" model="res.groups">
<field name="name">User</field>
<field name="category_id" ref="module_library_category" />
<field name="implied_ids" eval="[(4, ref('base.group_user'))]" />
</record>
</odoo>
### 字段說明
name: 組名
category_id:關聯應用,這是一個關聯字段,因此使用了 ref 屬性來通過 XML ID 連接已創建的分類
implied_ids:這是一個one-to-many關聯字段,包含一系列組來對組內用戶生效。這里使用了一個特殊語法,
創建管理員組,授予用戶組的所有權限以及為應用管理員保留的其它權限
<?xml version="1.0" ?>
<odoo>
<record id="module_library_category" model="ir.module.category">
<field name="name">Library</field>
</record>
<!-- 添加用戶組 Library User Group -->
<record id="library_group_user" model="res.groups">
<field name="name">User</field>
<field name="category_id" ref="module_library_category" />
<field name="implied_ids" eval="[(4, ref('base.group_user'))]" />
</record>
<!--創建管理員組 Library Manager Group -->
<record id="library_group_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="module_library_category" />
<field name="implied_ids" eval="[(4, ref('library_group_user'))]" />
<field name="users" eval="[
(4, ref('base.user_root')),
(4, ref('base.user_admin'))
]" />
</record>
</odoo>
同樣需要在聲明文件中添加該 XML 文件:
'data': [
'security/library_security.xml',
'views/library_menu.xml',
],
添加自動化測試
# 1.測試應放在tests/子目錄中,在tests/__init__.py
from . import test_book
# 2.在tests/test_book.py文件中添加實際的測試代碼:
from odoo.tests.common import TransactionCase
class TestBook(TransactionCase):
def setUp(self, *args, **kwargs):
result = super().setUp(*args, **kwargs)
self.Book = self.env['library.book']
self.book_ode = self.Book.create({
'name': 'Odoo Development Essentials',
'isbn': '879-1-78439-279-6'})
return result
def test_create(self):
"Test Books are active by default"
self.assertEqual(self.book_ode.active, True)
模型層
創建數據模型
1. 在模塊主__init__.py文件添加
from . import models
2. 在models/__init__.py文件種引入模型
from . import library_book
# 創建模型 models/library_book.py
from odoo import fields, models
class Book(models.Model):
_name = 'library.book' # 在視圖中能夠引用到
_description = 'Book' # 模型描述
name = fields.Char('Title', required=True)
isbn = fields.Char('ISBN')
active = fields.Boolean('Active?', default=True)
date_published = fields.Date()
image = fields.Binary('Cover')
publisher_id = fields.Many2one('res.partner', string='Publisher')
author_ids = fields.Many2many('res.partner', string='Authors')
#### odoo數據庫基本字段類型
Binary:二進制類型,用於保存圖片、視頻、文件、附件等,在視圖層顯示為一個文件上傳按鈕。【Odoo底層對該類型字段的容量作了限制,最多能容納20M內容】
Char:字符型,size屬性定義字符串長度。
Boolean:布爾型
Float:浮點型,如 rate = fields.float('Relative Change rate',digits=(12,6)), digits定義數字總長和小數部分的位數。
Integer:整型
Date:短日期,年月日,在view層以日歷選擇框顯示。
Datetime:時間戳。
Text:文本型,多用於多行文本框,可以用widget屬性為它添加樣式。
Html:與text類似,用於多行文本編輯,不過自帶編輯器樣式,並且會把內容以html解析。
Selection:下拉列表,枚舉類型。
#### 關聯字段類型
one2one: 一對一關系。 在V5.0以后的版本中不建議使用,而是用many2one替代。
格式:
fields.one2one(關聯對象Name, 字段顯示名, ... )
many2one: 多對一關系
格式:
fields.many2one(關聯對象Name, 字段顯示名, ... )
參數:
comodel_name(string) -- 目標模型名稱,除非是關聯字段否則該參數必選
domain -- 可選,用於在客戶端篩選數據的domain表達式
context -- 可選,用於在客戶端處理時使用
ondelete -- 當所引用的數據被刪除時采取的操作,取值:'set null', 'restrict', 'cascade'
auto_join -- 在搜索該字段時是否自動生成JOIN條件,默認False
delegate -- 設置為True時可以通過當前model訪問目標model的字段,與_inherits功能相同
one2many: 一對多關系
格式:
fields.one2many(關聯對象Name, 關聯字段, 字段顯示名, ... )
參數:
comodel_name -- 目標模型名稱,
inverse_name -- 在comodel_name 中對應的Many2one字段
domain -- 可選,用於在客戶端篩選數據的domain表達式
context -- 可選,用於在客戶端處理時使用
auto_join -- 在搜索該字段時是否自動生成JOIN條件,默認False
limit(integer) -- 可選,在讀取時限制數量
many2many: 多對多關系
格式: (生成第三表: ...._ref表)
'category_id'=fields.many2many('res.partner.category','res_partner_category_rel','partner_id','category_id','Categories')
# 表示以多對多關系關聯到對象res.partner.category,關聯表為'res_partner_category_rel',關聯字段為 'partner_id'和'category_id'。
# 當定義上述字段時,OpenERP會自動創建關聯表為 'res_partner_category_rel',它含有關聯字'partner_id'和'category_id'
參數:
comodel_name -- 目標模型名稱,除非是關聯字段否則該參數必選
relation -- 關聯的model在數據庫存儲的表名,默認采用comodel_name獲取數據
column1 -- 與relation表記錄相關聯的列名
column2 --與relation表記錄相關聯的列名
domain -- 用於在客戶端篩選數據的domain表達式
context -- 用於在客戶端處理時使用
limit(integer) --在讀取時限制數量
#### 引用類型
related字段
格式:
字段=fields.類型(related="某個字段.類字段",store=true/false)
# related字段可以簡記為“帶出字段”,由當前模型的某個關聯類型字段的某個字段帶出值。
reference字段
reference是比related更高級的引用字段,可以指定該字段引用那些模型范圍內的模型的哪些字段的值,范圍更廣。
#### Odoo保留字段
name(Char) -- _rec_name的默認值,在需要用來展示的時候使用
active(Boolean) -- 設置記錄的全局可見性,當值為False時通過search和list是獲取不到的
sequence(Integer) -- 可修改的排序,可以在列表視圖里通過拖拽進行排序
state(Selection) -- 對象的生命周期階段,通過fileds的states屬性使用
parent_id(Many2one) -- 用來對樹形結構的記錄排序,並激活domain表達式的child_of運算符
parent_left,parent_right -- 與 _parent_store結合使用,提供更好的樹形結構數據讀取
#### 自動化屬性
id Identifier field 是模型中每條記錄的唯一數字標識符
_log_access 是否創建日期字段,默認創建(default:True)
create_date 記錄創建日期 Type:Datetime
create_uid 第一創建人 Type:res.users
write_date 最后一次修改日期 Type:Datetime
write_uid 最后一次修改人 Type:res.users
_last_update 最后一次修改日期 它不存儲在數據庫,用於做並發檢測
display_name 對外顯示的名稱
# 不想在model自動添加這些屬性 ,在類種添加:
_log_access=False
#### Compute字段
ompute字段不是一種字段類型,而是指某個字段的值是計算出來的。
一個字段的值,可以通過一個函數來動態計算出來。定義格式如下:
字段名=fields.類型(compute="函數名",store=True/false) #store定義了該動態改變的字段值是否保存到數據庫表中
@api.depends(依賴的字段值)#depend的字段值一旦發生變化,就會觸發該函數,從而更新compute字段值。
def 函數(self):
self.字段=計算字段值
# 原博客https://www.cnblogs.com/ygj0930/p/10826099.html
設置訪問權限
添加訪問權限控制
權限通過security/ir.model.access.csv文件來實現,添加該文件並加入如下內容:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_book_user,BookUser,model_library_book,library_group_user,1,0,0,0
access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1
注解:
# 1. 應注意該文件第一行后不要留有空格,否則會導致報錯
# 2. csv列解讀
id 是記錄的外部標識符(也稱為XML ID),需在模塊中唯一
name 是描述性標題,僅在保證唯一時提供有用信息
model_id 是賦權模型的外部標識符,模型有ORM自動生成的XML ID,對於library.book,標識符為model_library_book
group_id 指明授權的安全組,我們給前文創建的安全組授權:library_group_user和library_group_manager
perm_… 字段標記read讀, write寫, create創建, 或unlink刪除權限,我們授予普通用戶讀權限、管理員所有權限
# 3. __manifest__.py的 data 屬性中添加對新文件的引用
'data': [
'security/library_security.xml',
'security/ir.model.access.csv',
'views/library_menu.xml',
],
行級權限規則
添加記錄規則,需編輯security/library_security.xml文件
記錄規則在ir.rule中定義,和往常一樣我們選擇一個唯一名稱。還應獲取操作的模型及使用權限限制的域過濾器。域過濾器使用 Odoo 中常用的元組列表,添加域表達式.
<data noupdate="1">
<record id="book_user_rule" model="ir.rule">
<field name="name">Library Book User Access</field>
<field name="model_id" ref="model_library_book" />
<field name="domain_force">
[('active','=',True)]
</field>
<field name="groups" eval="[(4,ref('library_group_user'))]" />
</record>
</data>
視圖層
視圖類別:
tree視圖,form表單視圖,search搜索視圖 等
添加菜單項
添加相應菜單項。編輯views/library_menu.xml文件,在 XML 元素中定義菜單項以及執行的操作:
<!-- Action to open the Book list -->
<act_window id="action_library_book"
name="Library Books"
res_model="library.book"
view_mode="tree,form"
/>
<!-- Menu item to open the Book list -->
<menuitem id="menu_library_book"
name="Books"
parent="menu_library"
action="action_library_book"
/>
注釋:
1.<act_window> 元素定義客戶端窗口操作,按找順序通過啟用列表和表單視圖打開library.book模型
2.<menuitem> 定義一個調用 action_library_book操作的定義菜單
# act 是行為動作, menu是菜單. 頁面上生成的菜單-->行為動作指向action
創建表單視圖
添加views/book_view.xml文件來定義表單視圖:
<?xml version="1.0"?>
<odoo>
<record id="view_form_book" model="ir.ui.view">
<field name="name">Book Form</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<form string="Book">
<group>
<field name="name" />
<field name="author_ids" widget="many2many_tags" />
<field name="publisher_id" />
<field name="date_published" />
<field name="isbn" />
<field name="active" />
<field name="image" widget="image" />
</group>
</form>
</field>
</record>
</odoo>
業務文件表單視圖
header 和 sheet將form表單分成 兩部分
header部分包含:可操作的button按鈕, 狀態欄
sheet部分包含: 數據列
<form string="Book">
<header>
<!-- 此處添加按鈕 -->
<button name="button_check_isbn" type="object"
string="Check ISBN" />
</header>
<sheet>
<group>
<field name="name" />
...
</group>
</sheet>
</form>
組來組織表單
<group>
標簽,用來組織表單視圖的布局
推薦在group 元素中添加 name 屬性,更易於其它模塊對其進行繼承
<sheet>
<!-- 分組展示 -->
<group name="group_top">
<!-- 左側部分展會的字段 -->
<group name="group_left">
<field name="name" />
<field name="author_ids" widget="many2many_tags" />
<field name="publisher_id" />
<field name="date_published" />
</group>
<!-- 右側部分展示的字段 -->
<group name="group_right">
<field name="isbn" />
<field name="active" />
<field name="image" widget="image" />
</group>
</group>
</sheet>
注釋
# 1. ir.ui.view 和record是固定搭配
# 2. name 視圖名稱 , id 當前視圖唯一的xml ID標識符 , model依賴的模型 arch form視圖固定搭配
# 3. 視圖具有繼承視圖,即:在原有的視圖上添加新的字段,新的展示內容
# 4. __manifest__.py 的 data中聲明
'views/book_view.xml',
列表視圖
列表視圖即 : <tree>
視圖 (初代結構)
<record id="view_tree_book" model="ir.ui.view">
<field name="name">Book List</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="author_ids" widget="many2many_tags" />
<field name="publisher_id" />
<field name="date_published" />
</tree>
</field>
</record>
搜索視圖
<search>
搜索視圖
<record id="view_search_book" model="ir.ui.view">
<field name="name">Book Filters</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<search>
<!-- 可搜索的字段值 -->
<field name="publisher_id" />
<!-- domain 過濾條件 -->
<filter name="filter_active"
string="Active"
domain="[('active','=',True)]" />
<filter name="filter_inactive"
string="Inactive"
domain="[('active','=',False)]" />
</search>
</field>
</record>
業務邏輯層
業務邏輯層編寫應用的業務規則,如驗證和自動計算。
添加業務邏輯
# 模型中有isbn字段, 上文在表單視圖中添加了button按鈕 name為:button_check_isbn . 現在為其添加上業務校驗邏輯
# 在models的 Book 模型中添加 校驗邏輯
#1. 檢驗 isbn有效行
def _check_isbn(self):
self.ensure_one()
isbn = self.isbn.replace('-', '') # 為保持兼容性 Alan 自行添加
digits = [int(x) for x in isbn if x.isdigit()]
if len(digits) == 13:
ponderations = [1, 3] * 6
terms = [a * b for a,b in zip(digits[:12], ponderations)]
remain = sum(terms) % 10
check = 10 - remain if remain !=0 else 0
return digits[-1] == check
# 2. 按鈕點擊時觸發此方法 button_check_isbn 方法與button的name必須同名
from odoo import api, fields, models
from odoo.exceptions import Warning
def button_check_isbn(self):
for book in self:
if not book.isbn:
raise Warning('Please provide an ISBN for %s' % book.name)
if book.isbn and not book._check_isbn():
raise Warning('%s is an invalid ISBN' % book.isbn)
return True
總結:
1.在處理業務邏輯是,添加異常后,odoo自動做事務回滾.
2.業務邏輯一般放在models定義的模型類中 , 本身是由orm去操作的,需要成熟面向對象思想
網頁和控制器
在頁面上響應數據
# 1. 在library_app/__init__.py導入控制器模塊目錄加入控制器
from . import models
from . import controllers
# 2.創建main.py,在library_app/controllers/__init__.py文件來讓目錄可被 Python 導入
from . import main
# 3.定義代碼
from odoo import http
class Books(http.Controller):
@http.route('/library/books', auth='user')
def list(self, **kwargs):
Book = http.request.env['library.book']
books = Book.search([])
return http.request.render(
'library_app.book_list_template', {'books':books})
### 注釋:
# 1. http.request.env獲取環境,從目錄中獲取有效圖書記錄集
# 2. http.request.render() 來處理 library_app.index_template Qweb 模板並生成輸出 HTML
# 3. auth 制定訪問的授權模式
# 4. {'books':books} book圖書集合傳遞到Qweb中
QWeb模板是視圖類型
放在/views子目錄下,創建views/book_list_template.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="book_list_template" name="Book List">
<div id="wrap" class="container">
<h1>Books</h1>
<t t-foreach="books" t-as="book">
<div class="row">
<span t-field="book.name" />,
<span t-field="book.date_published" />,
<span t-field="book.publisher_id" />
</div>
</t>
</div>
</template>
</odoo>
注釋:
1.<template>
2. t-foreach用於遍歷變量 books的每一項
3. t-field用於渲染記錄字段的內容