編碼規范?

本頁面介紹了Odoo編碼指南。這些指南旨在提高Odoo應用程序代碼的質量。確實,適當的代碼可以提高可讀性,簡化維護,幫助調試,降低復雜性并促進可靠性。這些指南應該應用于每個新模塊和所有新開發。

警告

**穩定版本 ** 中修改現有文件時,原始文件的樣式嚴格優先于任何其他樣式指南。換句話說,請勿修改現有文件以應用這些指南。這樣可以避免破壞代碼行的修訂歷史記錄。應盡量保持差異最小。有關更多詳細信息,請參閱我們的 拉取請求指南。

警告

在修改 主(開發)版本 中的現有文件時,僅針對修改的代碼或如果大部分文件正在修訂,則僅將這些準則應用于現有代碼。換句話說,僅在現有文件結構正在經歷重大變化時修改。在這種情況下,首先進行 移動 提交,然后應用與功能相關的更改。

模塊結構?

目錄?

一個模塊被組織在重要的目錄中。這些目錄包含業務邏輯;查看它們應該可以讓您了解模塊的目的。

  • data/ :演示和數據 XML

  • models/ :模型定義

  • controllers/ : 包含控制器(HTTP路由)

  • views/ :包含視圖和模板

  • static/ : 包含網頁資源,分為 css/、js/、img/、lib/ 等文件夾…

其他可選目錄組成該模塊。

  • wizard/ : 將瞬態模型( models.TransientModel )及其視圖分組

  • report/ :包含基于 SQL 視圖的可打印報表和模型。該目錄包括 Python 對象和 XML 視圖。

  • tests/ :包含Python測試

文件命名?

文件命名對于快速查找所有odoo插件中的信息非常重要。本節將解釋如何在標準odoo模塊中命名文件。我們以 `植物苗圃<https://github.com/tivisse/odoodays-2018/tree/master/plant_nursery>`_應用程序為例。它包含兩個主要模型 plant.nursery * 和 * plant.order 。

關于 模型 ,將業務邏輯按照屬于同一主模型的模型集進行拆分。每個集合位于一個以其主模型命名的文件中。如果只有一個模型,則其名稱與模塊名稱相同。每個繼承的模型應該在自己的文件中,以幫助理解受影響的模型。

addons/plant_nursery/
|-- models/
|   |-- plant_nursery.py (first main model)
|   |-- plant_order.py (another main model)
|   |-- res_partner.py (inherited Odoo model)

關于 安全性 ,應使用三個主要文件:

  • 首先是在 ir.model.access.csv 文件中定義訪問權限。

  • 用戶組定義在 <module>_groups.xml 中。

  • 記錄規則定義在 <model>_security.xml 中。

addons/plant_nursery/
|-- security/
|   |-- ir.model.access.csv
|   |-- plant_nursery_groups.xml
|   |-- plant_nursery_security.xml
|   |-- plant_order_security.xml

關于 視圖 ,后端視圖應該像模型一樣拆分,并以 _views.xml 為后綴。后端視圖包括列表、表單、看板、活動、圖形、透視等視圖。為了方便在視圖中按模型拆分,與特定操作無關的主菜單可以提取到可選的 <module>_menus.xml 文件中。模板(用于門戶網站顯示的QWeb頁面)放在名為 <model>_templates.xml 的單獨文件中。

addons/plant_nursery/
|-- views/
|   | -- plant_nursery_menus.xml (optional definition of main menus)
|   | -- plant_nursery_views.xml (backend views)
|   | -- plant_nursery_templates.xml (portal templates)
|   | -- plant_order_views.xml
|   | -- plant_order_templates.xml
|   | -- res_partner_views.xml

關于 數據 ,按目的(演示或數據)和主要模型進行拆分。文件名將是主要模型名稱后綴為 _demo.xml_data.xml 。例如,對于一個應用程序,它有演示和數據的主要模型以及與郵件模塊相關的子類型、活動和郵件模板:

addons/plant_nursery/
|-- data/
|   |-- plant_nursery_data.xml
|   |-- plant_nursery_demo.xml
|   |-- mail_data.xml

關于 控制器 ,通常所有控制器都屬于一個單獨的控制器,包含在名為 <module_name>.py 的文件中。Odoo中的一個舊慣例是將此文件命名為 main.py ,但已被認為過時。如果您需要從另一個模塊繼承現有控制器,請在 <inherited_module_name>.py 中執行。例如,在應用程序中添加門戶控制器是在 portal.py 中完成的。

addons/plant_nursery/
|-- controllers/
|   |-- plant_nursery.py
|   |-- portal.py (inheriting portal/controllers/portal.py)
|   |-- main.py (deprecated, replaced by plant_nursery.py)

關于 靜態文件 ,Javascript文件遵循與Python模型相同的全局邏輯。每個組件都應該有一個有意義的文件名,并位于自己的文件中。例如,活動小部件位于郵件模塊的 activity.js 中。子目錄也可以創建來結構化’包’(有關詳細信息,請參見Web模塊)。對于JS小部件的模板(靜態XML文件)和它們的樣式(scss文件),應該應用相同的邏輯。不要鏈接Odoo之外的數據(圖像、庫),而是將其復制到代碼庫中,不要使用圖像的URL。

關于 向導 ,命名約定與Python模型相同: <transient>.py<transient>_views.xml 。兩者都放在向導目錄中。這個命名來自于舊版Odoo應用程序使用瞬態模型的向導關鍵字。

addons/plant_nursery/
|-- wizard/
|   |-- make_plant_order.py
|   |-- make_plant_order_views.xml

關于使用 Python/SQL 視圖和經典視圖生成的 統計報告 ,命名規則如下:

addons/plant_nursery/
|-- report/
|   |-- plant_order_report.py
|   |-- plant_order_report_views.xml

關于主要包含數據準備和Qweb模板的 可打印報告 ,命名規則如下:

addons/plant_nursery/
|-- report/
|   |-- plant_order_reports.xml (report actions, paperformat, ...)
|   |-- plant_order_templates.xml (xml report templates)

因此,我們的Odoo模塊的完整樹形結構如下所示

addons/plant_nursery/
|-- __init__.py
|-- __manifest__.py
|-- controllers/
|   |-- __init__.py
|   |-- plant_nursery.py
|   |-- portal.py
|-- data/
|   |-- plant_nursery_data.xml
|   |-- plant_nursery_demo.xml
|   |-- mail_data.xml
|-- models/
|   |-- __init__.py
|   |-- plant_nursery.py
|   |-- plant_order.py
|   |-- res_partner.py
|-- report/
|   |-- __init__.py
|   |-- plant_order_report.py
|   |-- plant_order_report_views.xml
|   |-- plant_order_reports.xml (report actions, paperformat, ...)
|   |-- plant_order_templates.xml (xml report templates)
|-- security/
|   |-- ir.model.access.csv
|   |-- plant_nursery_groups.xml
|   |-- plant_nursery_security.xml
|   |-- plant_order_security.xml
|-- static/
|   |-- img/
|   |   |-- my_little_kitten.png
|   |   |-- troll.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/
|   |-- plant_nursery_menus.xml
|   |-- plant_nursery_views.xml
|   |-- plant_nursery_templates.xml
|   |-- plant_order_views.xml
|   |-- plant_order_templates.xml
|   |-- res_partner_views.xml
|-- wizard/
|   |--make_plant_order.py
|   |--make_plant_order_views.xml

注解

文件名只能包含 [a-z0-9_] (小寫字母數字和 _

警告

使用正確的文件權限:文件夾 755 和文件 644。

XML文件?

格式?

在XML中聲明記錄,建議使用 record 符號(使用 <record> ):

  • id 屬性放在 model 之前

  • 在字段聲明中,首先是 name 屬性。然后將 放置在 field 標簽中,或者在 eval 屬性中,最后是其他屬性(widget,options等),按重要性排序。

  • 嘗試按模型對記錄進行分組。如果操作/菜單/視圖之間存在依賴關系,則可能不適用此約定。

  • 使用下一點中定義的命名約定

  • 標簽 <data> 僅用于使用 noupdate=1 設置不可更新的數據。如果文件中只有不可更新的數據,則可以在 <odoo> 標簽上設置 noupdate=1 ,而不設置 <data> 標簽。

<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 的快捷方式

  • 模板: 用于聲明一個僅需要視圖的 arch 部分的 QWeb 視圖。

這些標簽優先于 記錄 符號。

XML ID和命名?

安全、視圖和操作?

請使用以下模式:

  • 對于菜單: <model_name>_menu ,或者 <model_name>_menu_do_stuff 用于子菜單。

  • 對于視圖: <model_name>_view_<view_type> ,其中 view_typekanban 、 form 、 tree 、 search 等。

  • 對于一個操作:主要操作應該遵循 <model_name>_action 的規則。其他操作應該以 _<detail> 為后綴,其中 detail 是一個簡短的小寫字符串,用于解釋該操作。這僅在為模型聲明多個操作時使用。

  • 對于窗口操作:在操作名稱后綴加上特定的視圖信息,例如: <model_name>_action_view_<view_type> 。

  • 對于一個組: <module_name>_group_<group_name> ,其中 group_name 是組的名稱,通常為’user’、’manager’等。

  • 對于規則: <model_name>_rule_<concerned_group> ,其中 concerned_group 是相關組的簡稱(’user’ 表示 ‘model_name_group_user’,’public’ 表示公共用戶,’company’ 表示多公司規則等)。

名稱應與 XML ID 相同,下劃線替換為點。操作應具有真實名稱,因為它用作顯示名稱。

<!-- views  -->
<record id="model_name_view_form" model="ir.ui.view">
    <field name="name">model.name.view.form</field>
    ...
</record>

<record id="model_name_view_kanban" model="ir.ui.view">
    <field name="name">model.name.view.kanban</field>
    ...
</record>

<!-- actions -->
<record id="model_name_action" model="ir.act.window">
    <field name="name">Model Main Action</field>
    ...
</record>

<record id="model_name_action_child_list" model="ir.actions.act_window">
    <field name="name">Model Access Children</field>
</record>

<!-- menus and sub-menus -->
<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"
/>

<!-- 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>

繼承 XML?

繼承視圖的 Xml Id 應該使用與原始記錄相同的 ID。這有助于一目了然地找到所有繼承。由于最終的 Xml Id 是由創建它們的模塊前綴化的,因此不會重疊。

命名應包含 .inherit.{details} 后綴,以便在查看名稱時更容易理解其覆蓋目的。

<record id="model_view_form" model="ir.ui.view">
    <field name="name">model.view.form.inherit.module2</field>
    <field name="inherit_id" ref="module1.model_view_form"/>
    ...
</record>

新的主視圖不需要使用inherit后綴,因為它們是基于第一個記錄的新記錄。

<record id="module2.model_view_form" model="ir.ui.view">
    <field name="name">model.view.form.module2</field>
    <field name="inherit_id" ref="module1.model_view_form"/>
    <field name="mode">primary</field>
    ...
</record>

Python?

警告

不要忘記閱讀 安全陷阱 部分,以編寫安全的代碼。

PEP8選項?

使用代碼檢查工具可以幫助顯示語法和語義警告或錯誤。Odoo源代碼盡力遵守Python標準,但有些可以忽略。

  • E501:行太長

  • E301: 預期有1個空行,但找到了0個

  • E302:期望有2個空行,但只找到了1個

導入?

導入項的順序為

  1. 外部庫(每行一個,按照字母順序排序并分割在Python標準庫中)

  2. odoo 的導入

  3. 從Odoo模塊導入(很少,只有在必要時才導入)

在這三個組內,導入的行按字母順序排序。

# 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
# 3 : imports from odoo addons
from odoo.addons.web.controllers.main import login_redirect
from odoo.addons.website.models.website import slug

Python編程慣用法?

  • 始終將 可讀性 放在 簡潔性 或使用語言特性或習慣用法之上。

  • 不要使用 .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
value = my_dict.get('key', None) # very very redundant
value = my_dict.get('key') # good

同時, if 'key' in my_dictif 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]
  • Collections are booleans too : 在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..."
# accessing the key,value pair
for key, value in my_dict.items():
        "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 等方法來簡化代碼閱讀和提高性能。

傳遞上下文?

上下文是一個不可修改的 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 的默認值。但是,如果在此創建期間創建了其他對象(例如,在銷售訂單創建時創建了銷售訂單行),它們的字段名稱為 my_field 的默認值也將被設置。

如果您需要創建一個關鍵上下文來影響某個對象的行為,請選擇一個好的名稱,并在必要時使用模塊名稱作為前綴來隔離其影響。 mail 模塊的鍵是一個很好的例子: mail_create_nosubscribe * , * mail_notrack * , * mail_notify_user_signature ,…

考慮可擴展性?

函數和方法不應該包含太多邏輯:擁有許多小而簡單的方法比擁有少數大而復雜的方法更可取。一個好的經驗法則是,一旦一個方法具有多個職責,就將其拆分(參見“單一職責原則”:http://en.wikipedia.org/wiki/Single_responsibility_principle)。

應避免在方法中硬編碼業務邏輯,因為這會防止子模塊的輕松擴展。

# do not do this
# modifying the domain or criteria implies overriding whole method
def action(self):
    ...  # long method
    partners = self.env['res.partner'].search(complex_domain)
    emails = partners.filtered(lambda r: arbitrary_criteria).mapped('email')

# better but do not do this either
# modifying the logic forces to duplicate some parts of the code
def action(self):
    ...
    partners = self._get_partners()
    emails = partners._get_emails()

# better
# minimum override
def action(self):
    ...
    partners = self.env['res.partner'].search(self._get_partner_domain())
    emails = partners.filtered(lambda r: r._filter_partners()).mapped('email')

上述代碼為了示例而過于可擴展,但必須考慮可讀性并做出權衡。

同時,按照函數的名稱進行命名:小而適當命名的函數是可讀性/可維護性代碼和更緊密文檔的起點。

這個建議也適用于類、文件、模塊和包。(另請參閱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() ,很有可能會以各種方式破壞系統,因為您將導致部分提交,從而導致部分和不干凈的回滾,其中包括:

  1. 業務數據不一致,通常會導致數據丟失

  2. 工作流不同步,文檔永久卡住

  3. 無法干凈地回滾的測試,將開始污染數據庫并觸發錯誤(即使在事務期間沒有發生錯誤,這也是正確的)

這是一個非常簡單的規則:

除非您已經顯式地創建了自己的數據庫游標,否則 絕不 應該自己調用 cr.commit() !而您需要這樣做的情況是極其特殊的!

順便提一下,如果您創建了自己的游標,則需要處理錯誤情況和適當的回滾,并在完成后正確關閉游標。

與普遍觀點相反,在以下情況下,您甚至不需要調用 cr.commit() :- 在 models.Model 對象的 _auto_init() 方法中:這由插件初始化方法或創建自定義模型時的 ORM 事務處理,- 在報告中: commit() 也由框架處理,因此您甚至可以從報告中更新數據庫- 在 models.Transient 方法中:這些方法與常規的 models.Model 方法完全相同,在事務中調用,并在結尾處使用相應的 cr.commit()/rollback() - 等等(如果您有疑問,請參見上面的一般規則?。?/p>

現在起,所有位于服務器框架之外的 cr.commit() 調用必須有一個 明確的注釋 ,解釋為什么它們是絕對必要的,為什么它們確實是正確的,以及為什么它們不會破壞事務。否則,它們可能會被刪除!

正確使用翻譯方法?

Odoo使用類似于GetText的方法,稱為”underscore” _( ) ,以指示代碼中使用的靜態字符串需要在運行時使用上下文的語言進行翻譯。通過導入以下方式,在您的代碼中訪問此偽方法:

from odoo 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: formatting outside of translation
# This won't benefit from fallback mechanism in case of bad translation
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)

# 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 ")

# Ok (pay attention to position of the brackets too!)
error = _("Answer to question %s is not valid.\n" \
          "Please enter an integer value.", question)

# Better
error = _("Answer to question %(title)s is not valid.\n" \
          "Please enter an integer value.", title=question)

一般來說,在Odoo中操作字符串時,優先使用 % 而不是 .format() (僅在字符串中有一個變量需要替換時),并且優先使用 %(varname) 而不是位置(當需要替換多個變量時)。這樣可以使社區翻譯人員更容易進行翻譯。

符號和約定?

  • 模型名稱(使用點表示法,前綴為模塊名稱):
    • 在定義Odoo模型時:使用名字的單數形式( res.partnersale.order 而不是 res.partnerSsaleS.orderS

    • 定義Odoo Transient(向導)時:使用 <related_base_model>.<action> ,其中 related_base_model 是與Transient相關的基本模型(在 models/ * 中定義), * action 是Transient執行的操作的簡短名稱。避免使用 wizard 一詞。例如: account.invoice.make , project.task.delegate.batch ,…

    • 在定義 報表 模型(如SQL視圖)時,請使用基于瞬態約定的 <related_base_model>.report.<action> 。

  • Odoo Python類:使用駝峰式命名法(面向對象風格)。

class AccountInvoice(models.Model):
    ...
  • 變量名:
    • 模型變量使用駝峰命名法

    • 使用下劃線小寫表示法表示常規變量。

    • 如果變量包含記錄 ID 或 ID 列表,請在變量名后綴加上 _id_ids 。不要使用 partner_id 來包含 res.partner 的記錄。

Partner = self.env['res.partner']
partners = Partner.browse(ids)
partner_id = partners[0].id
  • One2ManyMany2Many 字段應該總是以 _ids 作為后綴(例如: sale_order_line_ids)

  • Many2One 字段應該以 _id 作為后綴(例如: partner_id,user_id,…)

  • 方法約定
    • 計算字段:計算方法的模式為 _compute_<field_name>

    • 搜索方法:搜索方法模式為 _search_<field_name>

    • 默認方法:默認方法模式為 _default_<field_name>

    • 選擇方法:選擇方法模式為 _selection_<field_name>

    • 觸發方法:觸發方法的模式為 _onchange_<field_name>

    • 約束方法:約束方法模式為 _check_<constraint_name>

    • Action method: 對象的操作方法以 action_ 為前綴。由于它只使用一個記錄,因此在方法開頭添加 self.ensure_one() 。

  • 在模型屬性中,順序應該是
    1. 私有屬性( _name , _description , _inherit , _sql_constraints ,…)

    2. 默認方法和 default_get

    3. 字段聲明

    4. 計算、反向和搜索方法的順序應與字段聲明相同

    5. 選擇方法(用于返回選擇字段計算值的方法)

    6. 約束方法( @api.constrains )和 onchange 方法( @api.onchange

    7. CRUD 方法(ORM 覆蓋)

    8. 操作方法

    9. 最后,其他業務方法。

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(string='Reserved Seats', store=True
        readonly=True, compute='_compute_seats')
    seats_available = fields.Integer(string='Available Seats', store=True
        readonly=True, compute='_compute_seats')
    price = fields.Integer(string='Price')
    event_type = fields.Selection(string="Type", selection='_selection_type')

    # compute and search fields, in the same order of fields declaration
    @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
    def _compute_seats(self):
        ...

    @api.model
    def _selection_type(self):
        return []

    # 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
    def action_validate(self):
        self.ensure_one()
        ...

    # Business methods
    def mail_user_confirm(self):
        ...

JavaScript?

靜態文件組織?

Odoo插件有一些關于如何組織各種文件的約定。我們在這里詳細解釋了Web資源應該如何組織。

首先需要知道的是,Odoo服務器將為所有位于 static/ * 文件夾中的文件提供(靜態)服務,但是需要以插件名稱為前綴。例如,如果文件位于 * addons/web/static/src/js/some_file.js ,則可以通過以下URL靜態訪問: your-odoo-server.com/web/static/src/js/some_file.js

慣例是按照以下結構組織代碼:

  • 靜態文件 :所有靜態文件一般指的是

    • static/lib : 這是存放js庫的地方,應該在子文件夾中。例如, jquery 庫的所有文件都在 addons/web/static/lib/jquery 中。

    • static/src :通用的靜態源代碼文件夾

      • static/src/css : 所有的 CSS 文件

      • 靜態/字體

      • 靜態/圖像

      • 靜態/src/js

        • static/src/js/tours : 用戶教程文件(非測試)

      • static/src/scss : SCSS文件

      • static/src/xml : 所有將在 JS 中呈現的 qweb 模板

    • static/tests : 這是我們放置所有測試相關文件的位置。

      • static/tests/tours : 這里是我們放置所有旅游測試文件(不是教程)的地方。

Javascript 編碼指南?

  • 所有 JavaScript 文件都建議使用 use strict;

  • 使用代碼檢查工具 (jshint, …)

  • 永遠不要添加壓縮過的 JavaScript 庫

  • 類聲明使用駝峰命名法

更詳細的JS指南詳見 github wiki。您也可以查看現有的JavaScript API,通過查看JavaScript參考文檔。

CSS 和 SCSS?

語法和格式?

.o_foo, .o_foo_bar, .o_baz {
   height: $o-statusbar-height;

   .o_qux {
      height: $o-statusbar-height * 0.5;
   }
}

.o_corge {
   background: $o-list-footer-bg-color;
}
  • 四個(4)空格縮進,不使用制表符;

  • 每列最多80個字符寬;

  • 左大括號 ( { ):最后一個選擇器后面要有空格;

  • 右括號 ( } ):獨占一行;

  • 每個聲明占一行;

  • 有意義地使用空格。

"stylelint.config": {
    "rules": {
        // https://stylelint.io/user-guide/rules

        // Avoid errors
        "block-no-empty": true,
        "shorthand-property-no-redundant-values": true,
        "declaration-block-no-shorthand-property-overrides": true,

        // Stylistic conventions
        "indentation": 4,

        "function-comma-space-after": "always",
        "function-parentheses-space-inside": "never",
        "function-whitespace-after": "always",

        "unit-case": "lower",

        "value-list-comma-space-after": "always-single-line",

        "declaration-bang-space-after": "never",
        "declaration-bang-space-before": "always",
        "declaration-colon-space-after": "always",
        "declaration-colon-space-before": "never",

        "block-closing-brace-empty-line-before": "never",
        "block-opening-brace-space-before": "always",

        "selector-attribute-brackets-space-inside": "never",
        "selector-list-comma-space-after": "always-single-line",
        "selector-list-comma-space-before": "never-single-line",
    }
},

屬性順序?

從“外部”到“內部”排序屬性,從 position 開始,以裝飾規則( font , filter 等)結束。

Scoped SCSS variablesCSS變量 必須放置在最頂部,并在它們與其他聲明之間留有一個空行。

.o_element {
   $-inner-gap: $border-width + $legend-margin-bottom;

   --element-margin: 1rem;
   --element-size: 3rem;

   @include o-position-absolute(1rem);
   display: block;
   margin: var(--element-margin);
   width: calc(var(--element-size) + #{$-inner-gap});
   border: 0;
   padding: 1rem;
   background: blue;
   font-size: 1rem;
   filter: blur(2px);
}

命名規范?

CSS中的命名約定非常有用,可以使您的代碼更加嚴格、透明和信息豐富。

避免使用 id 選擇器,并使用 o_<module_name> 作為類的前綴,其中 <module_name> 是模塊的技術名稱( sale , im_chat ,…)或模塊保留的主要路由(主要用于網站模塊,即 website_forum 模塊的 o_forum )。
唯一的例外是 webclient:它只是使用 o_ 前綴。

避免創建過于具體的類和變量名稱。在命名嵌套元素時,選擇“Grandchild”方法。

Example

不要

<div class=“o_element_wrapper”>
   <div class=“o_element_wrapper_entries”>
      <span class=“o_element_wrapper_entries_entry”>
         <a class=“o_element_wrapper_entries_entry_link”>Entry</a>
      </span>
   </div>
</div>

<div class=“o_element_wrapper”>
   <div class=“o_element_entries”>
      <span class=“o_element_entry”>
         <a class=“o_element_link”>Entry</a>
      </span>
   </div>
</div>

除了更加緊湊,這種方法還可以簡化維護,因為它限制了在 DOM 發生更改時重命名的需求。

SCSS 變量?

我們的標準約定是 $o-[root]-[element]-[property]-[modifier] ,其中:

  • $o-

    前綴。

  • [root]

    組件名稱 **或者 ** 模塊名稱(以組件名稱為優先)。

  • [element]

    內部元素的可選標識符。

  • [property]

    變量定義的屬性/行為。

  • [modifier]

    一個可選的修飾符。

Example

$o-block-color: value;
$o-block-title-color: value;
$o-block-title-color-hover: value;

SCSS變量(作用域)?

這些變量在塊內聲明,無法從外部訪問。我們的標準約定是 $-[變量名] 。

Example

.o_element {
   $-inner-gap: compute-something;

   margin-right: $-inner-gap;

   .o_element_child {
      margin-right: $-inner-gap * 0.5;
   }
}

SCSS混合和函數?

我們的標準約定是 o-[名稱] 。使用描述性名稱。在命名函數時,使用命令形式的動詞(例如: 獲取 , 制作 , 應用 …)。

scoped variables form 中,可選參數使用 $-[argument] 的形式進行命名。

Example

@mixin o-avatar($-size: 1.5em, $-radius: 100%) {
   width: $-size;
   height: $-size;
   border-radius: $-radius;
}

@function o-invert-color($-color, $-amount: 100%) {
   $-inverse: change-color($-color, $-hue: hue($-color) + 180);

   @return mix($-inverse, $-color, $-amount);
}

CSS 變量?

在Odoo中,CSS變量的使用嚴格限制于DOM相關。使用它們來 上下文地 調整設計和布局。

我們的標準約定是BEM,因此使用 --[root]__[element]-[property]--[modifier] 的格式,其中:

  • [root]

    組件名稱 **或者 ** 模塊名稱(以組件名稱為優先)。

  • [element]

    內部元素的可選標識符。

  • [property]

    變量定義的屬性/行為。

  • [modifier]

    一個可選的修飾符。

Example

.o_kanban_record {
   --KanbanRecord-width: value;
   --KanbanRecord__picture-border: value;
   --KanbanRecord__picture-border--active: value;
}

// Adapt the component when rendered in another context.
.o_form_view {
   --KanbanRecord-width: another-value;
   --KanbanRecord__picture-border: another-value;
   --KanbanRecord__picture-border--active: another-value;
}

使用 CSS 變量?

在Odoo中,CSS變量的使用嚴格限于DOM相關,意味著它們被用于 上下文 適應設計和布局,而不是用于管理全局設計系統。這些通常在組件的屬性在特定上下文或其他情況下可能會變化時使用。

我們在組件的主塊中定義這些屬性,并提供默認回退。

Example

my_component.scss?
.o_MyComponent {
   color: var(--MyComponent-color, #313131);
}
my_dashboard.scss?
.o_MyDashboard {
   // Adapt the component in this context only
   --MyComponent-color: #017e84;
}

CSS 和 SCSS 變量?

盡管看起來相似, CSSSCSS 變量的行為非常不同。主要區別在于, SCSS 變量是 命令式 的并且會被編譯掉,而 CSS 變量是 聲明式 的并且包含在最終輸出中。

在Odoo中,我們采用了最佳實踐:使用 SCSS 變量來定義設計系統,同時在上下文適應時選擇 CSS 變量。

通過添加 SCSS 變量來改進前面示例的實現,以便在頂層獲得控制并確保與其他組件的一致性。

Example

secondary_variables.scss?
$o-component-color: $o-main-text-color;
$o-dashboard-color: $o-info;
// [...]
component.scss?
.o_component {
   color: var(--MyComponent-color, #{$o-component-color});
}
dashboard.scss?
.o_dashboard {
   --MyComponent-color: #{$o-dashboard-color};
}

:root 偽類?

在Odoo的用戶界面中,我們通常 不使用:root 偽類上定義CSS變量的技術。這種做法通常用于全局訪問和修改CSS變量。我們使用SCSS來執行此操作。

這個規則的例外應該是相當明顯的,比如跨 bundle 共享的模板需要一定程度的上下文意識才能正確渲染。