由於自己並非資訊相關科系畢業,基礎知識與技能相當貧乏,透過此次學習Odoo系統基礎建置與開發,我才初次接觸了如何在Windows系統透過VM來使用Linux系統,以及終端機指令的操作,來一步一步建置Odoo系統,包括所需的Python套件、PostgreSQL資料庫系統、pgAdmin資料庫管理工具、Odoo系統資料夾結構、Odoo系統設定文件、PyCharm整合開發環境、系統更新與維護方式等等,需要了解與學習的技術非常非常的多,自己也僅學習使用了一小部分的工具與技巧而已。
系統開發由於牽涉到許多資料夾結構與不同文件如csv、py、xml等,因此未來使用Git做為示範檔案存放空間的需求或許也將越來越迫切,現階段仍純粹在網誌整理、發布文章;本系列一共六篇學習筆記,為花費大量時間綜合整理Peter Wu老師課堂教學、《Odoo快速入門與實戰》簡體書,以及零散的網路教學文章而得,前五篇的學習筆記完全操作使用Odoo社群版,第六篇的學習筆記則加入「康虎雲報表」的安裝與教學,可至「康虎軟件工作室」官方網站下載相關軟體,配合學習筆記的說明來安裝使用。
自學Odoo系統的建置開發相當不容易:在台灣,Odoo系統的知名度與普及性遠不如SAP、鼎新、Oracle等大廠,資訊廠商與開發人員的數量也相對稀少,就連參考書籍也完全沒有繁體中文的版本,僅有少數英文書與簡體書可以購買(而且這些書都寫得很糟),加上這幾年Odoo系統的快速發展,不同的版本之間有不小的變動,學會的技術可能過幾年就完全不能用了…。綜合上述許多原因,即便本系列學習筆記已經整理很清楚了,對於完全零經驗的開發者而言,相信仍舊難以閱讀、理解。
目前Odoo系統在歐美與中國大陸的資源、社群與討論,都比台灣頻繁、密集得多,想要入門Odoo系統的學習者建議還是需要老師來帶領,才有辦法掌握操作、管理、開發的各項眉眉角角。
l Odoo遵循MVC(Models, Views, Actions)設計原則進行設計,將架構分為:
數據層-採用PostgreSQL。
邏輯層-採用ORM,ORM是Odoo的引擎,會根據我們的Python方法與對應的實體自動生成SQL語法。
視圖層-展示層。
數據層-採用PostgreSQL。
邏輯層-採用ORM,ORM是Odoo的引擎,會根據我們的Python方法與對應的實體自動生成SQL語法。
視圖層-展示層。
l base模組中的模型主要可分為兩種:
訊息資料庫(Information Repository)-其模組的外部ID一般以"ir."開頭-
ir.action.act_window設置窗口的動作
ir.ui.menu設置選單
ir.ui.view用於處理視圖
ir.model用於模型
ir.model.fields用於處理模型的字段
資源(Resource)-其模組的外部ID一般以"res."開頭-
res.partner諸如客戶、供應商或聯絡人等都是基於這個模型
res.company設置公司相關的訊息
res.currency處理與貨幣相關的功能
res.country處理與國家相關的設置訊息
res.users用於處理與用戶相關的功能
res.groups與權限相關的功能都是基於這個模型
訊息資料庫(Information Repository)-其模組的外部ID一般以"ir."開頭-
ir.action.act_window設置窗口的動作
ir.ui.menu設置選單
ir.ui.view用於處理視圖
ir.model用於模型
ir.model.fields用於處理模型的字段
資源(Resource)-其模組的外部ID一般以"res."開頭-
res.partner諸如客戶、供應商或聯絡人等都是基於這個模型
res.company設置公司相關的訊息
res.currency處理與貨幣相關的功能
res.country處理與國家相關的設置訊息
res.users用於處理與用戶相關的功能
res.groups與權限相關的功能都是基於這個模型
Odoo ORM API
l 在終端機使用Python 3 Shell操作,資料庫名稱以odoo12為例,若成功執行,畫面將顯示如下圖:
cd /opt/odoo/odoo
python3 odoo-bin shell -c /etc/odoo.conf -d odoo12
self
cd /opt/odoo/odoo
python3 odoo-bin shell -c /etc/odoo.conf -d odoo12
self
l Odoo ORM是關係式,速度較慢,但方便關聯:
# 指向搜尋的標的,後接search()或browse()等方法
myrec_orm = self.env['res.partner'].search([])
# 顯示指向的標的,res.partner(3, 1)表示指向的id(Primary Key)
myrec_orm
# 顯示指向的每一筆資料,active欄位為True,該筆資料才會啟用
for rec in myrec_orm:
rec.name, rec.display_name, rec.active
# 指向搜尋的標的,後接search()或browse()等方法
myrec_orm = self.env['res.partner'].search([])
# 顯示指向的標的,res.partner(3, 1)表示指向的id(Primary Key)
myrec_orm
# 顯示指向的每一筆資料,active欄位為True,該筆資料才會啟用
for rec in myrec_orm:
rec.name, rec.display_name, rec.active
l SQL是選取出來的資料,速度較快,但因已無鏈列而不方便關聯:
# 指向搜尋的標的
self.env.cr.execute("select name, display_name, active from res_partner")
myrec_sql = self.env.cr.fetchall()
# 顯示指向的標的
myrec_sql
# SQL指令下,active欄位為False的資料也會顯示
for rec in myrec_sql:
rec[0], rec[1], rec[2]
# 指向搜尋的標的
self.env.cr.execute("select name, display_name, active from res_partner")
myrec_sql = self.env.cr.fetchall()
# 顯示指向的標的
myrec_sql
# SQL指令下,active欄位為False的資料也會顯示
for rec in myrec_sql:
rec[0], rec[1], rec[2]
l Odoo ORM可以選取計算的欄位:
myrec_orm = self.env['openacademy.score'].search([])
myrec_orm
for rec in myrec_orm:
rec.score_student.student_name, rec.score_total, rec.score_avg
myrec_orm = self.env['openacademy.score'].search([])
myrec_orm
for rec in myrec_orm:
rec.score_student.student_name, rec.score_total, rec.score_avg
l SQL不可以選取計算的欄位:
# 這會失敗,因為計算的欄位不存在於資料庫
self.env.cr.execute("select score_student, score_total, score_avg from openacademy_score")
# 這會失敗,因為計算的欄位不存在於資料庫
self.env.cr.execute("select score_student, score_total, score_avg from openacademy_score")
l 使用Odoo ORM API新增資料:
myrec = self.env['openacademy.student'].search([])
myrec.create({'student_no': 'S001', 'student_name': 'Peter', 'student_class': '1'})
# 執行指令,只需在Shell中使用,或exit()後自動執行指令
self.env.cr.commit()
myrec = self.env['openacademy.student'].search([])
myrec.create({'student_no': 'S001', 'student_name': 'Peter', 'student_class': '1'})
# 執行指令,只需在Shell中使用,或exit()後自動執行指令
self.env.cr.commit()
l 使用Odoo ORM API修改資料:
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
# 確保選到正確的資料
myrec.student_no
myrec.write({'student_name': 'Timmy'})
self.env.cr.commit()
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
# 確保選到正確的資料
myrec.student_no
myrec.write({'student_name': 'Timmy'})
self.env.cr.commit()
l 使用Odoo ORM API刪除資料:
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
myrec.student_no
myrec.unlink()
self.env.cr.commit()
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
myrec.student_no
myrec.unlink()
self.env.cr.commit()
l 使用Odoo ORM API操作紀錄集:
myrec = self.env['res.partner'].search([])
len(myrec)
myrec.ids
myrec.mapped('name')
myrec.mapped(lambda rec: (rec.id, rec.name))
myrec.sorted(key=lambda rec: rec.id, reverse=True)
myrec_2 = myrec.filtered(lambda rec: rec.name.startswith('s'))
myrec_2
myrec = self.env['res.partner'].search([])
len(myrec)
myrec.ids
myrec.mapped('name')
myrec.mapped(lambda rec: (rec.id, rec.name))
myrec.sorted(key=lambda rec: rec.id, reverse=True)
myrec_2 = myrec.filtered(lambda rec: rec.name.startswith('s'))
myrec_2
l 使用Odoo ORM API操作日期和時間:
from odoo import fields
import datetime
# 將字串轉換為date或datetime
fields.Datetime.from_string('2018-07-26 08:56:27')
# 將date或datetime轉換為字串
fields.Datetime.to_string(fields.Datetime.now())
fields.Date.today()
fields.Datetime.now()
# 複製一個已有的紀錄
myrec = self.env.ref('base.user_root')
myrec_2 = myrec.copy({'name': 'admin'})
# 返回該筆紀錄的日期
fields.Date.context_today(myrec, timestamp=None)
fields.Datetime.context_timestamp(myrec_2, timestamp=datetime.datetime.now())
from odoo import fields
import datetime
# 將字串轉換為date或datetime
fields.Datetime.from_string('2018-07-26 08:56:27')
# 將date或datetime轉換為字串
fields.Datetime.to_string(fields.Datetime.now())
fields.Date.today()
fields.Datetime.now()
# 複製一個已有的紀錄
myrec = self.env.ref('base.user_root')
myrec_2 = myrec.copy({'name': 'admin'})
# 返回該筆紀錄的日期
fields.Date.context_today(myrec, timestamp=None)
fields.Datetime.context_timestamp(myrec_2, timestamp=datetime.datetime.now())
l 更多ORM API用法:
self.env.user目前使用者的紀錄
self.env.uid目前使用者的ID,等於self.env.user.id
self.env.context系統語系等紀錄
self.env.cr.savepoint()設置一個保存點,在rollback時使用
self.env.cr.rollback()取消目前的更新操作,rollback到上次設置的保存點
self.env.user目前使用者的紀錄
self.env.uid目前使用者的ID,等於self.env.user.id
self.env.context系統語系等紀錄
self.env.cr.savepoint()設置一個保存點,在rollback時使用
self.env.cr.rollback()取消目前的更新操作,rollback到上次設置的保存點
Odoo ORM API - One2many和Many2many字段專用的操作方式
l One2many和Many2many字段專用的特殊格式:
(模式, 子資料id(Primary Key),需進資料庫查看, 資料內容)
(模式, 子資料id(Primary Key),需進資料庫查看, 資料內容)
l 模式0-在特定的母資料新增子資料:
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(0, _, {'student_no': 'S002', 'student_name': 'Stanley', 'student_class': '3'})]})
self.env.cr.commit()
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(0, _, {'student_no': 'S002', 'student_name': 'Stanley', 'student_class': '3'})]})
self.env.cr.commit()
l 模式1-在特定的母資料修改特定的子資料:
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(1, 3, {'student_contact': 'LAN-SIR'})]})
self.env.cr.commit()
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(1, 3, {'student_contact': 'LAN-SIR'})]})
self.env.cr.commit()
l 模式2-在特定的母資料刪除特定的子資料:
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(2, 3, _)]})
self.env.cr.commit()
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(2, 3, _)]})
self.env.cr.commit()
l 模式3-在特定的母資料取消關聯特定的子資料
(子資料不能設定為cascade,否則不是取消關聯而是刪除):
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(3, 4, _)]})
self.env.cr.commit()
(子資料不能設定為cascade,否則不是取消關聯而是刪除):
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(3, 4, _)]})
self.env.cr.commit()
l 模式4-在特定的母資料新增特定的子資料:
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(4, 1, _)]})
self.env.cr.commit()
myrec = self.env['openacademy.studentclass'].search([('studentclass_name', '=', 'C001')])
myrec.studentclass_name
myrec.write({'studentclass_line': [(4, 1, _)]})
self.env.cr.commit()
l 模式5-在特定的母資料刪除所有的子資料:
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
myrec.student_no
myrec.write({'student_course': [(5, _, _)]})
self.env.cr.commit()
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
myrec.student_no
myrec.write({'student_course': [(5, _, _)]})
self.env.cr.commit()
l 模式6-在特定的母資料替換所有的子資料:
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
myrec.student_no
myrec.write({'student_course': [(6, _, [2, 3])]})
self.env.cr.commit()
myrec = self.env['openacademy.student'].search([('student_no', '=', 'S001')])
myrec.student_no
myrec.write({'student_course': [(6, _, [2, 3])]})
self.env.cr.commit()
i18n資料夾-翻譯文件po檔
l Step 1啟動欲翻譯的語言:
Settings > Activate the developer mode
Translations > Languages > Active欲翻譯的語言 > Update Terms > Load
Settings > Activate the developer mode
Translations > Languages > Active欲翻譯的語言 > Update Terms > Load
l Step 2匯出欲翻譯語言的po檔:
Translations > Export Translation >
Language選擇欲翻譯的語言,
File Format選擇PO File,
Apps To Export選擇翻譯的模組 > Export > 點選po檔,此例為zh_TW.po
Translations > Export Translation >
Language選擇欲翻譯的語言,
File Format選擇PO File,
Apps To Export選擇翻譯的模組 > Export > 點選po檔,此例為zh_TW.po
l Step 3將翻譯內容填寫至po檔的msgstr,msgid則是原文內容。
l Step 4將翻譯完成的po檔匯入系統,客製化開發模組的po檔要放在i18n資料夾(po檔檔名都不可以修改):
Translations > Import Translation > 填寫Language Name、Code,匯入File > Import
Translations > Import Translation > 填寫Language Name、Code,匯入File > Import
l Step 5變更使用者使用的語言,並重新登入系統:
Users & Companies > Users > Preferences > Language
Users & Companies > Users > Preferences > Language
wizards資料夾-彈出式視窗精靈
l 習慣上,wizards資料夾是一個獨立的Package,所有wizards相關檔案皆放置於此。
l Step 1在openacademy資料夾內修改__init__.py:
# 原程式碼增加以下程式碼
from . import wizards
# 原程式碼增加以下程式碼
from . import wizards
l Step 2在wizards資料夾內創建openacademy_student_search_wizard.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
# models.TransientModel用在臨時欄位
class openacademystudentsearchwizard(models.TransientModel):
_name = "openacademy.student_search_wizard"
student_name = fields.Char(string = '學生姓名')
student_class = fields.Selection([('1', '一年級'), ('2', '二年級'), ('3', '三年級')], string = '年級')
student_fm = fields.Selection([('M', '男'), ('F', '女')], string = '性別')
def run_student_search(self):
# 得到的domain格式為[('', 'ilike', ''), ('', '=', ''), ('', '=', '')]
domain = []
if self.student_name:
domain.append(('student_name', 'ilike', self.student_name))
if self.student_class:
domain.append(('student_class', '=', self.student_class))
if self.student_fm:
domain.append(('student_fm', '=', self.student_fm))
if not domain:
domain = [(1, '=', 1)]
myviewid = self.env.ref('openacademy.view_openacademy_student_tree')
return {'view_name': 'openacademystudentwizard',
'name': ('Openacademy Student Data'),
'views': [[False, 'tree'], [False, 'form']],
'res_model': 'openacademy.student',
'context': self._context,
'type': 'ir.actions.act_window',
'view_id': myviewid.id,
# target有self與main選項,self於頁面上方保留歷程
'target': 'main',
'domain': domain,
'flags': {'action_buttons': True},
'view_mode': 'form',
'view_type': 'form'
}
# -*- coding: utf-8 -*-
# Author: Peter Wu
from odoo import models, fields, api
from odoo.exceptions import UserError
# models.TransientModel用在臨時欄位
class openacademystudentsearchwizard(models.TransientModel):
_name = "openacademy.student_search_wizard"
student_name = fields.Char(string = '學生姓名')
student_class = fields.Selection([('1', '一年級'), ('2', '二年級'), ('3', '三年級')], string = '年級')
student_fm = fields.Selection([('M', '男'), ('F', '女')], string = '性別')
def run_student_search(self):
# 得到的domain格式為[('', 'ilike', ''), ('', '=', ''), ('', '=', '')]
domain = []
if self.student_name:
domain.append(('student_name', 'ilike', self.student_name))
if self.student_class:
domain.append(('student_class', '=', self.student_class))
if self.student_fm:
domain.append(('student_fm', '=', self.student_fm))
if not domain:
domain = [(1, '=', 1)]
myviewid = self.env.ref('openacademy.view_openacademy_student_tree')
return {'view_name': 'openacademystudentwizard',
'name': ('Openacademy Student Data'),
'views': [[False, 'tree'], [False, 'form']],
'res_model': 'openacademy.student',
'context': self._context,
'type': 'ir.actions.act_window',
'view_id': myviewid.id,
# target有self與main選項,self於頁面上方保留歷程
'target': 'main',
'domain': domain,
'flags': {'action_buttons': True},
'view_mode': 'form',
'view_type': 'form'
}
l Step 3在wizards資料夾內創建__init__.py:
# -*- coding: utf-8 -*-
# Author: Peter Wu
from . import openacademy_student_search_wizard
# -*- coding: utf-8 -*-
# Author: Peter Wu
from . import openacademy_student_search_wizard
l Step 4在wizards資料夾內創建openacademy_student_search_wizard.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="openacademy_student_form_wizard" model="ir.ui.view">
<field name="name">openacademy.student.form.wizard</field>
<field name="model">openacademy.student_search_wizard</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<h1>學生資料複合查詢</h1>
<group>
<field name="student_name"/>
<field name="student_class"/>
<field name="student_fm"/>
</group>
</sheet>
<footer>
<button type="object" name="run_student_search" string="查詢" class="oe_highlight"/>
<button special="cancel" string="離開"/>
</footer>
</form>
</field>
</record>
<act_window id="action_openacademy_student_search_view" name="學生資料複合查詢" res_model="openacademy.student_search_wizard" src_model="" view_mode="form" target="new" multi="False"/>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="openacademy_student_form_wizard" model="ir.ui.view">
<field name="name">openacademy.student.form.wizard</field>
<field name="model">openacademy.student_search_wizard</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<h1>學生資料複合查詢</h1>
<group>
<field name="student_name"/>
<field name="student_class"/>
<field name="student_fm"/>
</group>
</sheet>
<footer>
<button type="object" name="run_student_search" string="查詢" class="oe_highlight"/>
<button special="cancel" string="離開"/>
</footer>
</form>
</field>
</record>
<act_window id="action_openacademy_student_search_view" name="學生資料複合查詢" res_model="openacademy.student_search_wizard" src_model="" view_mode="form" target="new" multi="False"/>
</data>
</odoo>
l Step 5在openacademy資料夾內修改__manifest__.py:
'data': [
'wizards/openacademy_student_search_wizard.xml',
],
'data': [
'wizards/openacademy_student_search_wizard.xml',
],
l Step 6在views資料夾內修改openacademy_menu.xml:
<!--原程式碼增加以下程式碼-->
<menuitem id="menu_openacademy_search_categ"
name="複合查詢"
parent="menu_openacademy_root"
sequence="30"/>
<menuitem id="menu_openacademy_student_search_view"
name="" parent="menu_openacademy_search_categ"
action="action_openacademy_student_search_view"
sequence="10"/>
<!--原程式碼增加以下程式碼-->
<menuitem id="menu_openacademy_search_categ"
name="複合查詢"
parent="menu_openacademy_root"
sequence="30"/>
<menuitem id="menu_openacademy_student_search_view"
name="" parent="menu_openacademy_search_categ"
action="action_openacademy_student_search_view"
sequence="10"/>
權限劃分-表單檢視權限不同
l Step 1在security資料夾內創建openacademy_menu_rule.xml;
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_data再按Tab鍵、
輸入odoo_security_category再按Tab鍵、
輸入odoo_security_group再按Tab鍵:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="security_openacademy_categ" model="ir.module.category">
<field name="name">學生管理權限</field>
<!--eval常用於計算,例如計算昨天<field name="change_date" eval="(dateime.now()+timedelta(-1)).strftime('%Y-%m-%d')"/>-->
<field name="sequence" eval="1"/>
</record>
<record id="group_openacademy_1" model="res.groups">
<field name="name">學生</field>
<field name="category_id" ref="security_openacademy_categ"/>
<!--Odoo ORM API的模式4-->
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="group_openacademy_2" model="res.groups">
<field name="name">教員</field>
<field name="category_id" ref="security_openacademy_categ"/>
<field name="implied_ids" eval="[(4, ref('group_openacademy_1'))]"/>
</record>
<record id="group_openacademy_3" model="res.groups">
<field name="name">校長</field>
<field name="category_id" ref="security_openacademy_categ"/>
<field name="implied_ids" eval="[(4, ref('group_openacademy_2'))]"/>
</record>
</data>
</odoo>
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_data再按Tab鍵、
輸入odoo_security_category再按Tab鍵、
輸入odoo_security_group再按Tab鍵:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="security_openacademy_categ" model="ir.module.category">
<field name="name">學生管理權限</field>
<!--eval常用於計算,例如計算昨天<field name="change_date" eval="(dateime.now()+timedelta(-1)).strftime('%Y-%m-%d')"/>-->
<field name="sequence" eval="1"/>
</record>
<record id="group_openacademy_1" model="res.groups">
<field name="name">學生</field>
<field name="category_id" ref="security_openacademy_categ"/>
<!--Odoo ORM API的模式4-->
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="group_openacademy_2" model="res.groups">
<field name="name">教員</field>
<field name="category_id" ref="security_openacademy_categ"/>
<field name="implied_ids" eval="[(4, ref('group_openacademy_1'))]"/>
</record>
<record id="group_openacademy_3" model="res.groups">
<field name="name">校長</field>
<field name="category_id" ref="security_openacademy_categ"/>
<field name="implied_ids" eval="[(4, ref('group_openacademy_2'))]"/>
</record>
</data>
</odoo>
l Step 2在openacademy資料夾內修改__manifest__.py:
'data': [
'security/openacademy_menu_rule.xml',
],
'data': [
'security/openacademy_menu_rule.xml',
],
l Step 3在views資料夾內修改openacademy_menu.xml:
<!--增加groups,將欲對應的record id與menuitem groups關聯起來-->
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="menu_openacademy_root"
name="學生成績系統"
sequence="200"/>
<menuitem id="menu_openacademy_categ"
name="基礎資料"
parent="menu_openacademy_root"
sequence="10"/>
<menuitem id="menu_openacademy_student_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_1"
action="action_openacademy_student_view"
sequence="10"/>
<menuitem id="menu_openacademy_teacher_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_3"
action="action_openacademy_teacher_view"
sequence="20"/>
<menuitem id="menu_openacademy_studentclass_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_2"
action="action_openacademy_studentclass_view"
sequence="30"/>
<menuitem id="menu_openacademy_course_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_3"
action="action_openacademy_course_view" sequence="40"/>
<menuitem id="menu_openacademy_score_categ"
name="成績紀錄"
parent="menu_openacademy_root"
sequence="20"/>
<menuitem id="menu_openacademy_score_view"
name="" parent="menu_openacademy_score_categ"
groups="group_openacademy_1"
action="action_openacademy_score_view"
sequence="10"/>
<menuitem id="menu_openacademy_search_categ"
name="複合查詢"
parent="menu_openacademy_root"
sequence="30"/>
<menuitem id="menu_openacademy_student_search_view"
name="" parent="menu_openacademy_search_categ"
action="action_openacademy_student_search_view"
sequence="10"/>
</data>
</odoo>
<!--增加groups,將欲對應的record id與menuitem groups關聯起來-->
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="menu_openacademy_root"
name="學生成績系統"
sequence="200"/>
<menuitem id="menu_openacademy_categ"
name="基礎資料"
parent="menu_openacademy_root"
sequence="10"/>
<menuitem id="menu_openacademy_student_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_1"
action="action_openacademy_student_view"
sequence="10"/>
<menuitem id="menu_openacademy_teacher_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_3"
action="action_openacademy_teacher_view"
sequence="20"/>
<menuitem id="menu_openacademy_studentclass_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_2"
action="action_openacademy_studentclass_view"
sequence="30"/>
<menuitem id="menu_openacademy_course_view"
name="" parent="menu_openacademy_categ"
groups="group_openacademy_3"
action="action_openacademy_course_view" sequence="40"/>
<menuitem id="menu_openacademy_score_categ"
name="成績紀錄"
parent="menu_openacademy_root"
sequence="20"/>
<menuitem id="menu_openacademy_score_view"
name="" parent="menu_openacademy_score_categ"
groups="group_openacademy_1"
action="action_openacademy_score_view"
sequence="10"/>
<menuitem id="menu_openacademy_search_categ"
name="複合查詢"
parent="menu_openacademy_root"
sequence="30"/>
<menuitem id="menu_openacademy_student_search_view"
name="" parent="menu_openacademy_search_categ"
action="action_openacademy_student_search_view"
sequence="10"/>
</data>
</odoo>
l Step 4創建新的使用者來驗證:
Settings > Users & Companies > Users > Create >
設定Application Accesses、Notification Management Handle in Odoo >
完成後回到Users,左邊核取方塊打勾 > Action > Change Password
Settings > Users & Companies > Users > Create >
設定Application Accesses、Notification Management Handle in Odoo >
完成後回到Users,左邊核取方塊打勾 > Action > Change Password
權限劃分-繼續設定,資料檢視權限不同(員工、部門)
l Step 1此功能需事先安裝Employee模組,在models資料夾內修改openacademy_student.py:
# 原程式碼增加以下程式碼
student_employee = fields.Many2one('hr.employee', string = 'Employee')
# 原程式碼增加以下程式碼
student_employee = fields.Many2one('hr.employee', string = 'Employee')
l Step 2在views資料夾內修改openacademy_student.xml:
<!--原程式碼tree和form皆增加以下程式碼-->
<field name="student_employee"/>
<!--原程式碼tree和form皆增加以下程式碼-->
<field name="student_employee"/>
l Step 3在security資料夾內創建openacademy_student_rule.xml;
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_data再按Tab鍵、
輸入odoo_security_rule再按Tab鍵:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--學生權限,依學生帳號不同,檢視學生自己的資料-->
<record id="openacademy_student_rule1" model="ir.rule">
<field name="name">openacademy.student.rule1</field>
<field ref="model_openacademy_student" name="model_id"/>
<field name="domain_force">[('student_employee.id', '=', user.employee_ids.id )]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_1'))]"/>
</record>
<!--教員權限,依教員部門不同,檢視所屬部門學生的資料-->
<record id="openacademy_student_rule2" model="ir.rule">
<field name="name">openacademy_student_rule2</field>
<field ref="model_openacademy_student" name="model_id"/>
<field name="domain_force">[('student_employee.department_id.id', 'in', [x.department_id.id for x in user.employee_ids])]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_2'))]"/>
</record>
<!--校長權限,擁有所有資料檢視權限-->
<record id="openacademy_student_rule3" model="ir.rule">
<field name="name">openacademy_student_rule3</field>
<field ref="model_openacademy_student" name="model_id"/>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_3'))]"/>
<field name="global" eval="1"/>
</record>
</data>
</odoo>
因為已將setting.jar匯入Live templates,可使用編輯技巧-
輸入odoo_data再按Tab鍵、
輸入odoo_security_rule再按Tab鍵:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--學生權限,依學生帳號不同,檢視學生自己的資料-->
<record id="openacademy_student_rule1" model="ir.rule">
<field name="name">openacademy.student.rule1</field>
<field ref="model_openacademy_student" name="model_id"/>
<field name="domain_force">[('student_employee.id', '=', user.employee_ids.id )]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_1'))]"/>
</record>
<!--教員權限,依教員部門不同,檢視所屬部門學生的資料-->
<record id="openacademy_student_rule2" model="ir.rule">
<field name="name">openacademy_student_rule2</field>
<field ref="model_openacademy_student" name="model_id"/>
<field name="domain_force">[('student_employee.department_id.id', 'in', [x.department_id.id for x in user.employee_ids])]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_2'))]"/>
</record>
<!--校長權限,擁有所有資料檢視權限-->
<record id="openacademy_student_rule3" model="ir.rule">
<field name="name">openacademy_student_rule3</field>
<field ref="model_openacademy_student" name="model_id"/>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_3'))]"/>
<field name="global" eval="1"/>
</record>
</data>
</odoo>
l Step 4在openacademy資料夾內修改__manifest__.py:
'data': [
'security/openacademy_student_rule.xml',
],
'data': [
'security/openacademy_student_rule.xml',
],
l Step 5在security資料夾內創建openacademy_score_rule.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--學生權限,依學生帳號不同,檢視學生自己的資料-->
<record id="openacademy_score_rule1" model="ir.rule">
<field name="name">openacademy.score.rule1</field>
<field ref="model_openacademy_score" name="model_id"/>
<field name="domain_force">[('score_student.student_employee.id', '=', user.employee_ids.id )]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_1'))]"/>
</record>
<!--教員權限,依教員部門不同,檢視所屬部門學生的資料-->
<record id="openacademy_score_rule2" model="ir.rule">
<field name="name">openacademy.score.rule2</field>
<field ref="model_openacademy_score" name="model_id"/>
<field name="domain_force">[('score_student.student_employee.department_id.id', 'in', [x.department_id.id for x in user.employee_ids])]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_2'))]"/>
</record>
<!--校長權限,擁有所有資料檢視權限-->
<record id="openacademy_score_rule3" model="ir.rule">
<field name="name">openacademy.score.rule3</field>
<field ref="model_openacademy_score" name="model_id"/>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_3'))]"/>
<field name="global" eval="1"/>
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--學生權限,依學生帳號不同,檢視學生自己的資料-->
<record id="openacademy_score_rule1" model="ir.rule">
<field name="name">openacademy.score.rule1</field>
<field ref="model_openacademy_score" name="model_id"/>
<field name="domain_force">[('score_student.student_employee.id', '=', user.employee_ids.id )]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_1'))]"/>
</record>
<!--教員權限,依教員部門不同,檢視所屬部門學生的資料-->
<record id="openacademy_score_rule2" model="ir.rule">
<field name="name">openacademy.score.rule2</field>
<field ref="model_openacademy_score" name="model_id"/>
<field name="domain_force">[('score_student.student_employee.department_id.id', 'in', [x.department_id.id for x in user.employee_ids])]</field>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_2'))]"/>
</record>
<!--校長權限,擁有所有資料檢視權限-->
<record id="openacademy_score_rule3" model="ir.rule">
<field name="name">openacademy.score.rule3</field>
<field ref="model_openacademy_score" name="model_id"/>
<field name="groups" eval="[(4, ref('openacademy.group_openacademy_3'))]"/>
<field name="global" eval="1"/>
</record>
</data>
</odoo>
l Step 6在openacademy資料夾內修改__manifest__.py:
'data': [
'security/openacademy_score_rule.xml',
],
'data': [
'security/openacademy_score_rule.xml',
],
l Step 7關聯Odoo系統使用者與Employee模組來驗證學生員工權限:
Employees > Employees > Create > HR Setings > Related User > Save
新增的student_employee欄位也要先關聯好資料,學生登入系統時僅能檢視學生自己的資料。
Employees > Employees > Create > HR Setings > Related User > Save
新增的student_employee欄位也要先關聯好資料,學生登入系統時僅能檢視學生自己的資料。
l Step 8關聯Odoo系統使用者與Employee模組來驗證教員部門權限:
Employees > Departments > Create > Save
Employees > Employees > Edit > Work Information > Department
讓多位學生與一位教員放在同一個部門,教員登入系統時僅能檢視所屬部門學生的資料。
Employees > Departments > Create > Save
Employees > Employees > Edit > Work Information > Department
讓多位學生與一位教員放在同一個部門,教員登入系統時僅能檢視所屬部門學生的資料。
沒有留言:
張貼留言