コーディング ガイドライン

本ページでは、Odoo コーディングガイドラインを紹介し、Odoo Appsコードの品質向上を目指しています。 実際に適切なコードは読みやすさを改善し、メンテナンスを容易にし、デバッグを助け、複雑さを減らし、信頼性を高めます。 これらのガイドラインは、すべての新しいモジュールおよびすべての新しい開発に適用されるべきです。

警告

安定版 で既存のファイルを変更する場合、オリジナルのファイルスタイルは他のスタイルガイドラインより厳密に優先されます。 つまり、これらのガイドラインを適用するために、既存のファイルを変更しないでください。 コード行のリビジョン履歴を乱すのを避けます。差分は最小限に抑えるべきです。詳細については、pull request guide を参照してください。

警告

master (development)バージョン の既存のファイルを変更する場合は、既存のコードにこれらのガイドラインを適用します。 つまり、既存のファイル構造を大幅に変更する場合にのみ変更します。 その場合、最初に move コミットを実行し、その機能に関連する変更を適用します。

モジュール構造

警告

コミュニティによって開発されたモジュールの場合、モジュールに会社名のようなプレフィックスを付けることを強くお勧めします。

ディレクトリ

モジュールは重要なディレクトリで構成されています。ビジネスロジックが含まれており、 それを確認することで、モジュールの目的を理解できるはずです。

  • data/ : デモやデータのxml

  • models/ : モデルの定義

  • controllers/ : コントローラー(HTTP ルート)が含まれている

  • views/ : ビューやテンプレートが含まれている

  • static/ : css/, js/, img/, lib/, ...

モジュールは、その他の任意のディレクトリによって構成されることもあります。

  • wizard/ : 一時モデル (models.TransientModel) とそのビューをまとめています

  • report/ : SQL ビューに基づく印刷可能なレポートとモデルが含まれる。Python オブジェクトと XML ビューがこのディレクトリに含まれる

  • tests/ : Python テストが含まれる

ファイルの命名

ファイルの命名は、すべてのアドオンから情報をすばやく見つけるために重要です。このセクションでは、標準の odoo モジュールでファイルに名前を付ける方法を説明します。 例として、plant murde applicationを使用します。それには、 plant.narseryplant.order の2つの主要なモデルが含まれています。

models については、同じメインモデルに属するモデルでビジネスロジックを分割します。 各セットは、メインモデルに基づいて指定されたファイルにあります。 モデルが 1 つだけの場合は、モジュール名と同じ名前になります。 それぞれの継承されたモデルは、影響を受けるモデルを理解できるように、個別のファイルにします。

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

security については、3つの主要なファイルを使用する必要があります:

  • 1つ目は、 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 については、バックエンドビューもモデルと同様に分割し、ファイル名の末尾に _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

data については、目的(デモまたはデータ)とメインモデルで分割します。ファイル名は、main_modelに _demo.xml または _data.xml のサフィックスをつなげた名になります。 たとえば、メインモデルとサブタイプのデモとデータを持つアプリケーションの場合、アクティビティとメールテンプレートはすべてメールモジュールに関連しています:

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

controllers については、通常すべてのコントローラーは 1 つのファイルにまとめられており、そのファイル名は <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)

static群 については、JavascriptファイルはPythonモデルと同じロジックに従います。 各コンポーネントは、意味のある名前のファイルに含める必要があります。たとえば、メールモジュールの activity.js にアクティビティウィジェットがあります。 サブディレクトリは 'package' を構造化するために作成することもできます (詳細は web モジュールを参照してください)。 JSウィジェットのテンプレート(静的XMLファイル)とスタイル(scssファイル)に同じロジックを適用する必要があります。 Odoo の外にあるデータ (画像、ライブラリ) をリンクしないでください: URL を画像に使用せず、代わりにコードベースにコピーしてください。

wizards については、命名規則はPythonモデルと同様で、 <transient>.py<transient>_views.xml のようにします。 両方ともウィザードディレクトリに配置されます。この名称は、一時モデルのウィザードキーワードを使用している古いodooアプリケーションに由来しています。

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

統計的な reports については、Python や SQL ビュー、また従来のビューで構成されますが、そのファイルの命名は次の通りです:

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

印刷可能な reports については、主にデータの準備処理や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> を使用)を推奨します:

  • model 属性の前に id 属性を配置します

  • フィールドを宣言する際は、まず 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">
        <list>
            <field name="my_field_1"/>
            <field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" />
        </list>
    </field>
</record>

Odooは糖衣構文として機能するカスタムタグをサポートしています:

  • menuitem: ir.ui.menu を宣言するためのショートカットとして使用する

  • template: ビューの arch セクションのみが必要な QWeb ビューを宣言するために使用します。

これらのタグは record 表記よりも優先されます。

XML IDとその命名規則

セキュリティ、表示、アクション

次のパターンを使用します。

  • メニューの場合: <model_name>_menu 。サブメニューの場合は、 <model_name>_menu_do_stuff

  • ビューの場合: <model_name>_view_<view_type>。 ここでの view_typekanban, form, list, 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 には、対象となるグループの短い名称を指定します( 'model_name_group_user' には、 'user' 、公開ユーザーには、 'public' 、マルチカンパニー用ルールには、 'company' のように)。

Nameは、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} という接尾辞を含めてください。これにより、name を見ただけで何をオーバーライドしているのかが明確になり、理解しやすくなります。

<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

警告

:ref:`Security Pitfalls <reference/security/pitfalls>`セクションを読むだけでなく、安全なコードを書くことも忘れないでください。

PEP8 オプション

Linter を使用すると、構文やセマンティックな警告やエラーを表示するのに役立ちます。 Odoo ソースコードは Python 標準を尊重しようとしますが、その中には無視できるものもあります。

  • E501: 行が長すぎます

  • E301: 1 行が空白で、0 が見つかりました

  • E302: 予期された空白行、見つかった行 1

インポート

インポートは次のように順序付けられています

  1. 外部ライブラリ (python stdlibで1行ずつソートされ分割されます)

  2. odoo のインポート

  3. 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 Command, _, api, fields, models # ASCIIbetically 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']}
  • 複数の戻り値は、単純な場合はOKです

# 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') の意味が非常に異なる場合は、正しいものを使用していることを確認してください。

  • リスト内包を学ぶ: リスト内包、辞書内包、基本的な操作を mapfiltersum などを使用すると、コードが読みやすくなります。

# 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など)で評価されると、多くのオブジェクトが「ブール型ish」の値を持ちます。 これらの中には、空の場合は「falsy」、項目を含む場合は「truthy」となるコレクション (リスト、dicts、set、...) があります。

bool([]) is False
bool([1]) is True
bool([False]) is True

ですから、if len(some_collection): の代わりに if 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)
  • 良い開発者として、あなたのコードを文書化します (メソッドのdocstring、コードの一部をトリッキーにするためのシンプルなコメント)

  • これらのガイドラインに加えて、次のリンクも興味深い場合があります: https://david.goodger.org/projects/pycon/pycon/2007/idiomatic/handout.html (少し古いが、かなり関連性があります)

Odoo でのプログラミング

  • ジェネレータやデコレータの作成は避けてください: Odoo APIが提供するもののみを使用してください。

  • pythonのように、filtered, maped, 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 のデフォルト値が設定されます。 しかし、この作成中に、他のオブジェクト (例えば、sale.order.line など) が販売されている場合。 フィールド名 my_field が生成された場合、デフォルト値も設定されます。

オブジェクトの動作に影響を与える重要なコンテキストを作成する必要がある場合。 良い名前を選び、最終的にはその影響を分離するためにモジュールの名前でプレフィックスを付けます。 mail モジュールのキーは良い例です: mail_create_nosubscribe, mail_notrack, mail_notify_user_signature ...

拡張可能なものと考える

関数とメソッドは、あまりにも多くのロジックを含めるべきではありません。小さくてシンプルなメソッドをたくさん持つことは、大規模で複雑なメソッドを持つよりもお勧めします。 親指のルールは、メソッドに複数の責任があるとすぐに分割することです(http://en.wikipedia.org/wiki/Single_responsity_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. きれいに巻き戻すことができず、データベースを汚染し始めます そしてエラーを引き起こします(トランザクション中にエラーが発生しなくても、これは真です)

ここに非常に単純なルールがあります。

You should NEVER call cr.commit() yourself, UNLESS you have created your own database cursor explicitly! And the situations where you need to do that are exceptional!

そして、あなたが独自のカーソルを作成した場合は、エラーケースと適切なロールバックを処理する必要があります。 カーソルを閉じることができます

一般的な信念とは異なり、以下のような状況で cr.commit() を呼び出す必要もありません。モデルの ``_auto_init()`` メソッドです。 odel object: addonsの初期化メソッドによって処理されます またはカスタムモデルを作成するときのORMトランザクションによって。レポートでは、 commit() もフレームワークによって処理されます。 ですから、モデル内であってもデータベースを更新することができます。 ransient メソッド: これらのメソッドは通常の models.Model とまったく同じように、トランザクション内で、対応する cr で呼び出されます。 最後に ommit()/rollback() などがあります(疑わしい場合は上の一般的なルールを参照してください)。

すべての cr.commit() は今後サーバーフレームワークの外で呼び出されますが、なぜそれらが絶対に必要なのかを説明する 明示的なコメント が必要です。 なぜ彼らが正しいのか?なぜ彼らは取引を破らないのか? そうでなければ、彼らはすることができ、削除されます!

翻訳方法を正しく使用

Odoo は "underscore" _() という名前の GetText のようなメソッドを使用して、コードで使用される静的な文字列を実行時に変換する必要があることを示します。 このメソッドは、環境の言語を使用して self.env._ で使用できます。

使用する際には、いくつかの非常に重要なルールに従う必要があります。 役に立たないジャンクで翻訳を埋めるのを避けるために

基本的に、このメソッドはコード内で手動で書かれた静的な文字列にのみ使用されます。 商品名などの項目値を翻訳することはできません。 これは対応するフィールドの translate フラグを使用する代わりに行う必要があります。

メソッドはオプションの位置または名前付きパラメータを受け付けます ルールは非常に簡単です。アンダースコアメソッドへの呼び出しは常に self' 形式である必要があります。 nv._('literal string') とそれ以外のものはありません:

_ = self.env._

# 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では、文字列を操作する際に、(1つの変数だけを置換する場合) .format() よりも % を使うことを推奨しています。また、(複数の変数を置換する必要がある場合)位置指定よりも %(varname) を使用することを推奨します。こうすることで、コミュニティ翻訳者によって翻訳作業が容易になります。

記号と規則

  • モデル名 (ドット表記、プレフィックスをモジュール名で使用) :
    • Odoo Model を定義する場合: res.partnerSsaleS.orderS の代わりに res.partnersale.order という名前の単一形式を使用してください

    • Odooの一時モデル(ウィザード)を定義する際は、 <related_base_model>.<action> という形式を使用してください。ここでの related_base_model はその一時モデルに関連する基本モデル( models/ ディレクトリで定義)を指し、 action はその一時モデルが実行する処理の短い名前です。 wizard という語の使用は避けてください。例:account.invoice.make、project.task.delegate.batch など。

    • When defining report model (SQL views e.i.) : use <related_base_model>.report.<action>, based on the Transient convention.

  • Odoo Python クラス : キャメルケース(オブジェクト指向スタイル)を使用してください。

class AccountInvoice(models.Model):
    ...
  • 変数名 :
    • モデル変数にキャメルケースを使用

    • 共通変数の小文字表記を使用する

    • レコード ID または id のリストが含まれている場合は、変数名を _id または _ids でサフィックスします。 res.partnerのレコードを含むために``partner_id`` を使用しないでください

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 : compute method pattern は _compute_<field_name>

    • 検索方法 : 検索方法パターンは _search_<field_name>

    • Default method : the default method pattern is _default_<field_name>

    • 選択方法: 選択方法パターンは _selection_<field_name>

    • Onchange メソッド: onchange メソッドのパターンは _onchange_<field_name>

    • 制約メソッド: 制約メソッドパターンは _check_<constraint_name>

    • アクションメソッド : オブジェクトアクションメソッドは action_ でプレフィックスされます。1つのレコードだけを使用するため、メソッドの先頭に self.ensure_one() を追加します。

  • モデルの属性の順序は、
    1. プライベート属性 (_name, _description, _継承, _sql_constraints, ...)

    2. デフォルトのメソッドと default_get

    3. フィールド宣言

    4. フィールド宣言と同じ順序で計算、逆および検索メソッド

    5. 選択メソッド (選択フィールドの計算値を返すために使用されるメソッド)

    6. メソッド(@api.constraints)と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_search, _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 にある場合は、静的に your-odoo-server.com/web/static/src/js/some_file.js URL で利用できます。

規則は、次の構造に従ってコードを整理することです。

  • static: 全ての静的ファイル

    • static/lib: js libs をサブフォルダに配置する場所です。 例えば、jquery ライブラリ内のすべてのファイルは addons/web/static/lib/jquery にあります

    • static/src: 一般的な静的ソースコードフォルダ

      • static/src/css: すべての CSS ファイル

      • static/fonts

      • static/img

      • static/src/js

        • static/src/js/tours: エンドユーザーツアーファイル(テストではなくチュートリアル)

      • static/src/scss: scss ファイル

      • static/src/xml: JSでレンダリングされるすべてのqwebテンプレート

    • static/tests: ここではテスト関連のファイルをすべて配置します。

      • static/tests/tours: ここではツアーのテストファイル(チュートリアルではありません)を作成します。

Javascriptコーディングガイドライン

  • use strict; is recommended for all javascript files

  • linter (jshint, ...) を使用する

  • Minified Javascript ライブラリを追加しない

  • クラス宣言にキャメルケースを使用

より正確な JS のガイドラインは github wiki に詳述されています。 Javascript リファレンスを参照することで、Javascript の既存の API を確認することもできます。

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 (4) スペースインデント、タブなし;

  • 最大80文字の列;

  • 括弧({)を開く: 最後のセレクタの後に空白を挿入する;

  • closing brace (}): 独自の新しい行に;

  • 宣言ごとに1行ずつ、

  • 有意義なホワイトスペースの使い方です

"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",
    }
},

プロパティの順序

「外側」から「位置」から始まり、装飾的なルール(font`filter`など)で終わる「外側」のプロパティを並べ替えます。

Scoped SCSS 変数CSS 変数 を最上部に配置する必要があります。 次に空の行を別の宣言と区切っていきます

.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>`はモジュールの技術的な名前です(saleim_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>

Do

<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 変数 (スコープ付き)

これらの変数はブロック内で宣言されており、外部からはアクセスできません。標準の規約は $-[variable name] です。

Example

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

   margin-right: $-inner-gap;

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

SCSS ミックスと関数

標準の規則は o-[name] です。記述的な名前を使用してください。関数の命名には命令形式で動詞を使用してください (例: get, make, apply...)。

scoped variables form, so $-[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 Variables

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`変数は**imperative**でコンパイルされているのに対し、`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 疑似クラス

:root 疑似クラスで CSS 変数を定義することは、通常、Odoo の UI で 使わない テクニックです。 この練習は、一般的に CSS 変数にアクセスしたり変更したりするために使用されます。代わりに SCSS を使用して行います。

このルールの例外はかなり明白である必要があります 例えばバンドル間で共有されているテンプレートのように、適切にレンダリングされるためには、一定レベルのコンテキスト認識が必要です。