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]