1. 程式人生 > >Odoo10 開發者文件(3)--建立一個模組·

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

的Python包,包含模組中各種Python檔案的匯入說明。
例如,如果模組有一個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>
  1. 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的通用要求,具有正確的型別和優先順序最低的檢視將被使用(所以各型別優先順序最低的檢視是預設檢視為型)。
檢視繼承允許改變在別處宣告的檢視(新增或刪除內容)。

  1. 通用檢視宣告
    檢視被宣告為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