odoo17 | 编码规范大全

发布时间:2024年01月16日

编码规范

本页介绍Odoo编码指南。这些旨在改善 Odoo应用程序代码的质量。事实上,适当的代码可以提高可读性,简化 维护,帮助调试,降低复杂性并提高可靠性。 这些准则应适用于每个新模块和所有新开发。

警告

在稳定版本中修改现有文件时,原始文件样式 严格取代任何其他风格准则。换句话说,请不要永远 修改现有文件以应用这些准则。它避免了中断 代码行的修订历史记录。差异应保持最小。查看更多 有关详细信息,请参阅我们的拉取请求指南。

警告

在主(开发)版本中修改现有文件时,应用这些 仅针对已修改代码的现有代码的指南,或者如果文件的大部分内容是 正在修订中。换言之,仅当现有文件结构符合以下条件时,才修改现有文件结构 正在发生重大变化。在这种情况下,首先执行移动提交,然后应用 与功能相关的更改。

模块结构

目录

模块被组织在重要的目录中。它们包含业务逻辑; 看一看它们应该会让你理解这个模块的目的。

  • data/ : 演示和数据 XML

  • models/ : 模型定义

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

  • views/ :包含视图和模板

  • static/ :包含 Web 资产,分为 css/、js/、img/、lib/、…

其他可选目录组成该模块。

  • Wizard/ :重新组合瞬态模型 () 及其视图models.TransientModel

  • report/ :包含基于SQL视图的可打印报表和模型。Python 对象和 XML 视图包含在此目录中

  • tests/ :包含 Python 测试用例

文件命名

文件命名对于通过所有odoo插件快速查找信息非常重要。 本节介绍如何在标准odoo模块中命名文件。作为 例如,我们使用植物苗圃应用程序。 它包含两个主要模型 plant.nurseryplant.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文件中完成的访问权限定义。

  • 用户组定义在model(命令的模型)_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_menu .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。两者都放在向导(wizard)目录中。这种命名来自旧版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中声明记录,建议使用记录符号(使用):

  • 将id属性放在模型之前

  • 对于字段声明,name属性是首选。然后,将值放在字段标签中,或者放在eval属性中,最后按重要性排序其他属性(widget,options等)。

  • 尽量按模型分组记录。在action/menu/views之间存在依赖关系的情况下,此约定可能不适用。

  • 使用下一点定义的命名约定

  • <data>标签仅用于设置不可更新的数据,带有noupdate=1。如果文件中只有不可更新的数据,可以将noupdate=1设置为<odoo>标记,并且不设置<data>标记。
    若要在 XML 中声明记录,建议使用记录表示法(使用 <record>):

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

  • template:使用它来声明只需要视图的arch部分的QWeb视图。

这些标签优于record标签。

XML ID 和命名

Security, View and Action

使用以下模式:

  • 对于菜单(menu):<model_name>_menu,或对于子菜单使用<model_name>_menu_do_stuff。

  • 对于视图(view):<model_name>_view_<view_type>,其中view_typekanban,form,tree,search等。

  • 对于操作(Action):主要操作使用<model_name>_action。其他操作以 _<detail>为后缀,其中detail是一个简短说明操作的字符串。这仅在为模型声明多个操作时使用。

  • 对于窗口操作(window actions):操作名称后缀为特定视图信息,如<model_name>_action_view_<view_type>。

  • 对于组(group):<module_name>_group_<group_name>,其中group_name是组的名称,通常为’user’,'manager’等。

  • 对于规则(rule):<model_name>_rule_<concerned_group>,其中concerned_group是相关组的简短名称(对于’model_name_group_user’使用’user’,对于公共用户使用’public’,对于多公司规则使用’company’等)。

xml 的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} 后缀,以便在查看其名称时更容易理解覆盖目的。

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

新的主视图不需要继承后缀,因为它们是基于第一个主视图的新记录。

<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 选项

使用linter可以帮助显示语法和语义警告或错误。Odoo源代码试图遵守Python标准,但其中一些可以忽略。

  • E501:行太长

  • E301: 预期 1 个空白行,找到 0 个

  • E302:预期 2 个空白行,找到 1 个

Imports

Imports按以下方式排序

  • 外部库(每行一个,按python标准库排序和拆分)

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

编程的习惯用法 (Python)

  • 总是保证可读性,而不是简洁性或使用语言特征或习语。

  • 不要使用 .clone()

# 不好的写法
new_dict = my_dict.clone()
new_list = old_list.clone()
# 正确写法
new_dict = dict(my_dict)
new_list = list(old_list)
  • Python字典:创建和更新
#  -- 创建空字典
my_dict = {}
my_dict2 = dict()

# -- 字典赋值
# 不好的写法
my_dict = {}
my_dict['foo'] = 3
my_dict['bar'] = 4
# 正确写法
my_dict = {'foo': 3, 'bar': 4}

# -- 更新字典
# 不好的写法
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# 正确的写法
my_dict.update(foo=3, bar=4, baz=5)
my_dict = dict(my_dict, **my_dict2)
  • 使用有意义的变量/类/方法名
  • 无用的变量:临时变量可以通过给对象命名来使代码更清晰,但这并不意味着你应该一直创建临时变量:
# 多余的写法
schema = kw['schema']
params = {'schema': schema}
# 简洁的写法
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) # 非常冗余
value = my_dict.get('key') # 正确写法

此外,如果 my_dict 中的“key”和 my_dict.get(‘key’) 具有非常不同的含义,请确保您使用了正确的那个。

  • 学习列表解析:使用列表解析、字典解析以及使用map、filter、sum等基本操作。它们使代码更容易阅读。
# 不是很好的写法
cube = []
for i in res:
    cube.append((i['id'],i['name']))
# 更好的写法
cube = [(i['id'], i['name']) for i in res]
  • 集合也是布尔值:在python中,许多对象在布尔上下文中计算时具有“布尔值”(例如if)。其中包括集合(列表,字典,集合,…),当为空时为“false”,当包含项时为“true”:
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)
  • 作为一名优秀的开发人员,为你的代码编制文档(方法上的docstring,代码棘手部分的简单注释)

Odoo编程

  • 避免创建生成器和装饰器:只使用Odoo API提供的那些。

  • 与python一样,使用过滤、映射、排序等方法来简化代码阅读和提高性能。

传播上下文

上下文是一个不能修改的 frozendict。要使用不同的上下文调用方法,应使用 with_context 方法。

 # 所有上下文都被替换
records.with_context(new_context).do_stuff()
 # 附加的上下文值覆盖本地上下文值
records.with_context(**additionnal_context).do_other_stuff()

警告

在上下文中传递参数可能会产生危险的副作用。

由于值是自动传播的,因此可能会出现一些意想不到的行为。在上下文中调用带有default_my_field键的模型的create()方法将为相关模型设置my_field的默认值。但是,如果在创建过程中,其他对象(例如sale.order.
order)。排队,特价。创建一个字段名为my_field的订单,它们的默认值也将被设置。

如果您需要创建一个影响对象行为的键上下文,请选择一个好名称,并最终通过模块名称前缀来隔离其影响。邮件(mail)模块的键就是一个很好的例子:mail_create_nosubscribe、mail_notrack、mail_notify_user_signature等。

考虑可扩展性

函数和方法不应包含太多的逻辑:拥有许多小而简单的方法比拥有少数大而复杂的方法更可取。一个好的经验法则是,一旦一个方法有多个职责,就应立即拆分它。

应避免在方法中硬编码业务逻辑,因为它会阻止子模块轻松扩展。

Coding guidelines
This page introduces the Odoo Coding Guidelines. Those aim to improve the quality of Odoo Apps code. Indeed proper code improves readability, eases maintenance, helps debugging, lowers complexity and promotes reliability. These guidelines should be applied to every new module and to all new development.

 Warning

When modifying existing files in stable version the original file style strictly supersedes any other style guidelines. In other words please never modify existing files in order to apply these guidelines. It avoids disrupting the revision history of code lines. Diff should be kept minimal. For more details, see our pull request guide.

 Warning

When modifying existing files in master (development) version apply those guidelines to existing code only for modified code or if most of the file is under revision. In other words modify existing files structure only if it is going under major changes. In that case first do a move commit then apply the changes related to the feature.

Module structure
Directories
A module is organized in important directories. Those contain the business logic; having a look at them should make you understand the purpose of the module.

data/ : demo and data xml

models/ : models definition

controllers/ : contains controllers (HTTP routes)

views/ : contains the views and templates

static/ : contains the web assets, separated into css/, js/, img/, lib/, …

Other optional directories compose the module.

wizard/ : regroups the transient models (models.TransientModel) and their views

report/ : contains the printable reports and models based on SQL views. Python objects and XML views are included in this directory

tests/ : contains the Python tests

File naming
File naming is important to quickly find information through all odoo addons. This section explains how to name files in a standard odoo module. As an example we use a plant nursery application. It holds two main models plant.nursery and plant.order.

Concerning models, split the business logic by sets of models belonging to a same main model. Each set lies in a given file named based on its main model. If there is only one model, its name is the same as the module name. Each inherited model should be in its own file to help understanding of impacted models.

addons/plant_nursery/
|-- models/
|   |-- plant_nursery.py (first main model)
|   |-- plant_order.py (another main model)
|   |-- res_partner.py (inherited Odoo model)
Concerning security, three main files should be used:

First one is the definition of access rights done in a ir.model.access.csv file.

User groups are defined in <module>_groups.xml.

Record rules are defined in <model>_security.xml.

addons/plant_nursery/
|-- security/
|   |-- ir.model.access.csv
|   |-- plant_nursery_groups.xml
|   |-- plant_nursery_security.xml
|   |-- plant_order_security.xml
Concerning views, backend views should be split like models and suffixed by _views.xml. Backend views are list, form, kanban, activity, graph, pivot, .. views. To ease split by model in views main menus not linked to specific actions may be extracted into an optional <module>_menus.xml file. Templates (QWeb pages used notably for portal / website display) are put in separate files named <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
Concerning data, split them by purpose (demo or data) and main model. Filenames will be the main_model name suffixed by _demo.xml or _data.xml. For instance for an application having demo and data for its main model as well as subtypes, activities and mail templates all related to mail module:

addons/plant_nursery/
|-- data/
|   |-- plant_nursery_data.xml
|   |-- plant_nursery_demo.xml
|   |-- mail_data.xml
Concerning controllers, generally all controllers belong to a single controller contained in a file named <module_name>.py. An old convention in Odoo is to name this file main.py but it is considered as outdated. If you need to inherit an existing controller from another module do it in <inherited_module_name>.py. For example adding portal controller in an application is done in portal.py.

addons/plant_nursery/
|-- controllers/
|   |-- plant_nursery.py
|   |-- portal.py (inheriting portal/controllers/portal.py)
|   |-- main.py (deprecated, replaced by plant_nursery.py)
Concerning static files, Javascript files follow globally the same logic as python models. Each component should be in its own file with a meaningful name. For instance, the activity widgets are located in activity.js of mail module. Subdirectories can also be created to structure the ‘package’ (see web module for more details). The same logic should be applied for the templates of JS widgets (static XML files) and for their styles (scss files). Don’t link data (image, libraries) outside Odoo: do not use an URL to an image but copy it in the codebase instead.

Concerning wizards, naming convention is the same of for python models: <transient>.py and <transient>_views.xml. Both are put in the wizard directory. This naming comes from old odoo applications using the wizard keyword for transient models.

addons/plant_nursery/
|-- wizard/
|   |-- make_plant_order.py
|   |-- make_plant_order_views.xml
Concerning statistics reports done with python / SQL views and classic views naming is the following :

addons/plant_nursery/
|-- report/
|   |-- plant_order_report.py
|   |-- plant_order_report_views.xml
Concerning printable reports which contain mainly data preparation and Qweb templates naming is the following :

addons/plant_nursery/
|-- report/
|   |-- plant_order_reports.xml (report actions, paperformat, ...)
|   |-- plant_order_templates.xml (xml report templates)
The complete tree of our Odoo module therefore looks like

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
 Note

File names should only contain [a-z0-9_] (lowercase alphanumerics and _)

 Warning

Use correct file permissions : folder 755 and file 644.

XML files
Format
To declare a record in XML, the record notation (using <record>) is recommended:

Place id attribute before model

For field declaration, name attribute is first. Then place the value either in the field tag, either in the eval attribute, and finally other attributes (widget, options,) ordered by importance.

Try to group the record by model. In case of dependencies between action/menu/views, this convention may not be applicable.

Use naming convention defined at the next point

The tag <data> is only used to set not-updatable data with noupdate=1. If there is only not-updatable data in the file, the noupdate=1 can be set on the <odoo> tag and do not set a <data> tag.

<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 supports custom tags acting as syntactic sugar:

menuitem: use it as a shortcut to declare a ir.ui.menu

template: use it to declare a QWeb View requiring only the arch section of the view.

These tags are preferred over the record notation.

XML IDs and naming
Security, View and Action
Use the following pattern :

For a menu: <model_name>_menu, or <model_name>_menu_do_stuff for submenus.

For a view: <model_name>_view_<view_type>, where view_type is kanban, form, tree, search, …

For an action: the main action respects <model_name>_action. Others are suffixed with _<detail>, where detail is a lowercase string briefly explaining the action. This is used only if multiple actions are declared for the model.

For window actions: suffix the action name by the specific view information like <model_name>_action_view_<view_type>.

For a group: <module_name>_group_<group_name> where group_name is the name of the group, generally ‘user’, ‘manager’, …

For a rule: <model_name>_rule_<concerned_group> where concerned_group is the short name of the concerned group (‘user’ for the ‘model_name_group_user’, ‘public’ for public user, ‘company’ for multi-company rules,).

Name should be identical to xml id with dots replacing underscores. Actions should have a real naming as it is used as display name.

<!-- 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>
Inheriting XML
Xml Ids of inheriting views should use the same ID as the original record. It helps finding all inheritance at a glance. As final Xml Ids are prefixed by the module that creates them there is no overlap.

Naming should contain an .inherit.{details} suffix to ease understanding the override purpose when looking at its 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>
New primary views do not require the inherit suffix as those are new records based upon the first one.

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

Do not forget to read the Security Pitfalls section as well to write secure code.

PEP8 options
Using a linter can help show syntax and semantic warnings or errors. Odoo source code tries to respect Python standard, but some of them can be ignored.

E501: line too long

E301: expected 1 blank line, found 0

E302: expected 2 blank lines, found 1

Imports
The imports are ordered as

External libraries (one per line sorted and split in python stdlib)

Imports of odoo

Imports from Odoo modules (rarely, and only if necessary)

Inside these 3 groups, the imported lines are alphabetically sorted.

# 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
Idiomatics of Programming (Python)
Always favor readability over conciseness or using the language features or idioms.

Don’t use .clone()

# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)
Python dictionary : creation and update

# -- 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)
Use meaningful variable/class/method names

Useless variable : Temporary variables can make the code clearer by giving names to objects, but that doesn’t mean you should create temporary variables all the time:

# pointless
schema = kw['schema']
params = {'schema': schema}
# simpler
params = {'schema': kw['schema']}
Multiple return points are OK, when they’re simpler

# 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
Know your builtins : You should at least have a basic understanding of all the Python builtins (http://docs.python.org/library/functions.html)

value = my_dict.get('key', None) # very very redundant
value = my_dict.get('key') # good
Also, if 'key' in my_dict and if my_dict.get('key') have very different meaning, be sure that you’re using the right one.

Learn list comprehensions : Use list comprehension, dict comprehension, and basic manipulation using map, filter, sum, … They make the code easier to read.

# 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 : In python, many objects have “boolean-ish” value when evaluated in a boolean context (such as an if). Among these are collections (lists, dicts, sets,) which are “falsy” when empty and “truthy” when containing items:

bool([]) is False
bool([1]) is True
bool([False]) is True
So, you can write if some_collection: instead of if len(some_collection):.

Iterate on iterables

# 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..."
Use 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)
As a good developer, document your code (docstring on methods, simple comments for tricky part of code)

In additions to these guidelines, you may also find the following link interesting: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html (a little bit outdated, but quite relevant)

Programming in Odoo
Avoid to create generators and decorators: only use the ones provided by the Odoo API.

As in python, use filtered, mapped, sorted, … methods to ease code reading and performance.

Propagate the context
The context is a frozendict that cannot be modified. To call a method with a different context, the with_context method should be used :

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
 Warning

Passing parameter in context can have dangerous side-effects.

Since the values are propagated automatically, some unexpected behavior may appear. Calling create() method of a model with default_my_field key in context will set the default value of my_field for the concerned model. But if during this creation, other objects (such as sale.order.line, on sale.order creation) having a field name my_field are created, their default value will be set too.

If you need to create a key context influencing the behavior of some object, choose a good name, and eventually prefix it by the name of the module to isolate its impact. A good example are the keys of mail module : mail_create_nosubscribe, mail_notrack, mail_notify_user_signature, …

Think extendable
Functions and methods should not contain too much logic: having a lot of small and simple methods is more advisable than having few large and complex methods. A good rule of thumb is to split a method as soon as it has more than one responsibility (see http://en.wikipedia.org/wiki/Single_responsibility_principle).

Hardcoding a business logic in a method should be avoided as it prevents to be easily extended by a submodule.

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

上面的代码为了示例而过度扩展,但必须考虑可读性,并且必须进行权衡。

此外,相应地命名您的函数:命名简短且适当的函数是可读/可维护代码和更紧密文档的起点。

此建议也适用于类、文件、模块和包。

永远不要提交事务

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(),你很有可能会以各种方式破坏系统,因为你会导致部分提交,从而导致部分和不干净的回滚,除其他外,还包括:

  • 业务数据不一致,通常会丢失数据

  • 工作流不同步,文件永久卡住

  • 无法干净地回滚的测试,并且会开始污染数据库并触发错误(即使在事务期间没有发生错误也是如此)

这里有一个非常简单的规则:

除非您明确创建了自己的数据库游标,否则您不应该自己调用 cr.commit(),而且需要这样做的情况是例外!

顺便说一下,如果你创建了自己的游标,那么你需要处理错误情况和适当的回滚,以及在完成游标时正确关闭它。

与普遍看法相反,在以下情况下,你甚至不需要调用 cr.commit(): - 在 models.Model 对象的 _auto_init() 方法中:这由 addons 初始化方法或 ORM 事务在创建自定义模型时处理 - 在报告中:commit() 也由框架处理,因此即使在报告中也可以更新数据库 - 在 models.Transient 方法中:这些方法与常规 models.Model 方法完全相同,在事务中调用,并在最后使用相应的 cr.commit()/rollback() 等。(如果你有疑问,请参阅上面的通用规则!)

从现在开始,服务器框架之外的所有 cr.commit() 调用都必须有一个明确的注释,解释为什么它们是绝对必要的,为什么它们确实是正确的,为什么它们不会破坏事务。否则,它们可以而且将会被删除!

正确使用翻译方法

Odoo使用一个名为“underscore”_()的类似gettext的方法来指示代码中使用的静态字符串需要在运行时使用上下文的语言进行翻译。这个伪方法可以在你的代码中通过导入如下代码来访问:

from odoo import _

在使用它时必须遵循一些非常重要的规则,以便它能够正常工作,并避免在翻译中填充无用的垃圾。

基本上,这种方法只应用于在代码中手动编写的静态字符串,它不能用于翻译字段值,如产品名称等。必须使用相应字段上的翻译标记来完成。

该方法接受可选的位置参数或命名参数。规则非常简单:对下划线方法的调用应始终采用’触点名称.文本’的形式,而不能采用其他形式

# 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) 而不是 position(当必须替换多个变量时)。这使得社区翻译人员更容易进行翻译。

符号和约定

  • 模型名(使用点表示法,以模块名作为前缀):

    • 定义Odoo模型时:使用名称的单数形式(res.partner和sale.order,而不是res.partnerS和saleS.orderS)

    • 定义Odoo临时对象(向导)时:使用<related_base_model.<action>,其中related_base_model是与临时对象相关的基本模型(在models/中定义),action是临时对象操作的简写。避免使用向导字。例如:account.invoice.make,project.task.delegate.batch,…

    • 当定义报表模型(SQL视图等)时:使用<related_base_model>.report.<action>,基于Transient约定。

  • 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
  • One2Many和Many2Many字段应该始终以_id作为后缀(例如:sale_order_line_ids)

  • Many2One字段应该以_id作为后缀(例如:partner_id, user_id,…)

  • 方法约定

    • 计算字段:计算方法模式为 _compute_<field_name>
    • 搜索方法:搜索方法模式为 _search_<field_name>
    • 默认方法:默认方法模式为 _default_<field_name>
    • 选择方法:选择方法模式为_selection_<field_name>
    • Onchange 方法:onchange 方法模式为 _onchange_<field_name>
    • 约束方法:约束方法模式为 _check_<constraint_name>
    • 动作方法:对象动作方法以action_为前缀。 由于它只使用一条记录,因此请在方法的开头添加 self.ensure_one()
  • 在Model属性中的顺序应该是

    • 私有属性 (_name, _description, _inherit, _sql_constraints, …)
    • 默认方法和default_get
    • 字段声明
    • 计算、反转和搜索方法的顺序与字段声明相同
    • 选择方法(用于返回选择字段的计算值的方法)
    • 约束方法(@api.constrains)和 onchange 方法(@api.onchange)
    • CRUD 方法(ORM 覆盖)
    • 操作方法(Action methods)
    • 最后,其他商业方法。
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/文件夹中的所有文件,但前缀为插件名称。因此,例如,如果文件位于adds/web/static/src/js/some_file.js中,则可以在url your-odoo-server.com/web/static/src/js/some_file.js中静态访问该文件。

惯例是按照以下结构组织代码:

  • static:一般所有静态文件
    • static/lib:这是js库应该位于的地方,在一个子文件夹中。例如,jquery库中的所有文件都在andard/lib/jquery中
    • static/src:通用静态源代码文件夹
      • static/src/css:所有 CSS 文件
      • static/src/fonts :所有字体文件
      • static/src/img :所有图片文件
      • static/src/js : 所有Javascript 文件
        • static/src/js/tours: 最终用户指南文件(教程,不是测试)
      • static/src/scss: 所有 SCSS 文件
      • static/src/xml: 所有xml文件
    • static/tests: 这是我们放置所有测试相关文件的地方
      • static/tests/tours: t这是我们放置所有巡回测试文件(不是教程)的地方。

Javascript 编码指南

  • use strict; 建议用于所有 JavaScript 文件
  • 使用 linter (jshint, …)
  • 永远不要添加最小化的Javascript库
  • 使用骆驼式声明类

CSS 和 SCSS

语法和格式

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;
}

CSS

.o_foo, .o_foo_bar, .o_baz {
   height: 32px;
}

.o_foo .o_quux, .o_foo_bar .o_quux, .o_baz .o_qux {
   height: 16px;
}

.o_corge {
   background: #EAEAEA;
}
  • 四(4)个空格缩进,没有标签;

  • 最多80个字符宽的列;

  • 左大括号 ({): 最后的选择器后面的空白;

  • 闭括号(}):放在新的一行;

  • 每份声明用一行;

  • 有意义地使用空格。

建议的 Stylelint 设置

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

属性顺序

从“外部”开始,从位置开始,以装饰规则(字体、过滤器等)结束。

作用域 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>是模块的技术名称(sale、im_chat等)或模块保留的主路由(主要用于网站模块,例如:网站论坛模块的o_forum)。
这个规则的唯一例外是Web客户端:它只使用o_前缀。
避免创建过于具体的类和变量名。在命名嵌套元素时,选择“子孙”方法。

例子

反面例子

<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]
可选修饰符。

例子

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

SCSS 变量(作用域)

这些变量在块内声明,无法从外部访问。 我们的标准惯例是 .$-[variable name]
例子

.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…)。

以作用域变量的形式命名可选参数,如$-[argument]。

例子

@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]
可选的修饰符。

例子

.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密切相关,这意味着它们用于根据上下文调整设计和布局,而不是管理全局设计系统。这些通常在组件的属性在特定上下文或其他情况下可能发生变化时使用。

我们在组件的主块中定义这些属性,提供默认回退。

例子

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 变量

尽管看起来很相似,CSS和SCSS变量的行为却截然不同。主要区别在于,SCSS变量是命令式的,并且会被编译掉,而CSS变量是声明式的,并包含在最终输出中。

另请参阅 SASS 文档中的 CSS/SCSS 变量差异

在Odoo中,我们采用了两全其美的做法:使用SCSS变量来定义设计系统,同时在上下文适应方面选择CSS变量。

通过添加SCSS变量来改进前一个示例的实现,以便在顶级获得控制权并确保与其他组件的一致性。

例子

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 来执行此操作。

此规则的例外情况应该相当明显,例如在需要一定程度的上下文感知才能正确呈现的包之间共享的模板。

文章来源:https://blog.csdn.net/weixin_40986713/article/details/135601955
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。