Odoo10 開發者文件(3)--建立一個模組·
警告
該教程需要 已安裝odoo
開啟/停止Odoo服務
odoo使用客戶端/伺服器架構,客戶端是通過RPC(遠端過程呼叫協議)訪問Odoo服務的web瀏覽器。
業務邏輯和擴充套件通常在伺服器端執行,儘管支援客戶端功能(如,想互動式地圖的新資料表示)可以新增到客戶端。
為了啟動伺服器,在shell內簡單的呼叫命令Odoo,如果必要新增檔案的完整路徑
odoo-bin
伺服器按Ctrl-C兩次從終端停止,或殺死相應的OS程序。
建立一個Odoo模組
伺服器和客戶端擴充套件都打包為可選擇的載入在資料庫中的模組。
Odoo模組既可以新增新的業務邏輯到Odoo系統,也可以修改和擴充套件現有的業務邏輯:一個模組可以被建立為Odoo通用會計支援新增你的國家的會計準則,而下一個模組,增加了對公車的實時視覺化支援。
因此,odoo的一切都隨著模組的開始結束而開始結束。
1.模組的組成
一個Odoo模組可以包含多個元素:
業務物件:
宣告為Python的類,這些資源是自動持續的,通過基於配置的Odoo。
資料檔案
XML或CSV檔案宣告元資料(檢視或工作流),配置資料(模組引數化),演示資料和更多
Web controllers
處理Web瀏覽器的請求
靜態web資料
Web介面或網站使用的圖片,CSS或JS檔案
2.模組結構
每個模組是模組目錄內的目錄。模組目錄通過–addons-path 選項被指定。
提示
大多數命令列選項也可以使用配置檔案來設定
odoo模組是通過它的manifest宣告的。一個模組也是一個帶__init__.py
例如,如果模組有一個
mymodule.py
, __init__.py
應該包含檔案:
from . import mymodule
Odoo提供了一種機制來幫助建立一個新的模組,odoo-bin有一子命令scaffold 建立一個空的模組:
odoo-bin scaffold <module name> <where to put it>
該命令為模組建立一個子目錄,並自動為模組建立一堆標準檔案。其中大部分只包含註釋程式碼或xml。這些檔案的使用將在本教程中解釋。
練習
模組建立
使用命令列上建立一個空的模組Open Academy,並安裝在Odoo上。
1. 呼叫命令odoo-bin scaffold openacademy odoo/addons
2. 將manifest檔案更新到模組。
3. 不要改變其他檔案。
openacademy/__manifest__.py::
# -*- coding: utf-8 -*-
{
'name': "Open Academy",
'summary': """Manage trainings""",
'description': """
Open Academy module for managing trainings:
- training courses
- training sessions
- attendees registration
""",
'author': "My Company",
'website': "http://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml
# for the full list
'category': 'Test',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
],
# only loaded in demonstration mode
'demo': [
'demo.xml',
],
}
openacademy/__init__.py:
# -*- coding: utf-8 -*-
from . import controllers
from . import models
openacademy/controllers.py:
# -*- coding: utf-8 -*-
from odoo import http
# class Openacademy(http.Controller):
# @http.route('/openacademy/openacademy/', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/openacademy/openacademy/objects/', auth='public')
# def list(self, **kw):
# return http.request.render('openacademy.listing', {
# 'root': '/openacademy/openacademy',
# 'objects': http.request.env['openacademy.openacademy'].search([]),
# })
# @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/', auth='public')
# def object(self, obj, **kw):
# return http.request.render('openacademy.object', {
# 'object': obj
# })
openacademy/demo.xml:
<odoo>
<data>
<!-- -->
<!-- <record id="object0" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 0</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object1" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 1</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object2" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 2</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object3" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 3</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object4" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 4</field> -->
<!-- </record> -->
<!-- -->
</data>
</odoo>
openacademy/models.py:
# -*- coding: utf-8 -*-
from odoo import models, fields, api
# class openacademy(models.Model):
# _name = 'openacademy.openacademy'
# name = fields.Char()
openacademy/security/ir.model.access.csv:
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
openacademy/templates.xml:
<odoo>
<data>
<!-- <template id="listing"> -->
<!-- <ul> -->
<!-- <li t-foreach="objects" t-as="object"> -->
<!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
<!-- <t t-esc="object.display_name"/> -->
<!-- </a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </template> -->
<!-- <template id="object"> -->
<!-- <h1><t t-esc="object.display_name"/></h1> -->
<!-- <dl> -->
<!-- <t t-foreach="object._fields" t-as="field"> -->
<!-- <dt><t t-esc="field"/></dt> -->
<!-- <dd><t t-esc="object[field]"/></dd> -->
<!-- </t> -->
<!-- </dl> -->
<!-- </template> -->
</data>
</odoo>
3.物件關係對映
Odoo的一個關鍵組成部分是ORM層。這一層可以避免手工編寫大多數SQL並提供了可擴充套件性和安全性的服務。
業務物件被宣告為Python類擴充套件模型,該模型將它們整合到自動持久化系統中。
模型可以通過在它們的定義中設定一些屬性進行配置。最重要的屬性是_name,這是必要的,在Odoo中定義系統模型的名字。這裡是一個模型的最小完整定義:
from odoo import models
class MinimalModel(models.Model):
_name = 'test.model'
4.模型檔案
欄位用於定義模型可以儲存和在哪裡。欄位被定義為模型類的屬性:
from odoo import models, fields
class LessMinimalModel(models.Model):
_name = 'test.model2'
name = fields.Char()
(1) 共同屬性
就像模型本身一樣,它的欄位可以通過將配置屬性作為引數來配置:
name = field.Char(required=True)
一些屬性在所有領域都可用,這裡是最常見的:
string (unicode, default: field's name)
使用者介面中欄位的標籤(使用者可見)。
required (bool, default: False)
如果為TRUE,欄位不能為空,則必須具有預設值或在建立記錄時始終給予值。
help (unicode, default: '')
Long-form, 為使用者提供了一個幫助提示。
index (bool, default: False)
要求 Odoo建立列上的資料庫索引。
(2) 簡單欄位
有兩種型別的欄位:“simple”欄位,它們直接儲存在模型表中的原子值,以及連線記錄(同一模型或不同模型)的“relational”欄位。
簡單欄位的例子:Boolean, Date, Char.
(3) 保留欄位
Odoo在所有模型創造了一些欄位。這些欄位由系統管理,不應寫入。如果有用或必要,它們可以被讀取:
id (Id)
模型中記錄的唯一識別符號。
create_date (Datetime)
記錄的建立日期.
create_uid (Many2one)
建立記錄的使用者。
write_date (Datetime)
記錄的最後修改日期。
write_uid (Many2one)
最後修改記錄的使用者。
(4) 特殊欄位
預設情況下,Odoo所有的模型也需要一個name欄位,以便各種顯示和搜尋行為。被用於這些目的的欄位能通過設定_rec_name
重寫。
練習
定義一個模型
在openacademy 模組裡 定義一個新的資料模型Course ,course 有標題(title)和描述(description) ,必須有標題
編輯openacademy/models/models.py檔案,包含到Course 類中。
openacademy/models.py:
from odoo import models, fields, api
class Course(models.Model):
_name = 'openacademy.course'
name = fields.Char(string="Title", required=True)
description = fields.Text()
5.資料欄位
雖然使用Python程式碼定製行為,但模組的值的一部分在載入時設定的資料中。
提示
一些模組的存在只為資料新增到Odoo。
模組資料通過資料檔案,帶有<record>
元素的xml檔案宣告。每個元素建立或更新資料庫記錄。
<odoo>
<data>
<record model="{model name}" id="{record identifier}">
<field name="{a field name}">{a value}</field>
</record>
</data>
</odoo>
· model是用於記錄Odoo模型名稱。
· id是一個外部識別符號,它允許引用到記錄(而不必知道它的內部資料庫識別符號)。
· <field>
元素的name是模型中欄位的name(例如描述)。他們的body是欄位的值。
必須在清單檔案中宣告要載入的資料檔案。它們可以在“data”列表中(總是載入)或在“demo”列表中宣告(僅在演示模式下載入)。
練習
定義演示資料 ,用演示課程建立演示資料,完善課程模式。
編輯openacademy/demo/demo.xml檔案來包含一些資料
openacademy/demo.xml:
<odoo>
<data>
<record model="openacademy.course" id="course0">
<field name="name">Course 0</field>
<field name="description">Course 0's description
Can have multiple lines
</field>
</record>
<record model="openacademy.course" id="course1">
<field name="name">Course 1</field>
<!-- no description for this one -->
</record>
<record model="openacademy.course" id="course2">
<field name="name">Course 2</field>
<field name="description">Course 2's description</field>
</record>
</data>
</odoo>
- Actions和Menus
動作和選單是資料庫中的常規記錄,通常通過資料檔案宣告。動作可以用三種方式觸發:
(1) 通過點選選單項(連結到特定的動作)
(2) 通過點選檢視的按鈕 (如果被連線到動作)
(3) 作為物件的上下文操作
因為選單的宣告有點複雜,有一個<menuitem>
快捷方式來更容易地宣告 ir.ui.menu 並連結它到相關的action。
<record model="ir.actions.act_window" id="action_list_ideas">
<field name="name">Ideas</field>
<field name="res_model">idea.idea</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
action="action_list_ideas"/>
危險
必須在xml檔案的相應選單前宣告該操作。
資料檔案是按順序執行的,在選單能建立前,動作的id必須存在於資料庫中
-
練習
定義新的選單入口
在OpenAcademy 選單入口定義新的選單入口來訪問課程,使用者應該能夠:
· 顯示所有課程列表
· 建立/修改課程
(1) 建立openacademy/views/openacademy.xml,附帶一個action和觸發action的選單。
(2) 把它新增到openacademy/__manifest__.py
的data列表。
openacademy/__manifest__.py
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
],
# only loaded in demonstration mode
'demo': [
----------
openacademy/views/openacademy.xml:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
that is an action opening a view or a set of views
-->
<record model="ir.actions.act_window" id="course_list_action">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Create the first course
</p>
</field>
</record>
<!-- top level menu: no parent -->
<menuitem id="main_openacademy_menu" name="Open Academy"/>
<!-- A first level in the left side menu is needed
before using action= attribute -->
<menuitem id="openacademy_menu" name="Open Academy"
parent="main_openacademy_menu"/>
<!-- the following menuitem should appear *after*
its parent openacademy_menu and *after* its
action course_list_action -->
<menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
action="course_list_action"/>
<!-- Full id location:
action="openacademy.course_list_action"
It is not required when it is the same module -->
</data>
</odoo>
基本檢視
檢視定義顯示模型記錄的方式。每種型別的視圖表示一個視覺化模式(一個記錄列表,一個聚合的圖形,…)。檢視可以被要求通過他們的型別(例如類合作伙伴名單)或專門通過它們的ID的通用要求,具有正確的型別和優先順序最低的檢視將被使用(所以各型別優先順序最低的檢視是預設檢視為型)。
檢視繼承允許改變在別處宣告的檢視(新增或刪除內容)。
- 通用檢視宣告
檢視被宣告為ir.ui.view模型的記錄,檢視型別由arch欄位的根元素說明:
<record model="ir.ui.view" id="view_id">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>
危險
檢視的內容是XML.因此,arch欄位必須宣告為type="xml"
,以便正確解析。
2.樹檢視
樹檢視,也被稱為列表檢視。以表單形式顯示記錄。
根元素是<tree>
,樹檢視的最簡單形式只列出了表中顯示的所有欄位(每個欄位列):
<tree string="Idea list">
<field name="name"/>
<field name="inventor_id"/>
</tree>
3.表單檢視
表單被用於建立和編輯單個記錄。
根元素是<form>
,它們由高層次結構元素(groups、notebooks)和互動元素(按鈕和欄位)組成:
<form string="Idea form">
<group colspan="4">
<group colspan="2" col="2">
<separator string="General stuff" colspan="2"/>
<field name="name"/>
<field name="inventor_id"/>
</group>
<group colspan="2" col="2">
<separator string="Dates" colspan="2"/>
<field name="active"/>
<field name="invent_date" readonly="1"/>
</group>
<notebook colspan="4">
<page string="Description">
<field name="description" nolabel="1"/>
</page>
</notebook>
<field name="state"/>
</group>
</form>
練習
使用XML製作form檢視
給Course 物件建立form檢視,資料應該顯示:Course 的name和description。
openacademy/views/openacademy.xml:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="course_form_view">
<field name="name">course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="Course Form">
<sheet>
<group>
<field name="name"/>
<field name="description"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
練習
Notebooks
在Course 表單視圖裡,將“description”欄位置於選項卡之下,以便稍後新增其他選項卡,包含附加資訊。
如下修改Course 表單檢視:
openacademy/views/openacademy.xml
<sheet>
<group>
<field name="name"/>
</group>
<notebook>
<page string="Description">
<field name="description"/>
</page>
<page string="About">
This is an example of notebooks
</page>
</notebook>
</sheet>
</form>
</field>
表單檢視也可以使用純HTML來進行更靈活的佈局:
<form string="Idea Form">
<header>
<button string="Confirm" type="object" name="action_confirm"
states="draft" class="oe_highlight" />
<button string="Mark as done" type="object" name="action_done"
states="confirmed" class="oe_highlight"/>
<button string="Reset to draft" type="object" name="action_draft"
states="confirmed,done" />
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Idea Name" />
<h1><field name="name" /></h1>
</div>
<separator string="General" colspan="2" />
<group colspan="2" col="2">
<field name="description" placeholder="Idea description..." />
</group>
</sheet>
</form>
4.搜尋檢視
搜尋檢視自定義與列表檢視關聯的搜尋欄位(以及其他聚合檢視)。它們的根元素是<search>
,它們由定義哪些欄位可以被搜尋的欄位組成:
<search>
<field name="name"/>
<field name="inventor_id"/>
</search>
如果模型沒有搜尋檢視,Odoo產生一個只允許用名稱欄位檢索的搜尋檢視。
練習
搜尋檢視
允許根據他們的標題或描述來搜尋課程。
openacademy/views/openacademy.xml
</field>
</record>
<record model="ir.ui.view" id="course_search_view">
<field name="name">course.search</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="description"/>
</search>
</field>
</record>
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
模型之間的關係
例如,銷售訂單記錄與包含客戶資料的客戶機記錄有關,它還與銷售訂單線記錄有關。
練習
建立一個session 模型
對於Open Academy 模組,我們考慮一個session 模型:一個session 是一個給定的時間給定的觀眾在一個給定的時間發生的課程。
建立session 模型。session 具有名稱、開始日期、持續時間和若干個座位。新增一個動作和選單項來顯示它們。通過選單項使新模型可見。
1、在openacademy/models/models.py中建立session 類
2、在openacademy/view/openacademy.xml中給session 物件新增入口
openacademy/models.py
name = fields.Char(string="Title", required=True)
description = fields.Text()
class Session(models.Model):
_name = 'openacademy.session'
name = fields.Char(required=True)
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
openacademy/views/openacademy.xml
<!-- Full id location:
action="openacademy.course_list_action"
It is not required when it is the same module -->
<!-- session form view -->
<record model="ir.ui.view" id="session_form_view">
<field name="name">session.form</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<sheet>
<group>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="session_menu" name="Sessions"
parent="openacademy_menu"
action="session_list_action"/>
</data>
</odoo>
注意
digits=(6, 2)指定浮點數的精度:6是數字的總數,而2是小數點後的位數。請注意,它的結果在小數點前是最大值4位數字。
1.關係欄位
關係欄位連結記錄,包括相同的模型(層次結構)或不同模型之間的連結記錄。
關係欄位型別:
(1) Many2one(other_model, ondelete='set null')
到另一個物件的簡單鏈接:
print foo.other_id.name
(2) One2many(other_model, related_field)
一個虛擬的關係,一個many2one的逆。一個one2many作為容器的記錄,訪問的結果是一個(可能為空)的記錄集:
for other in foo.other_ids:
print other.name
危險
因為One2many 是一個虛擬的關係,在other_model裡必須有一個Many2one欄位,name必須是related_field 。
(3)Many2many(other_model)
雙向多重關係,一方的任何記錄都可以與另一方的任何記錄有關。作為記錄的容器,訪問它也會導致一組可能空的記錄:
for other in foo.other_ids:
print other.name
練習
Many2one 關係
使用Many2one ,修改Course 和Session 模型,反映他們與其他模型的關係:
· course有一個responsible 使用者,該欄位的值是內建模型res.users的一個記錄。
· Session 有一個instructor,該欄位的值是內建模型res.partner的一個記錄。
· 一個session被連線到一個course,該欄位的值是模型openacademy.course的一個記錄,並且是必須的。
· 更新檢視新增Many2one欄位到模型裡, 並將它們新增到檢視中。
openacademy/models.py
name = fields.Char(string="Title", required=True)
description = fields.Text()
responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)
class Session(models.Model):
_name = 'openacademy.session'
----------
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
instructor_id = fields.Many2one('res.partner', string="Instructor")
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
openacademy/views/openacademy.xml
<sheet>
<group>
<field name="name"/>
<field name="responsible_id"/>
</group>
<notebook>
<page string="Description">
----------
</field>
</record>
<!-- override the automatically generated list view for courses -->
<record model="ir.ui.view" id="course_tree_view">
<field name="name">course.tree</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<tree string="Course Tree">
<field name="name"/>
<field name="responsible_id"/>
</tree>
</field>
</record>
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
----------
<form string="Session Form">
<sheet>
<group>
<group string="General">
<field name="course_id"/>
<field name="name"/>
<field name="instructor_id"/>
</group>
<group string="Schedule">
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- session tree/list view -->
<record model="ir.ui.view" id="session_tree_view">
<field name="name">session.tree</field>
<field name="model">openacademy.session</field>
<field name