1. 程式人生 > >odoo12從零開始:三、1)建立你的第一個應用模型(module)

odoo12從零開始:三、1)建立你的第一個應用模型(module)

前言

      以前,我一直都不知道為什麼好多框架的入門都是“hello world”開始,當我思前想後我要如何介紹odoo的model、record、template等繼承等高階特性時,發現在那之前便需要清楚地介紹什麼是模型(model),什麼是記錄(record),什麼是模板(template),以及他們到底是幹什麼用以及是怎麼用的?想要知道它們是怎麼用的,就得介紹odoo的一個應用模組(module)的結構是什麼樣的。那麼瞭解一個應用結構最快的辦法,那就是我們自己去完成一個。Let's do it!

Tips: 初學者容易搞混模組(module)和模型(model)。
模組(module):是一個odoo應用,包含模型(models)、控制器(controllers)、檢視(views)、許可權(ir.rule,ir.group, ir.model.access)、初始化資料(data)、報表(report)、
嚮導(wizard)、靜態檔案(static)等。 模型(model):是模組(module)的一部分,是odoo的ORM的描述物件,它的工作是幫我們將記憶體中的物件反映為資料庫中的關係資料。

 模組結構(Module Structure)

主要目錄:
       data/ : demo和資料的xml檔案
     models/ : models定義
controllers/ : 包含controllers (HTTP路由等)
      views/ : 包含檢視(views)和模板(templates)
     static/ : 包含web資源, 分為css/, js/, img/, lib/等
其他可選目錄結構:
     wizard/ : 嚮導,由瞬時模型(models.TransientModel)構成,以及嚮導的檢視(views)
     report/ : 報表,包含含有sql的模型,XML檔案等
      tests/ : 測試程式碼

專案結構示意圖

addons/模組名/
|-- __init__.py
|-- __manifest__.py (描述檔案)
|-- controllers/
|   |-- __init__.py
|   |-- main.py
|   |-- *****.py
|-- data/
|   |-- *****_data.xml
|   |-- *****_demo.xml
|-- models/
|   |-- __init__.py
|   |-- *****.py
|-- report/
|   |-- __init__.py
|   |-- *****_report.py
|   |-- *****_report_views.xml
|   |-- *****_reports.xml (report actions, paperformat, ...)
|   |-- *****_templates.xml (xml report templates)
|-- security/
|   |-- ir.model.access.csv
|   |-- *****_groups.xml
|   |-- *****_security.xml
|-- static/
|   |-- description/
|   |   |-- icon.png(模組的icon)
|   |-- img/
|   |   |-- *****.png
|   |   |-- *****.jpg
|   |-- lib/
|   |   |-- external_lib/
|   |-- src/
|   |   |-- js/
|   |   |   |-- widget_a.js
|   |   |   |-- widget_b.js
|   |   |-- scss/
|   |   |   |-- widget_a.scss
|   |   |   |-- widget_b.scss
|   |   |-- xml/
|   |   |   |-- widget_a.xml
|   |   |   |-- widget_a.xml
|-- views/
|   |-- assets.xml
|   |-- **********.xml
|-- wizard/
|   |--*****.py
|   |--*****.xml

本節程式碼

git clone -b v3.1 https://github.com/lingjiawen/odoo_project.git

開發模組(module)

我們基於上一節的程式碼版本(V2.1)中進行開發:

git clone -b v2.1 https://github.com/lingjiawen/odoo_project.git

我們先來嘗試仿照官方的hr模組開發一個簡易版的員工模組,包含員工基本資訊,員工部門和員工職位管理。

1、建立使用者目錄

首先,我們在my_addons/下新建employee/目錄,在目錄下新建以下檔案:

my_addons/employee/
|-- __init__.py
|-- __manifest__.py
|-- models/
|   |-- __init__.py
|-- views/

2、填寫描述檔案__manifest__.py

# -*- coding: utf-8 -*-
{
    'name': 'Employee',
    'version': '12.0.1.0',
    'summary': '對員工的基本資訊,部門和職位進行管理',
    'description': '''
        員工管理模組
    ''',
    'author': 'misterling',
    'sequence': 15,
    'category': 'Uncategorized',
    'license': 'LGPL-3',
    'depends': ['base'],
    'data': [],
    'demo': [],
    'qweb': [],
    'installable': True,
    'application': True,
    'auto_install': False,
    # 'pre_init_hook': '',
    # 'post_init_hook': '',
    # 'uninstall_hook': '',
}

以上描述便是常用的配置項,我們來看看它們都代表著什麼:

          name: 模組的標題
       version: 版本號
       summary: 模組的子標題
   description: 模組的描述性文字
        author: 作者
      sequence: 模組在apps中的排列的序號,影響展示順序。
      category: 模組的分類,在設定->使用者&公司->群組中"應用"欄位中可以看到
       license: 代表著你的開源協議。
       depends: 依賴的模組,在安裝當前模組時,如果依賴模組未安裝,將會自動安裝;升級依賴的模組時,所有依賴它的模組也將會跟著升級。
data: 載入XML檔案。 demo: 載入demo檔案。 qweb: 載入qweb template檔案。 installable: 是否可以安裝。 application: 是否是應用,在應用列表中,被應用篩選隔離,好的開發習慣應該謹慎考慮是否是應用。 auto_install: 是否自動安裝,設為True的應用將在資料庫初始化時自動安裝 pre_init_hook: 顧名思義,模組安裝前的鉤子,指定方法名即可 post_init_hook: 模組安裝完成後的鉤子 uninstall_hook: 模組解除安裝時的鉤子
Tips:我們在書寫python檔案時,不用忘記在首部新增
# -*- coding: utf-8 -*-
以支援中文編碼

 3、建立員工物件

 我們在models/下面新建employee.py檔案,編寫以下內容:

# -*- coding: utf-8 -*-
import base64
import logging

from odoo import api, fields, models
from odoo.modules.module import get_module_resource
from odoo import tools, _

_logger = logging.getLogger(__name__)

GENDER = [
    ('male', u'男'),
    ('female', u'女'),
    ('other', u'其他')
]

MARITAL = [
    ('single', u'單身'),
    ('married', '已婚'),
    ('cohabitant', '合法同居'),
    ('widower', '喪偶'),
    ('divorced', '離婚')
]


class Employee(models.Model):
    _name = "ml.employee"
    _description = '''
        員工資訊
    '''

    @api.model
    def _default_image(self):
        image_path = get_module_resource('hr', 'static/src/img', 'default_image.png')
        return tools.image_resize_image_big(base64.b64encode(open(image_path, 'rb').read()))

    name = fields.Char(string=u'姓名')

    # image
    image = fields.Binary(string=u"照片", default=_default_image, attachment=True, help=u"上傳員工照片,<1024x1024px")
    image_medium = fields.Binary(string=u"中尺寸照片", attachment=True, help="128x128px照片")
    image_small = fields.Binary(string=u"小尺寸照片", attachment=True, help="64x64px照片")

    company_id = fields.Many2one('res.company', string=u'公司')

    gender = fields.Selection(GENDER, string=u'性別')
    country_id = fields.Many2one('res.country', string=u'國籍')
    birthday = fields.Date(string=u'生日')
    marital = fields.Selection(MARITAL, string=u'婚姻狀況', default='single')

    # work
    address = fields.Char(string=u'家庭住址')
    mobile_phone = fields.Char(string=u'手機號碼')
    work_email = fields.Char(string=u'工作郵箱')
    leader_id = fields.Many2one('ml.employee', string=u'所屬上級')
    subordinate_ids = fields.One2many('ml.employee', 'leader_id', string=u'下屬')

    note = fields.Text(string=u'備註資訊')

    @api.model
    def create(self, values):
        tools.image_resize_images(values)
        return super(Employee, self).create(values)

    @api.multi
    def write(self, values):
        tools.image_resize_images(values)
        return super(Employee, self).write(values)

我們一起來梳理一下類檔案的主要內容:

from odoo import api, fields, models 引入 api, fields, models

1、class類
odoo的class繼承了models.Model類,是odoo最常用的模型類,其他的還有 models.TransientModel,瞬時模型,用於嚮導(wizard),系統會在一定時間後自動清除模型的記錄 models.AbstractModel,抽象模型,和抽象類是一樣的概念,系統不會為該模型建立資料庫表 2、內部標識 _name = "ml.employee",為odoo類的唯一標識,如果沒有指定_table屬性,那麼系統將會為該模型建立資料庫表名為ml_employee的資料表。 _description:主要為描述資訊 3、使用的欄位 odoo模型的欄位使用fields.xxx來宣告。 1)Char:文字欄位 2)Binary:二進位制欄位,通常用於圖片、附件等檔案讀寫 3)Many2one:多對一關係欄位,如: company_id = fields.Many2one('res.company', string=u'公司') 表現為多個員工可以對應同一個公司,'res.company'是odoo內建公司模型 4)Selection:列表選擇欄位,第一個引數為元組列表,表示可選列表 5)Date: 日期控制元件欄位 6)One2many:一對多關係欄位,如: subordinate_ids = fields.One2many('ml.employee', 'leader_id', string=u'下屬') 表示一個員工可以有多個下屬 7)Text: 文字欄位,在前端表現為textarea,char在前端表現為input 4、屬性 string:表示欄位的顯示名稱 default:表示欄位的預設值 attachment:binary欄位的特有屬性,表現為是否以附件的形式儲存,設為True時,將會儲存到ir.attachment中 5、ORM 方法修飾器 @api.model:模型修飾器,相當於靜態方法,方法將為模型類共有,而不是每個例項。 @api.multi:對記錄集執行一些操作,方法的邏輯通常會包含對 self 的遍歷 6、方法 1)_default_image:我們獲取hr模組目錄下的圖片,賦值給image欄位作為預設值 2)create、write:重寫記錄的建立、編輯方法,使用odoo自帶的工具image_resize_images對image_medium,image_small進行賦值

寫完employee類之後,我們在與其同級的__init__.py中引入:

# -*- coding: utf-8 -*-

from . import employee

再在與models/目錄同級的__init__.py中引入models:

# -*- coding: utf-8 -*-

from . import models

到此,我們就已經建立好了employee的類模型,接著我們要為其寫檢視(views)

 4、編寫檢視

 我們先來看看odoo最常用的三種檢視:樹形(tree),表單(form),搜尋(search),它們儲存於odoo內建的ir.ui.view模型中,其他還有圖形(graph)、透視表(pivot)、日曆(calendar)、圖示(diagram)、甘特圖(gantt)、看板(kanban)、QWEB、活動(activity),是odoo的最主要的頁面展現形式。

1)tree檢視

 tree檢視為模型記錄(record)的列表展示形式

2)form檢視

 form檢視為表單展現形式,主要用於odoo記錄的建立,編輯。

3)search檢視

search檢視主要用於在tree、kanban等檢視中進行搜尋、過濾、分組記錄以方便檢視。

我們為我們的員工模型書寫這三種檢視:

新建views/employee.xml檔案,加入odoo data標籤:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
    </data>
</odoo>

先在data內增加一個form檢視:

<record id="view_ml_employee_form" model="ir.ui.view">
    <field name="name">員工資訊表單</field>
    <field name="model">ml.employee</field>
    <field name="arch" type="xml">
        <form string="員工資訊">
            <sheet>
                <field name="image" widget='image' class="oe_avatar"
                       options='{"preview_image":"image_medium"}'/>
                <div class="oe_title">
                    <label for="name" class="oe_edit_only"/>
                    <h1>
                        <field name="name" placeholder="員工姓名" required="True"/>
                    </h1>
                </div>
                <notebook>
                    <page string="員工資訊">
                        <group>
                            <group string="基本資訊">
                                <field name="gender" required="True"/>
                                <field name="country_id"/>
                                <field name="birthday"/>
                                <field name="marital"/>
                            </group>
                            <group string="工作資訊">
                                <field name="company_id" options="{'no_open': True, 'no_create': True}" groups="base.group_multi_company"/>
                                <field name="address"/>
                                <field name="mobile_phone" widget="phone"/>
                                <field name="work_email" widget="email"/>
                                <field name="leader_id" options="{'no_open': True, 'no_create': True}"/>
                            </group>
                        </group>
                    </page>
                    <page string="下屬資訊">
                        <field name="subordinate_ids">
                            <tree editable="bottom">
                                <field name="name" attrs="{'required': True}"/>
                                <field name="gender" required="True"/>
                                <field name="country_id"/>
                                <field name="mobile_phone"/>
                                <field name="work_email"/>
                            </tree>
                        </field>
                    </page>
                </notebook>
            </sheet>
        </form>
    </field>
</record> 

 我們來看看form表單的寫法:

寫一個form表單,實質上在為模型ir.ui.view增加一條記錄,odoo中為模型增加一條記錄可以使用record標籤,我們為它取了唯一的id:view_ml_employee_form(我們約定,記錄的書寫使用view_模型名_form/tree的命名方式),然後使用record內的model屬性指定增加的記錄屬於ir.ui.view模型。

我們先使用field標籤插入name和model,

<field name="name">員工資訊表單</field>
<field name="model">ml.employee</field>

代表我們是為ml.employee模型書寫的form檢視

我們在系統設定中開啟“開發者模式”,然後開啟設定->技術->使用者介面->檢視,可以看到系統中現在已有的記錄(完成開發並升級後):

我們使用

<field name="arch" type="xml">
</field>

插入form內容。

其中:

1、使用<form></form>標籤包裹表示記錄型別為form檢視
2、使用<field name="屬性名" />的方式顯示欄位
3、<notebook>
        <page>
        </page>
        <page>
        </page>
        ……
    </notebook>
為翻頁標籤
4、widget='image'為顯示型別為圖片
5、required="True"為必填,常用的還有invisible、readonly等
6、需要使用group包裹field以正常顯示欄位的string值

One2many欄位有特定的寫法:

<field name="subordinate_ids">
    <tree editable="bottom">
        <field name="name" attrs="{'required': True}"/>
        <field name="gender" required="True"/>
        <field name="country_id"/>
        <field name="mobile_phone"/>
        <field name="work_email"/>
    </tree>
</field>

欄位內嵌tree檢視來自定義顯示方式,editable="bottom"表示不彈出新視窗來建立明細記錄。

細心的朋友可能看到必填有 required=True 和 attrs="{'required': True}"兩種寫法,事實上,invisible, readonly也有這兩種寫法。

他們的區別在於:

required=True:這個寫法是死的,在檢視載入時就已經確定。
attrs="{"required": [('name', '!=', False)]}: 這種寫法可以書寫domain來過濾(上面的寫法也可以)。最重要的是,它會隨著name的變化來動態改變required的值。

更多詳細的介紹我們將會在後面views的專章介紹,這裡只要瞭解大概就可以了。

options="{'no_open': True, 'no_create': True}"

其主要作用在對於Many2one欄位,不允許其開啟和新建它的專有檢視。

Tips:
我們約定:many2one欄位的命名使用xxx_id,如lead_id;
        One2many欄位的命名我們使用xxx_ids,如subordinate_ids;

我們再為它書寫tree檢視:

<record id="view_ml_employee_tree" model="ir.ui.view">
    <field name="name">員工資訊列表</field>
    <field name="model">ml.employee</field>
    <field name="arch" type="xml">
        <tree string="員工資訊">
            <field name="name"/>
            <field name="company_id"/>
            <field name="gender"/>
            <field name="country_id"/>
            <field name="mobile_phone"/>
            <field name="work_email"/>
            <field name="leader_id"/>
        </tree>
    </field>
</record> 

Tree檢視相對比較簡單:

1、<tree></tree>包裹表示為tree檢視
2、羅列欄位以確定列表的顯示欄位以及顯示順序

我們再為其新增Search檢視:

<record id="view_ml_employee_filter" model="ir.ui.view">
    <field name="name">員工搜尋檢視</field>
    <field name="model">ml.employee</field>
    <field name="arch" type="xml">
        <search string="員工">
            <!--用於搜尋的欄位-->
            <field name="name" string="員工"
                   filter_domain="['|',('work_email','ilike',self),('name','ilike',self)]"/>
            <field name="gender" string="性別"/>
            <separator/>
            <!--定義好的過濾器-->
            <filter string="男員工" name="gender_male"
                    domain="[('gender', '=', 'male')]"/>
            <filter string="女員工" name="gender_female"
                    domain="[('gender', '=', 'female')]"/>
            <separator/>
            <!--分組-->
            <group expand="0" string="分組">
                <filter name="group_leader" string="領導" domain="[]" context="{'group_by':'leader_id'}"/>
                <filter name="group_company" string="Company" domain="[]" context="{'group_by':'company_id'}"
                        groups="base.group_multi_company"/>
            </group>
        </search>
    </field>
</record>

我們可以看到:

1、<search></search>包裹表示為search檢視
2、<field name="XXX" />宣告可以用於搜尋的欄位
3、<filter string="XXX" name="XXX" domain="XXX" />表示系統定義的過濾器
4、使用<group></group>包裹filter可以進行分組

實際效果如下:

1)搜尋

2)過濾器

 

 3)分組

 

 5、編寫動作和選單

我們寫好了tree、form和search檢視,我們需要編寫動作和選單來定義行為:

點選選單->觸發選單對應的action動作->展示action中繫結的檢視

 

我們繼續在data內增加:

<record model="ir.actions.act_window" id="view_ml_employee_action">
    <field name="name">員工資訊</field>
    <field name="res_model">ml.employee</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form</field>
    <field name="view_id" ref="view_ml_employee_tree"/>
    <field name="search_view_id" ref="view_ml_employee_filter"/>
</record>

我們可以看到,動作對應的系統model為ir.action.act_window,我們一樣可以在技術->動作->動作下找到我們定義的動作。

      view_mode:表示我們需要展示的檢視(有先後順序),tree檢視在最前面,我們觸發動作時首先展示的就是tree檢視;

           view_id:表示我們引用的檢視ref="view_ml_employee_tree",也就是我們在前面定義的tree檢視;

search_view_id:表示我們引用的過濾器為"view_ml_employee_filter";

根據上面需要引用tree和search我們不難推斷,可以為model定義多個tree、form和search檢視,通過不同的action,繫結不同的選單,可以觸發同一模型不同的展示檢視。

 

最後,我們為action新增一個選單,我們習慣於將選單使用單獨的檔案儲存,所以我們新建views/menu.xml檔案,書寫下列內容:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
        <!--一級選單-->
        <menuitem
                id="menu_employee_root"
                name="員工"
                web_icon="hr,static/description/icon.png"
                sequence="1"/>
        <!--二級選單 -->
        <menuitem
                id="menu_employee_info"
                name="員工資訊"
                parent="menu_employee_root"
                sequence="1"/>
        <!--三級選單 -->
        <menuitem
                id="menu_view_ml_employee_tree"
                name="員工檔案"
                action="view_ml_employee_action"
                parent="menu_employee_info"
                sequence="1"/>
    </data>
</odoo>

可以看到,我們使用menuitem標籤定義選單:

web_icon::一級選單特有屬性,表示展示的圖示,這裡我們借用hr模組的圖示

sequence:選單的展示順序

parent:上級選單,沒有定義則為一級選單

action:選單對應的動作,我們在三級選單中新增我們剛才編寫的action:view_ml_employee_action

 6、引用並安裝模組

在__manifest__.py->data中引用views:

'data': [
        'views/employee.xml',
        'views/menu.xml',
],

然後我們重啟伺服器,再開啟“開發者模式”,在應用頁面中重新整理本地列表

 再搜尋employee,點選安裝

 安裝之後我們就可以看到選單了:

 

 撒花!!!等等!看不到?為什麼呢!

Tips:Odoo12之前,admin使用者就是root使用者。Odoo12新增了root使用者,在使用者列表中不顯示,只在框架需要使用sudo增加許可權時在使用。admin依然登入系統並應擁有所有功能的訪問許可權,但不再能繞過訪問限制。

那我們有沒有辦法進入root使用者模式呢?有的:

首先,我們登入admin使用者,然後"啟用開發者模式",右上角進行"登出",你會發現登入頁面多了一個"以超級使用者登入"的方式

 

點選登入進去發現右上角有花紋,代表已經進入root模式,此時發現已經可以看到"員工模組"資訊。

但是我們不能每次都通過這種方式來訪問,而且其他使用者也沒有辦法對這個頁面進行訪問,所以我們要為它寫訪問許可權:

新建employee/security目錄,在security目錄下新建ir.model.access.csv檔案,增加內容:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ml_employee,員工檔案許可權,employee.model_ml_employee,,1,1,1,1

這樣檢視比較混亂,我們再pycharm檔案內->右鍵->edit as table->確定:

id:唯一標識
name:名稱
model_id:id:對應model,使用model_+下劃線格式模型_name作為標識
group_id:id:所屬群組資訊,這裡我們置空
perm_read、perm_write、perm_create、perm_unlink分別為讀、寫、創、刪四個許可權

在manifest中加上引用:

'data': [
    'security/ir.model.access.csv',

    'views/employee.xml',
    'views/menu.xml',
]

在應用介面中升級應用,ok~

 參考

1、Odoo官網引導: http://www.odoo.com/documentation/12.0/reference/guidelines.html

2、《Odoo12 Development Essentials --Fourth Edition》 --Daniel Reis

宣告

原文來自於部落格園(https://www.cnblogs.com/ljwTiey/p/11486885.html)

轉載請註明文章出處,文章如有任何版權問題,請聯絡作者刪除。

有任何問題,聯絡郵箱:[email protected]