Packer.Odoo.10---Chapter 2

創建第一個odoo 應用

Odoo遵循傳統的MVC模式。我們可以通過創建簡單的To-Do 應用來具體介紹分析

  • model :定義了應用的數據結構
  • view: 描述了用戶界面(可以理解為前端)
  • controller: 控制器,支持應用的具體業務邏輯

理解applications(應用)跟modules(模塊)的區別:
  • Module addons :它是applications的基礎,它能為Odoo添加新功能或者改變一個已經存在的模塊,它包含了一個名為"mainfest.py"的字典文件,和一些能夠實現新功能的文件
  • Applications : 它是將主要功能添加到Odoo的一種方式.例如Odoo中的Accounting or HR,依賴與之相對應對Applications,提供了非常重要的功能。它們在Odoo中的Apps菜單中高亮顯示
    簡而言之,當你的module十分復雜,為Odoo添加了新的或者非常重要的功能,你可能就需要把它創建為一個Application。當你的module只是為已經存在的Odoo模塊增加小變動。就不需要作為一個Application

創建一個module基礎架構
  • 我們把新的module放入在custom-addons文件夾中,新建todo_app文件夾,然后在該文件夾中創建manifest.py文件,由于todo_app是python包,需要新建init.py.
    mkdir -p custom-addons/todo_app
    cd custom-addons/todo_app
    touch __init__.py
    vim __manifest__.py
    manifest.py中添加如下代碼
{
    'name': 'To-Do Application',  
    'description': 'Manage your personal task',
    'author': 'xer',
    'depends': ['base'],
    'application': True,
    'data': [ ],
}
  • 以上depends這個key對應了一個list用來存放所依賴的模塊。例子中的‘base‘ 表示我們新建的todo_app在安裝時候會自動把Odoo中的'base'一起安裝.list中存放的都是模塊的包名,就跟'todo_app'這樣.
  • 其他manifest.py中的keys:
  1. summary:module的次要標題
  2. version: 代碼module的版本號
  3. license:版權,默認LGPL-3
  4. website:設置一個URL用來查找模塊相應的信息
  5. category:module的分類,默認為Uncategorized。目前已存在的categories可以在Settings| User | Groups中找到
  6. installable: 默認為True
  7. auto_install: 如果設置為True 這個模塊會自動安裝.

添加todo_app到addons path
  • 我們首選需要保證addons path中有我們剛才新建的todo_app的文件路徑
    ./odoo-bin -d todo --addons-path='custom-addons,odoo/addons' --save
安裝todo_app
  • Apps ==>Update Apps List 然后在搜索框搜索'todo_app' 點擊安裝即可
image.png
更新module
  • 當我們的Python 代碼發生改變時,需要對Odoo進行重啟, 因為odoo在啟動時只對當前python代碼讀取一次
  • 當我們的date files更改時候(例如views中添加新的視圖) 這時需要在Apps中對module進行upgrading操作.
  • 當python 代碼跟data files同時改變時.要重啟并upgrading module.
    在這里其實有個更好的方法:
  • 首先 Ctrl+c停止Odoo服務后,命令行輸入odoo-bin -d todo -u todo_app
    這里運用了參數-u <需要更新的模塊名(list)> 來指定需要更新的模塊名.

服務端開發者模式
  • 在odoo10 中有一個新的參數在開啟Odoo服務時可以運用 --dev=all
    這個參數有利于加快我們的開發:
  1. 自動的Reload python代碼.
  2. 自動的讀取xml files中的新定義.避免手動更新

model 層:

  • Models 描述了業務對象,例如 sales order, partner等,一個model 有許多屬性(attributes) ,并能在其中定義特殊的業務邏輯。
  • Models 使用Python(目前仍為2.7)語言進行編寫。使用了ORM模式可以對數據庫直接進行操作。
  • 我們會在todo_app模塊中新建一個'to-do tasks' model來對Models進行一步步的深入.這個task會有一個text field 用來描述task的詳細情況,還有一個選擇框來標記task是否被完成.最后會添加一個按鈕(button)來清除那些舊的,已經被完成的tasks.
創建一個data 模塊:
  • 根據Odoo的常規做法,我們需要一個models文件夾來存放用python編寫的models.py文件。但在這個簡單的例子中,我們不遵循這個規范,直接把todo_model.py放在todo_app文件夾中
  • 在todo_model.py 中添加相應的python代碼:
# -*- coding: utf-8 -*-
from odoo import models, fields
class TodoTask(models.Model):
   _name = 'todo.task'
   _description = "To-do Task"
   name = fields.Char('Description', required=True)
   is_done = fields.Boolean('Done')
   active = fields.Boolean('Active?', default=True)
  • 注意點:
    1. 需要從odoo中導入models,fields 對象
    2. 創建一個TodoTask類,該類繼承自models.Model
    3. _name屬性:用來定義我們新添加的這個類的名字.可以看做是當Odoo需要關聯TodoTask類時的標識符。
    4. _description屬性:這個屬性不是必須的。用來描models的記錄。
    5. 最后三行內容具體描述了我們的model的具體字段,其中nameactive是特殊字段。
      name:用作于記錄的名稱。(在與其他models有關聯關系時候,如Many2one,Many2many時顯示在關聯models上的名稱。)。
      active:只有當記錄中的active字段為true時,該記錄才能在頁面視圖中顯示。
      最后,在init.py中添加如下代碼
      from . import todo_model
      整體結構圖
      --custom-addons
      ----todo_app
      -------manifest.py
      -------init.py
      -------todo_models.py
      • 這里擴展下,'_rec_name'屬性:默認關聯顯示名稱為name對應的fields.但'_rec_name'屬性可以根據需要把要顯示在關聯models上的名字定義為想要的fields的名字.

重啟Odoo服務,由于我們還沒定義菜單,需要進入Settings | Technical | Database Structure | Models.使用'todo.task'來搜索我們剛才定義的model.點擊查詢結果來查看.

image.png
  • Odoo會在創建model數據表時自動加入4個字段
    1. id: 可認為是每個model record的主鍵
    2. create_date 和create_uid :什么時候創建,哪個用戶創建
    3. write_date and write_uid: 確認最后的修改時間和修改的用戶
    4. __last_update:沒有實際存貯在數據庫中,使用在并發檢查上

添加自動測試

Odoo有2種測試方法:

  1. YAML
  2. Python的測試類(Unittest2).
下面主要介紹第二種測試方法:
  • test代碼名字需要用'test_'作為開頭,并且需要在tests/init.py導入.但是tests文件夾不需要在modules中的init.py導入.因為Odoo會自己搜索測試代碼.
  • 在todo_app文件夾中新建tests文件夾,在tests中新建init.py.
  • init.py中添加代碼:
    from . import test_todo
  • 創建test_todo.py文件,添加代碼
from odoo.tests.common import TransactionCase
class TestTodo(TransactionCase):
    def test_create(self):
        'Create a simple Todo'
        Todo = self.env['todo.task']
        task = Todo.create({'name': 'Test Task'})
        self.assertEqual(task.is_done, False)
  • 運行測試代碼
    ./odoo-bin -d todo -i todo_app --test-enable

view層

  • view可以認為是用戶跟Odoo內部數據直接進行聯系的一個交互界面(通過html前端頁面來展示數據,通過form表單來提交用戶輸入數據到Odoo數據庫)
  • Odoo中的Views是通過xml來編寫的。Odoo中的xml 文件一般都存放在views這個子文件夾中。我們首先來進行對menu items進行編寫。(menu可以看做是一個動作,點擊后能渲染視圖在前端顯示)
添加 menu 主題
  • 既然我們已經有todo_models來存放我們的數據,那現在我們就需要讓用戶能夠通過menu來與之交互.
  • 創建'views/todo_menu.xml' 來定義menu.添加如下xml 代碼
<?xml version="1.0"?>
<odoo>
   <act_window id="action_todo_task"
               name="To-do Task"
               res_model="todo.task"
               view_mode="tree,form"/>
 <!--menuitem -->
   <menuitem id="menu_todo_task"
             name="Todos"
             action="action_todo_task"/>
</odoo>
  • The user interface,包括menu 跟actions 都是存儲在數據庫表中的。而我們的xml 文件可以看做用來把這些數據庫中的表展示出來在用戶界面上體現。前面的代碼描述添加到Odoo中的2條records。
    • <act_window> :定義了客戶端的窗口動作,會通過tree視圖跟form視圖來打開我的定義的todo.task
    • <menuitem>:定義了頂部的(menu)菜單欄,該菜單欄綁定了一個名為'action_todo_task'的action.
  • 所有的元素中包含id 這個屬性, 這個id屬性十分重要,被稱為XML ID:它用于唯一標識模塊內部的每個數據元素,并且能夠被其他元素所引用到.在我們的例子中,<menuitem>元素需要與action聯系起來,所以需要把<act_window>的id關聯到<menuitem>的action中
  • 添加xml文件到'manifest.py':
    'data' : ['views/todo_menu.xml'],
    更新todo_app模塊,發現我們的菜單欄中已經有了'Todos'這個菜單選項
    image.png

提升我們的視圖
添加一個form視圖
  • 所有的視圖都存儲在數據庫中,在'ir.ui.view'這個model中.添加一個view到module,我們在XML文件中聲明<record>這個元素,當模塊被安裝后這個<record>中的數據就被加載進數據庫中。
  • 添加views/todo_view.xml文件,添加如下代碼
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
    <!--<form view>-->
    <record id="view_form_todo_task" model="ir.ui.view">
        <field name="name">To-do Task Form</field>
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <form string="To-do Task">
          <group>
                    <field name="name"/>
                    <field name="is_done"/>
                    <field name="active" readonly="1"/>
                    </group>
            </form>
        </field>
    </record>
</odoo>
  • 之后別忘記添加到'manifest.py'中的'data'中.這個'todo_view.xml'文件在'ir.ui.view' model中添加了一條標識為'view_form_todo_task'的記錄.
    注意:這邊的話其實添加的所謂標識是添加在'ir.model.data' 表中作為一個外部ID來與'ir.ui.view'進行mapping關系的建立.而不是直接添加到'ir.ui.view'表中

  • 可以通過psql查詢數據庫
    select name from ir_ui_view where name='To-do Task Form' 來得到.(注意,在psql中,所有Odoo的數據表名稱的.都用_代替了.因為'view_form_todo_task'是添加在'ir.model.data'中,所以查詢'ir_ui_view'無法得到外部ID).

  • 在上述<record>代碼中,最為重要的屬性是'arch'.它包含了需要的視圖,例子中就定義了視圖為<form>.

  • 接下來的三段定義是把我們todo_models中的fields呈現在form視圖中.其中的'active'字段屬性我們設置為了只讀


業務文檔form視圖
  • 對于文檔模型,Odoo有一個專門的模仿成一頁紙形的展示風格。這個視圖包含了2個元素:
    • <header> :可以在其中添加<button>按鈕
    • <sheet>: (讓form好看一點)
    image.png
添加動作按鈕(action buttons)
  • form表單中可以定義buttons來執行相應的actions,這些buttons可以能夠打開一個新的窗口或者運行一個定義在models中的python方法.
  • buttons能定義在form中任何地方, 但是對于文檔格式的forms來說,建議存放在<header>中
  • 對于我們的todo_app應用,我們添加2個buttons來運行我們定義在'todo.task' model中的2個方法
 <header>
       <button name="do_toggle_done" type="object" string="Toggle Done" class="oe_highlight"/>
        <button name="do_clear_done" type="object"  string="Clear All Done"/>
</header>
  • 我們定義的buttons包含了下面4個屬性
    1.string:顯示為button在頁面上的名字
    2.type : 動作的類型(這里的object可以理解為調用了我們todo_model.py中的TodoTask這個類創建的object)
    3.name: action的標識符(我們代碼中的name可以理解為在TodoTask中定義的do_toggle_done方法.
    4.class:這是一個可選選項來運用CSS樣式.(我們的oe_highlight表示為把這個button設置為高亮顯示)

使用<group>來組織form視圖
  • <group> 標簽可以讓我們把form中的內容組織起來。在一個<group>中在放入<group>,就像<group><group></group></group>這樣,能夠在外部的<group>的內容中增加兩個縱列(其實可以這樣理解,一個<group>就是把我們field的字段分為左右兩列,左邊是field的名字,右邊是field要輸入的.)
image.png
  • 我們建議在<group>元素中添加一個name屬性以便以后更好的擴展我們的<group>
     <sheet>
              <group name="top">
                      <group name="left">
                      <field name="name"/>
                  </group>
                    <group name="right">
                     <field name="is_done"/>
                     <field name="active" readonly="1"/>
                 </group>
              </group>
        </sheet>
    

最后,我們的form視圖代碼如下:

            <form string="To-do Task">
                <header>
                    <button name="do_toggle_done" type="object"
                            string="Toggle Done" class="oe_highlight"/>
                    <button name="do_clear_done" type="object"
                            string="Clear All Done"/>
                </header>
                <sheet>
                <group name="top">
                    <group name="left">
                    <field name="name"/>
                </group>
                    <group name="right">
                        <field name="is_done"/>
                        <field name="active" readonly="1"/>
                    </group>
                </group>
                </sheet>>
            </form>

展示效果:

image.png

添加列舉(list)跟搜索(search)視圖
  • 當需要列舉一個model中的數據時,我們就需要使用<tree>視圖。Tree視圖能夠展示結構化的層級關系,(如linux下的tree命令)在Odoo中,我們通常使用Tree視圖來展示清晰的數據列表。
  • 我們在todo_view.xml中添加如下的tree視圖定義代碼:
    <!--tree view-->
    <record id="view_tree_todo_task" model="ir.ui.view">
        <field name="name">To-do Task tree</field>
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <tree colors="decoration-muted:is_done==True">
                <field name="name"/>
                <field name="is_done"/>
            </tree>
        </field>
    </record>
  • 以上的定義主要在列舉數據時只顯示兩列nameis_done.我們在這里設置了一個bootstrap的CSS樣式,當task的記錄中is_doneTrue時,記錄顯示為灰色.(需要安裝Odoo的'website'模塊才能使用bootstrap).
  • Odoo的右上角有一個搜索框,我們可以定義一個<search>視圖來為這個搜索框添加可選的過濾條件。
  • 下面構造我們的<search> 視圖代碼:
    <!--search view-->
    <record id="view_filter_todo_task" model="ir.ui.view">
    <field name="name">To-do Task Filter</field>
    <field name="model">todo.task</field>
    <field name="arch" type="xml">
        <search>
            <field name="name"/>
            <filter string="Not Done"
                    domain="[('is_done','=',False)]"/>
            <filter string="Done"
                    domain="[('is_done','!=',False)]"/>
        </search>
    </field>
    </record>
  • 可以看到在<search>標簽中,可以定義<filter>這個標簽,使用domain屬性來實現過濾條件.

邏輯層

  • 現在我們需要添加業務邏輯來實現我們的buttons:使用python在我們的todo_models.py中編寫與業務相關的python methods.
添加業務邏輯
  • 編輯我們在models文件夾中的'todo_model.py'文件.首先,我們需要import 新的API
    from odoo import models, fields, api
  • 我們的Toggle Donebutton邏輯十分簡單,就是用來轉換我們創立的task中的'Is_Done'這個flag.我們使用@api.multi這個裝飾器來表示對多條records的邏輯修改.self代表一個recordset.我們可以通過遍歷self來達到遍歷recordset從來得到需要的每條record.
  • TodoTask 類中,添加如下python代碼:
@api.multi
def do_toggle_done(self):
      for task in self:
            task.is_done = not task.is_done
      return True
  • 上面的代碼實現的邏輯是:遍歷所有的'to-do task' 記錄,然后修改每條記錄的'is_done' field, 反轉它的值.在最后我們return True是因為Odoo客戶端使用XML-RPC來調用我們的'do_toggle_done'方法,而這個協議不支持客戶端方法返回None值。
  • Clear All Done方法: 找到所有'is_done'的值為True的記錄,把它們的active屬性設置為False以達到讓該條記錄不在頁面顯示的功能.通常來說,放在form視圖中的button都是用來操作當前選中的記錄,在我們的這個例子中,我們想要讓這個button能夠影響所有的records.
@api.model
 def do_clear_done(self):
        dones = self.search([('is_done', '=',True)])
        dones.write({'active': False})
        return True
  • 上面的代碼中. @api.model裝飾器裝飾的方法中,self變量代表了當前的model,我們把所有is_done = True的recordset存放在變量dones中.然后再把它們的active 字段設置為False.
  • search方法是Odoo提供的一個API方法,返回符合條件的records. 這些條件使用Odoo的domain規則.是一個tuple組成的list.
  • write方法能夠對recordset直接使用.傳入的參數是一個'dict'.
添加測試
  • 我們需要為我們的業務邏輯添加測試,在我們以前編寫的測試類'tests/test_todo.py' ,添加如下測試方法test_create()方法:
 def test_create(self):
        # Test Toggle Done
        task.do_toggle_done()
        self.assertTrue(task.is_done)
        # Test Clear Done
        Todo.do_clear_done()
        self.assertFalse(task.active)

使用./odoo-bin -d todo -i todo_app --test-enable

設置模塊訪問權限(access security)

當我們加載我們的模塊時,記錄中會有一條warning message:

The model todo.task has no access rules, consider adding one.

  • 這條信息說明了我們的todo_app模塊沒有設置訪問權限規則,除了超級管理員以外其他的用戶不能使用我們的模塊.
  • 另一個問題是我們需要讓不同的用戶擁有自己私有的to-do tasks.
測試訪問權限
  • 實際上,我們在前面編寫的測試代碼不應該成功,但我們的管理員(admin)用戶身份導致了測試類完整運行。現在,我們使用Demo來代替我們的admin用戶。
  • 編輯'tests/test_todo.py'文件,我們新添加set Up這個方法:
    def setUp(self,*args,**kwargs):
        result = super(TestTodo, self).setUp(*args, **kwargs)
        user_demo = self.env.ref('base.user_demo')
        self.env = self.env(user=user_demo)
        return result
  • 函數定義的第一行,我們調用了父類中的set Up方法.接下來就是對調用測試方法的用戶的更改,上述代碼中,我們改變了測試環境,把demo用戶代替了admin
  • 接下來,我們在測試類中導入了斷言異常的處理函數.
    from odoo.exceptions import AccessError
  • 在test 類中添加一個新的測試方法:
    def test_record_rule(self):
        'Test per user record rules'
        Todo = self.env['todo.task']
        task = Todo.sudo().create({'name': 'Admin Task'})
        with self.assertRaises(AccessError):
            Todo.browse([task.id]).name
  1. 因為我們現在的env 已經使用Demo用戶,我們使用sudo()方法(可以理解為linux下的sudo)來切換到admin的上下文環境創立一個name為Admin Task的task。這么做的目的是創建一個不能被Demo所獲取的to-do task.
  2. 當我們嘗試使用Demo用戶去獲取上文創建的task時,會有一個AccessError異常被拋出.
  • 注意:
    此時,當我們嘗試運行剛才編寫的test時,會發生錯誤,因為我們還沒有設置權限設置.

添加訪問控制

訪問Settings | Technical | Security| Access Controls List:

image.png

這些信息是有模塊提供然后加載到odoo中的'ir.model.access'模塊,接下來,我們就在我們的model中添加所有權限給員工(employee)這個分組.注:員工這個分組是非常普通的.

  • 我們可以通過使用CSV文件來設置權限.新建'security/ir.model.access.csv'文件,添加如下內容
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_todo_task_group_user,todo.task.user,model_todo_task,base.group_user,1,1,1,1

CSV文件中的列名:
我們定義的CSV文件中,第一行的那些名字可以看做是書寫的參照規則:

  • id :外部標識,在我們的模塊中應該是獨一無二的
  • name :是一個描述標題,作為一個信息的展示,最好能保持唯一性.Odoo官方模塊通常使用modelname.group來進行取名.我們的todo_app中,使用了'todo.task.user'
  • model_id: 這是我們要賦予權限的model的外部標識,通常來說,Odoo會通過ORM對models使用自動的取名.對于我們的todo.task來說,這個外面id是'model_todo_task'.
  • group_id: 標識了需要賦予權限的用戶組.Odoo中重要的用戶組一般都是base模塊提供的,而我們需要的Employee的id為base.group_user
  • perm: 4個,'read','write','create','unlink'.分別代表了對讀,寫,創建,刪除的4個操作的允許與否.
    創建了security/ir.model.access.csv文件后還需要把其路徑導入到manifest.py的'data'列表中
    'data':[ 'security/ir.model.access.csv', ... ],
  • 現在,更新我們的模塊,warning message就會消失了,我們登錄Demo賬戶也能發現能夠訪問我們的todo_app模塊。運行測試類時只有'test_record_rule'方法仍會失敗

Row-level的訪問規則

Setting | Technical | Record Rules
記錄規則被定義在'ir.rule' model中,跟往常一樣,我們需要提供一個特殊的名字,記錄規則作用在的具體的model以及需要定義的domain過濾規則來進行權限約束。

  • 通常,規則被適用于特殊的安全用戶組,在我們的例子中,我們還是會使用員工(Employees)用戶組,如果不定義規則要適用的用戶組,Odoo就會認為是一個全局規則(Global rules).全局規則是特殊的存在因為它們無法被普通的規則覆蓋。
  • 為了添加記錄規則,我們創建'security/todo_access_rules.xml'文件,添加如下代碼
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
    <data noupdate="1">
        <record id="todo_task_user_rule" model="ir.rule">
            <field name="name">ToDo_rule</field>
            <field name="model_id" ref="model_todo_task"/>
            <field name="domain_force">[('create_uid','=',user.id)]</field>
            <field name="groups" eval="[(4,ref('base.group_user'))]"/>
        </record>
    </data>
</odoo>
  • 上面代碼中,注意noupdate='1'屬性,這個屬性意味著當模塊被升級時,我們的data并不會發生改變.通常在開發時我們需要多次調整數據,所以可以把它的值設為'0'.
  • 在<groups>字段中,我們發現有一個特殊的表達式,這是一個一對多的關系字段,在我們的例子中(4,x)這個tuple 表明把x添加到記錄中,在這里,x代表了員工用戶組,驗證id為 base.group_user.
  • 最后別忘記把我們剛新建的xml文件放入manifest.py的'data'中.
  • 下面,我們來運行我們編寫的測試類,可以發現全部
    測試通過.
添加一個module的圖標
  • 添加一個icon圖標讓我們的module看起來好看點.
    把它放入module目錄下的static/description目錄中即可.
    在todo_app目錄路徑下,簡單的幾句linux代碼:
    mkdir -p static/description
    cp 我們自己的icon圖標 static/description
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 繼承-擴展原有的應用 Odoo中,有一個非常重要的特色,不用直接修改底層對象就能為我們的模塊添加新的功能。這個特色...
    F4A3閱讀 665評論 0 1
  • 結構化應用數據 在前面的章節中,我們基本接觸了建造Odoo后臺應用的所有層面。下面我們就要具體分析‘model,v...
    F4A3閱讀 570評論 0 0
  • 視圖層-設計用戶界面 這一章節我們會學習用來構建用戶界面的視圖層。 視圖跟控件 context跟domian 使用...
    F4A3閱讀 963評論 0 3
  • ORM應用邏輯-業務處理 前面的章節我們學習了利于Odoo的視圖來構建用戶前端界面。本章介紹Odoo的后臺業務邏輯...
    F4A3閱讀 813評論 0 3
  • 文|你這磨人的小妖精 有時候朋友之于我們,在生活中比愛情還重要。 001/// 昨天下午,朋友貓兒打來電話,我眼睛...
    小妖的幺閱讀 1,715評論 5 17