本文介紹了新版Odoo編碼指南。那些旨在提高代碼的品質 (例如更好的可讀性)和Odoo應用程式。實際上,适當的代碼簡化了維護、調試,降低了複雜性,提高了可靠性。
這些指導原則應适用于每一個新的子產品和新的程式。隻有在代碼重構(遷移到新API、大重構、……)的情況下,這些準則才适用于舊子產品。
警告
這些指南是用新的子產品和新的檔案編寫的。當修改現有檔案時,檔案的原始樣式嚴格地取代任何其他樣式指南。 換句話說,不要修改現有檔案以應用這些指南,以避免破壞每一行的修訂曆史。有關更多細節,請參見pull request guide
子產品結構
目錄
在重要目錄中組織一個子產品。這些包含業務邏輯;檢視它們應該了解子產品的用途。
- data/ : demo和資料xml
- models/ : 模型定義
- controllers/ : 包含控制器(HTTP路由)
- views/ : 包含視圖和模闆
- static/ : 包含Web使用的靜态檔案,分别為 css/, js/, img/, lib/, ...
其他可選目錄組成子產品。
- wizard/ : 重組瞬态模型(以前的osv_memory)和它們的視圖
- report/ : 包含報表(RML報表[deprecated], 基于SQL視圖(為報表)的模型和其他複雜的報表)。Python對象和XML視圖包含在目錄中
- tests/ : 包含Python/YML測試
檔案命名
對于視圖的聲明,從(前端)模版分裂後端視圖到2個不同的檔案中。
對于models,通過一組模型将業務邏輯拆分,在每一個集合中選擇一個主模型,該模型将其名稱賦予該集合。如果隻有一個模型,則其名稱與子產品名相同。或每一組命名為<main_model>的以下檔案可以建立:
-
models/<main_model>.py
-
models/<inherited_main_model>.py
-
views/<main_model>_templates.xml
-
views/<main_model>_views.xml
例如,銷售子產品介紹
sale_order
和
sale_order_line
,在它們中
sale_order
為主。是以
<main_model>
檔案将命名為
models/sale_order.py
和
views/sale_order_views.py
對于資料,按目的拆分:demo或data。檔案名将是主模型的名字,使用 _demo.xml 或 _data.xml作為字尾
對于controllers, 唯一的檔案應該命名為main.py。否則,如果需要從另一子產品繼承現有控制器,它的名字将是<module_name>.py。與模型不同,每個控制器類應該包含在分離的檔案中
對與static檔案,由于資源可以在不同的上下文中使用 (前端,後端,兩者都是),它們将包含在僅有的一個bundle中。是以,CSS/Less, JavaScript和XML檔案使用bundle類型名字作為字尾。即: im_chat_common.css, im_chat_common.js在'assets_common' bundle中, 且im_chat_backend.css, im_chat_backend.js在'assets_backend' bundle中。如果子產品隻擁有一個檔案,則該約定将是<module_name>.ext (即: project.js)。不要連結到Odoo以外的資料(圖檔,庫):不要對圖檔使用一個URL但是可以代替的拷貝它到我們的代碼庫中。
關于data, 按目的拆分它們:data或demo。檔案名将是主模型的名字,以_data.xml或 _demo.xml作為字尾
關于向導,命名約定是:
-
<main_transient>.py
-
<main_transient>_views.xml
<main_transient>是占主導地位的瞬态模型的名字,就像模型。<main_transient>.py可以包含模型'model.action'和'model.action.line'
對于統計報告,它們的名字應該看起來像:
-
<report_name_A>_report.py
-
(通常是pivot和graph視圖)<report_name_A>_report_views.py
對于可列印報表,您應該具有:
-
(報表動作,表格格式定義, ...)<print_report_name>_reports.py
-
(xml報表模版)<print_report_name>_templates.xml
完整的樹應該看起來像
addons/<my_module_name>/
|-- __init__.py
|-- __manifest__.py
|-- controllers/
| |-- __init__.py
| |-- <inherited_module_name>.py
| `-- main.py
|-- data/
| |-- <main_model>_data.xml
| `-- <inherited_main_model>_demo.xml
|-- models/
| |-- __init__.py
| |-- <main_model>.py
| `-- <inherited_main_model>.py
|-- report/
| |-- __init__.py
| |-- <main_stat_report_model>.py
| |-- <main_stat_report_model>_views.xml
| |-- <main_print_report>_reports.xml
| `-- <main_print_report>_templates.xml
|-- security/
| |-- ir.model.access.csv
| `-- <main_model>_security.xml
|-- static/
| |-- img/
| | |-- my_little_kitten.png
| | `-- troll.jpg
| |-- lib/
| | `-- external_lib/
| `-- src/
| |-- js/
| | `-- <my_module_name>.js
| |-- css/
| | `-- <my_module_name>.css
| |-- less/
| | `-- <my_module_name>.less
| `-- xml/
| `-- <my_module_name>.xml
|-- views/
| |-- <main_model>_templates.xml
| |-- <main_model>_views.xml
| |-- <inherited_main_model>_templates.xml
| `-- <inherited_main_model>_views.xml
`-- wizard/
|-- <main_transient_A>.py
|-- <main_transient_A>_views.xml
|-- <main_transient_B>.py
`-- <main_transient_B>_views.xml
注
檔案命名應該僅包含
[a-z0-9_]
(小寫字母數字和
_
)
警告
使用正确的檔案權限 : 檔案夾755和檔案644(針對非windows系統)
XML檔案
格式
為了在XML中聲明記錄,建議使用record 記号(使用<record>) :
- 在
屬性前放model
屬性id
- 對于字段聲明,首先是
屬性。然後将值放在field标記中,或者在name
屬性中,最後根據重要性排序其他屬性(widget, options, ...)eval
- 嘗試按模型分組記錄。在action/menu/views之間的依賴性的情況下,此約定可能不适用
- 使用下一點定義的命名約定
- 标記<data>僅用于設定使用noupdate=1的不可更新的資料
<record id="view_id" model="ir.ui.view">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<tree>
<field name="my_field_1"/>
<field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" />
</tree>
</field>
</record>
Odoo支援自定義标簽充當文法糖:
- menuitem: 使用它作為聲明ir.ui.menu的快捷方式
- workflow: <workflow>标記向現有工作流發送信号
- template: 使用它聲明一個僅僅需要視圖的arch部分的QWeb視圖
- report: 用于聲明一個報表操作
- act_window: 如果記錄符号不能做你想做的事,就用它
4個首選的标記在record标記之前是優先考慮的
命名xml_id
安全, 視圖和操作
使用以下模式:
- 對于菜單:
<model_name>_menu
- 對于視圖:
, view_type 是<model_name>_view_<view_type>
,kanban
,form
,tree
, ...search
- 對于操作: 主要的操作格式
。另一些則用<model_name>_action
字尾,其中細節是一個小寫字元串,簡要解釋操作。隻有在為模型聲明多個操作時才使用此方法_<detail>
- 對于分組:
group_name是組的名稱,通常是'user', 'manager', ...<model_name>_group_<group_name>
- 對于規則:
concerned_group是連接配接的組的簡潔名稱('user' 對于'model_name_group_user', 'public' 對于公共使用者, 'company'對于多公司規則, ...)<model_name>_rule_<concerned_group>
<!-- views and menus -->
<record id="model_name_view_form" model="ir.ui.view">
...
</record>
<record id="model_name_view_kanban" model="ir.ui.view">
...
</record>
<menuitem
id="model_name_menu_root"
name="Main Menu"
sequence="5"
/>
<menuitem
id="model_name_menu_action"
name="Sub Menu 1"
parent="module_name.module_name_menu_root"
action="model_name_action"
sequence="10"
/>
<!-- actions -->
<record id="model_name_action" model="ir.actions.act_window">
...
</record>
<record id="model_name_action_child_list" model="ir.actions.act_window">
...
</record>
<!-- security -->
<record id="module_name_group_user" model="res.groups">
...
</record>
<record id="model_name_rule_public" model="ir.rule">
...
</record>
<record id="model_name_rule_company" model="ir.rule">
...
</record>
注
視圖名稱使用點标記
my.model.view_type
或者
my.model.view_type.inherit
而不是"這是我的模型的表單視圖".
繼承的XML
繼承視圖的命名模式是
<base_view>_inherit_<current_module_name>
。子產品隻能擴充視圖一次。用
_inherit_<current_module_name>
加字尾到原始的名稱,其中current_module_name 是擴充視圖的子產品的技術名稱
<record id="inherited_model_view_form_inherit_my_module" model="ir.ui.view">
...
</record>
Python
PEP8選項
使用linter可以幫助顯示文法和語義警告或錯誤。Odoo源代碼試圖尊重Python标準,但其中一些可以被忽略。
- E501: 行太長
- E301: 預計1空行,發現
- E302: 預計2條空行,發現1條
- E126: 懸停縮進延拓線
- E123: 閉合托架與開口托架線壓痕不比對
- E127: 為可視縮進顯示過度縮進線
- E128: 為可視縮進顯示連續的縮進線
- E265: 方塊注釋應以'# '開始
導入
導入順序如下
- 外部庫(在python stdlib中對每行進行排序和拆分)
- odoo的導入
- 從Odoo子產品導入(很少,隻有在必要時)
在這3組中,導入的線按字母順序排序。
# 1 : imports of python lib
import base64
import re
import time
from datetime import datetime
# 2 : imports of odoo
import odoo
from odoo import api, fields, models # alphabetically ordered
from odoo.tools.safe_eval import safe_eval as eval
from odoo.tools.translate import _
# 3 : imports from odoo modules
from odoo.addons.website.models.website import slug
from odoo.addons.web.controllers.main import login_redirect
慣用的Python程式設計習慣
- 每一個python檔案應該把
作為第一行# -*- coding: utf-8 -*-
- 總是喜歡可讀性,相比與簡介或者使用語言特性或習語
- 不要使用
.clone()
# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)
- Python字典:建立與更新
# -- creation empty dict
my_dict = {}
my_dict2 = dict()
# -- creation with values
# bad
my_dict = {}
my_dict['foo'] = 3
my_dict['bar'] = 4
# good
my_dict = {'foo': 3, 'bar': 4}
# -- update dict
# bad
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# good
my_dict.update(foo=3, bar=4, baz=5)
my_dict = dict(my_dict, **my_dict2)
- 使用有意義的變量/類/方法名稱
- 無用變量:臨時變量可以通過給對象命名,使代碼更清晰,但這并不意味着你應該始終建立臨時變量:
# pointless
schema = kw['schema']
params = {'schema': schema}
# simpler
params = {'schema': kw['schema']}
- 當它們更簡單時,多個傳回點是可以
# a bit complex and with a redundant temp variable
def axes(self, axis):
axes = []
if type(axis) == type([]):
axes.extend(axis)
else:
axes.append(axis)
return axes
# clearer
def axes(self, axis):
if type(axis) == type([]):
return list(axis) # clone the axis
else:
return [axis] # single-element list
- 了解你的嵌入部件:你至少應該對所有的Python建構有一個基本的了解 (http://docs.python.org/library/functions.html)
value = my_dict.get('key', None) # very very redundant
value= my_dict.get('key') # good
同樣的,
if 'key' in my_dict
和
if my_dict.get('key')
有非常不同的含義,確定你使用的是正确的
- 學習清單了解:使用清單了解,字典了解,
,map
,filter
, ... 的基本操作,它們使代碼更容易閱讀sum
# not very good
cube = []
for i in res:
cube.append((i['id'],i['name']))
# better
cube = [(i['id'], i['name']) for i in res]
- 集合也是布爾值:在Python中,許多對象在布爾上下文(如IF)中有“布爾值”值。其中有集合 (清單, 字典, 集合, ...) ,當它們為空時是“假”的,當包含項時是“真”的:
bool([]) is False
bool([1]) is True
bool([False]) is True
是以,你可以編寫
if some_collection:
取代
if len(some_collection):
.
- 在疊代器上疊代
# creates a temporary list and looks bar
for key in my_dict.keys():
"do something..."
# better
for key in my_dict:
"do something..."
# creates a temporary list
for key, value in my_dict.items():
"do something..."
# only iterates
for key, value in my_dict.iteritems():
"do something..."
- 使用dict.setdefault
# longer.. harder to read
values = {}
for element in iterable:
if element not in values:
values[element] = []
values[element].append(other_value)
# better.. use dict.setdefault method
values = {}
for element in iterable:
values.setdefault(element, []).append(other_value)
- 作為一個好的開發人員,編寫代碼 (方法上的文檔字元串,代碼中複雜部分的簡單注釋)
- 除了這些指南,你還可以發現以下的有趣連結: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html (有點過時,但相當相關)
Odoo中的程式設計
- 避免建立生成器和裝飾器:隻使用Odoo API提供的那些
- 與Python一樣,使用
,filtered
,mapped
, ... 簡化代碼閱讀和性能的方法sorted
讓你的方法批量工
添加函數時,確定它可以處理多個記錄。典型的,這種方法使用api.multi裝飾器進行裝飾(或者在舊API中寫入id清單)。然後,您必須疊代self來處理每個記錄
@api.multi
def my_method(self)
for record in self:
record.do_cool_stuff()
避免使用
api.one
裝飾器:這可能不會達到您所期望的,并且擴充這樣的方法并不像 api.multi 方法那麼簡單,因為它傳回一個結果清單(由記錄集ID排序)。
對于性能問題,當開發一個“狀态按鈕”(例如)時,不要在api.multi方法中的循環中執行search或search_count。建議使用read_group方法,僅在一個請求中計算所有值
@api.multi
def _compute_equipment_count(self):
""" Count the number of equipement per category """
equipment_data = self.env['hr.equipment'].read_group([('category_id', 'in', self.ids)], ['category_id'], ['category_id'])
mapped_data = dict([(m['category_id'][0], m['category_id_count']) for m in equipment_data])
for category in self:
category.equipment_count = mapped_data.get(category.id, 0)
傳播上下文
在新的API中,上下文是不能修改的
frozendict
。若要調用具有不同上下文的方法, 則應使用
with_context
方法:
records.with_context(new_context).do_stuff() # all the context is replaced
records.with_context(**additionnal_context).do_other_stuff() # additionnal_context values override native context ones
在上下文中傳遞參數會産生危險的副作用。由于這些值是自動傳播的,是以會出現一些行為。調用在上下文中使用default_my_field 鍵的模型
create()
方法将為相關模型設定預設值my_field 。但是,如果固化此建立,其他對象 (例如sale.order.line, 在sale.order建立上) 具有字段名e my_field,它們的預設值也将被設定。
如果需要建立影響某個對象行為的關鍵上下文,則選擇一個好名稱,并最終通過子產品的名稱對其添加字首以隔離其影響。一個很好的例子是
mail
子產品的鍵:mail_create_nosubscribe, mail_notrack, mail_notify_user_signature, ...
不要繞過ORM
當ORM可以做同樣的事情時,你不應該直接使用資料庫光标!如果這樣做,您将繞過所有ORM特征,可能是事務、通路權限等。
很有可能,你也使代碼更難閱讀,可能更不安全
# very very wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in (' + ','.join(map(str, ids))+') AND state=%s AND obj_price > 0', ('draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# no injection, but still wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in %s '\
'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# better
auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)])
請不要SQL注入!
在使用手動SQL查詢時,必須注意不要引入SQL注入漏洞。當使用者輸入被錯誤地過濾或被嚴重引用時,該漏洞出現,允許攻擊者向SQL查詢引入不受歡迎的子句(例如繞過過濾器或執行UPDATE或DELETE指令)。
安全的最好方法是永遠不要使用Python字元串連接配接(+)或字元串參數插值(%)将變量傳遞給SQL查詢字元串。
第二個原因,幾乎同樣重要的是,資料庫抽象層(psycopg2)的任務是決定如何格式化查詢參數,而不是您的任務!例如,psycopg2知道,當你傳遞一個值清單時,它需要将它們格式化為逗号分隔的清單,以括号括起來!
# the following is very bad:
# - it's a SQL injection vulnerability
# - it's unreadable
# - it's not your job to format the list of ids
self.env.cr.execute('SELECT distinct child_id FROM account_account_consol_rel ' +
'WHERE parent_id IN ('+','.join(map(str, ids))+')')
# better
self.env.cr.execute('SELECT DISTINCT child_id '\
'FROM account_account_consol_rel '\
'WHERE parent_id IN %s',
(tuple(ids),))
這是非常重要的,是以在重構時也要小心,最重要的是不要複制這些模式!
這裡有一個值得記憶的例子來幫助你記住這個問題 (但是不要在那裡複制代碼)。在繼續之前,請務必閱讀pyscopg2 的線上文檔,以了解如何正确使用它:
- 查詢參數問題(http://initd.org/psycopg/docs/usage.html#the-problem-with-the-query-parameters)
- 如何用psycopg2 傳遞參(http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries)
- 進階參數類型 (http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types)
在可能的情況下保持你的方法簡短/簡單
功能和方法不應該包含太多的邏輯: 有很多小的和簡單的方法比用很少的大而複雜的方法更可取。一個好的經驗法則是: - 它有不止一種責任 (詳見http://en.wikipedia.org/wiki/Single_responsibility_principle) -它太大,不能裝在一個螢幕上。
同時,相應地命名您的函數: 小的和正确命名的函數是可讀/可維護代碼的起點和更嚴格的文檔。
此建議也與類、檔案、子產品和包有關 (也可參見 http://en.wikipedia.org/wiki/Cyclomatic_complexity)
絕不送出事務
Odoo架構負責為所有RPC調用提供事務上下。其原理是,在每個RPC呼叫開始時打開新的資料庫遊标,并在調用傳回之前,在向RPC用戶端發送應答之前送出,大緻如下:
def execute(self, db_name, uid, obj, method, *args, **kw):
db, pool = pooler.get_db_and_pool(db_name)
# create transaction cursor
cr = db.cursor()
try:
res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
cr.commit() # all good, we commit
except Exception:
cr.rollback() # error, rollback everything atomically
raise
finally:
cr.close() # always close cursor opened manually
return res
如果在執行RPC調用期間發生任何錯誤,則事務以原子方式復原,儲存系統的狀态。
類似地,系統還在測試套件的執行期間提供專用事務,是以可以根據伺服器啟動選項復原或不復原。
結果是,如果您手動調用cr.commit(),任何地方都有很高的機會,您将以不同的方式中斷系統,因為您将導緻部分送出,進而部分和不幹淨的復原,導緻其他問題:
- 不一緻的業務資料,通常是資料丢失
- 工作流不同步,文檔永久粘貼
- 無法快速復原的測試,将開始污染資料庫,并觸發錯誤 (即使在事務期間沒有錯誤發生,這也是true)
這裡有一條非常簡單的規則
除非您已顯式建立了自己的資料庫遊标,否則應該從不調
cr.commit()
! 而你需要做的事情是例外的!
順便說一下,如果您确實建立了自己的遊标,那麼您需要處理錯誤情況和正确的復原,以及在完成該遊标時正确關閉光。
與流行的信條相反,在下面的情況下,您甚至不需要調用
cr.commit()
: - 在一個i額models.Model對象的_auto_init()方法中:在建立自定義模型時,通過addons初始化方法或ORM事務來處理這一點 - 在報表中:該commit()也由架構處理,是以您甚至可以從報表中更新資料庫 - 在models.Transient 方法中:這些方法被準确的稱為 models.Model 中的一個, 在事務中和在結束時使用相應的
cr.commit()/rollback()
等。 (如果您有疑問,請參見上面的一般規則!)
所有的
cr.commit()
從現在起調用伺服器架構之外的内容,必須有一個明确的注釋來解釋為什麼它們是絕對必要的,為什麼它們确實是正确的,以及為什麼它們不中斷事務。 否則,他們可以并将被删除!
正确使用翻譯方法
Odoo使用一個名為"下劃線"
_( )
的像文本一樣的方法來訓示代碼中使用的靜态字元串需要在運作時使用上下文語言進行翻譯。 通過導入如下代碼,可以在代碼中通路此僞方法:
from odoo.tools.translate import _
當使用它時,必須遵循一些非常重要的規則,以避免使用無用的垃圾。
基本上,這種方法隻适用于在代碼中手工編寫的靜态字元串,它不适用于翻譯字段值,例如産品名稱等。必須使用相應字段上的平移标志來完成此操作。
該規則非常簡單:對下劃線方法的調用應該總是以
_('literal string')
的形式出現,而無其他:
# good: plain strings
error = _('This record is locked!')
# good: strings with formatting patterns included
error = _('Record %s cannot be modified!') % record
# ok too: multi-line literal strings
error = _("""This is a bad multiline example
about record %s!""") % record
error = _('Record %s cannot be modified' \
'after being validated!') % record
# bad: tries to translate after string formatting
# (pay attention to brackets!)
# This does NOT work and messes up the translations!
error = _('Record %s cannot be modified!' % record)
# bad: dynamic string, string concatenation, etc are forbidden!
# This does NOT work and messes up the translations!
error = _("'" + que_rec['question'] + "' \n")
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is out of stock!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is out of stock!" % product.name)
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is not available!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is not available!" % product.name)
# Instead you can do the following and everything will be translated,
# including the product name if its field definition has the
# translate flag properly set:
error = _("Product %s is not available!") % product.name
此外,請記住,譯者必須處理傳遞給下劃線函數的文字值,是以請盡量使它們易于了解,并将虛假字元和格式設定為最小。譯者必須注意格式模式,如%s或%d,換行符等。需要儲存,但重要的是用一種明智和明顯的方式:
# Bad: makes the translations hard to work with
error = "'" + question + _("' \nPlease enter an integer value ")
# Better (pay attention to position of the brackets too!)
error = _("Answer to question %s is not valid.\n" \
"Please enter an integer value.") % question
一般來說,在Odoo中,當操作字元串時, 喜歡使用
%
在
.format()
中(在字元串中隻替換一個變量時),且喜歡
%(varname)
而不是位置(當需要替換多個變量時)。這使得社群翻譯者的翻譯變得更容易。
符号與約定
- 模型名稱(使用點标記,子產品名字首):
- 定義Odoo模型時:使用名稱的單數形式 (res.partner 和sale.order 而不是 res.partnerS 和saleS.orderS)
- 當定義一個Odoo 向導(wizard)是 : 使用
,其中related_base_model 是和瞬态關聯的基本模型(定義在models/中), 而操作是瞬态完成的事情的短暫的名字。例如:<related_base_model>.<action>
,account.invoice.make
, ...project.task.delegate.batch
- 當定義報表模型時(SQL視圖) : 使用
,基于瞬态約定<related_base_model>.report.<action>
- Odoo Python類: 在api v8 (面向對象的樣式)中的代碼使用駝峰命名法,在舊的api (SQL 風格)中代碼使用下劃線小寫标記法
class AccountInvoice(models.Model):
...
class account_invoice(osv.osv):
...
- 變量名:
- 為模型變量使用駝峰命名
- 常用變量使用下劃線小寫表示法
- 由于新API與記錄或記錄集一起工作而不是id清單,是以如果不包含id或id清單,則不要用_id或_ids 來作為變量名字尾
ResPartner = self.env['res.partner']
partners = ResPartner.browse(ids)
partner_id = partners[0].id
-
和One2Many
字段應該總是有 _ids作為字尾(例子:sale_order_line_ids)Many2Many
-
字典應該有 _id 做為字尾(例子 : partner_id, user_id, ...)Many2One
- 方法約定
- 計算字段:計算方法模式是 _compute_<field_name>
- 搜尋方法:搜尋方法模式是 _search_<field_name>
- 預設方法:預設方法模式是 _default_<field_name>
- Onchange方法:Onchange方法模式是_onchange_<field_name>
- 限制方法:限制方法模式是 _check_<constraint_name>
- 操作方法:一個對象操作方法是以action_為字首。它的裝飾器是
, 但是因為它隻使用一個記錄,在方法的開始添加@api.multi
self.ensure_one()
- 模型中的屬性順序應該是
- 私有屬性 (
,_name
,_description
, ...)_inherit
- 預設方法和
_default_get
- 字段聲明
- 以與字段聲明相同順序的計算和搜尋方法
- 限制方法 (
) 和onchange方法(@api.constrains
)@api.onchange
- CRUD方法 (ORM重寫的)
- 操作方法
- 最後,其他業務方法。
- 私有屬性 (
class Event(models.Model):
# Private attributes
_name = 'event.event'
_description = 'Event'
# Default methods
def _default_name(self):
...
# Fields declaration
name = fields.Char(string='Name', default=_default_name)
seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
store=True, readonly=True, compute='_compute_seats')
seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
store=True, readonly=True, compute='_compute_seats')
price = fields.Integer(string='Price')
# compute and search fields, in the same order of fields declaration
@api.multi
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
def _compute_seats(self):
...
# Constraints and onchanges
@api.constrains('seats_max', 'seats_available')
def _check_seats_limit(self):
...
@api.onchange('date_begin')
def _onchange_date_begin(self):
...
# CRUD methods (and name_get, name_search, ...) overrides
def create(self, values):
...
# Action methods
@api.multi
def action_validate(self):
self.ensure_one()
...
# Business methods
def mail_user_confirm(self):
...
Javascript 和CSS
對于javascript :
-
建議所有的JavaScript檔案use strict;
- 使用linter (jshint, ...)
- 永遠不要添加簡化的JavaScript庫
- 使用駝峰命名法進行類聲明
- 除非您的代碼應該在每個頁面上運作,否則要使用網站子產品的if_dom_contains函數來指定特定頁面。指定一個元素,該元素對您的代碼需要使用jQuery運作的頁面是特定的
odoo.website.if_dom_contains('.jquery_class_selector', function () {
/*your code here*/
});
對于CSS :
- 使用o_<module_name>作為你所有類的字首,其中module_name是一個子產品的技術名稱('sale', 'im_chat', ...) 或子產品保留的主要路徑(主要是對網站子產品,即 : 'o_forum' 為了website_forum 子產品)。這個規則唯一的例外是web用戶端:它簡單的使用o_ 作為前最
- 避免使用 id
- 使用Bootstrap原生類
- 使用下劃線小寫表示法命名類
Git
送出消息
使用如下作為你送出的字首
- [IMP] 為了改進
- [FIX] 用于錯誤修複
- [REF] 用于重構
- [ADD] 用于增加新資
- [REM] 用于資源的剔除
- [MOV] 用于移動檔案(不要改變移動檔案的内容,否則Git會失去追蹤,并且曆史将會丢失!), 或者簡單地将代碼從檔案移動到另一個文
- [MERGE] 用于合并送出(僅用于前/後端口)
- [CLA] 用于簽署Odoo 個人捐助許可證
然後,在消息本身中,指定由您的更改(子產品名稱, 庫, 轉換對象, ...) 影響的代碼的一部分以及更改的描述。
- 始終包含有意義的送出消息:它應該是自解釋的(足夠長),包括已經被改變的子產品的名稱和變化背後的原因。不要使用"bugfix"或"improvements"這樣的單詞
- 避免同時影響多個子產品的送出。試着将不同的送出分成不同的子產品(如果我們需要分别恢複一個子產品,這将是很有幫助的)
[FIX] website, website_mail: remove unused alert div, fixes look of input-group-btn
Bootstrap's CSS depends on the input-group-btn
element being the first/last child of its parent.
This was not the case because of the invisible
and useless alert.
[IMP] fields: reduce memory footprint of list/set field attributes
[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code.
Instead of using global ...
注
用長的描述來解釋為什麼不是什麼,什麼可以看到的差異
ps:有翻譯不當之處,歡迎留言指正。
原文位址:https://www.odoo.com/documentation/10.0/reference/guidelines.html