本文介绍了新版Odoo编码指南。那些旨在提高代码的质量 (例如更好的可读性)和Odoo应用程序。实际上,适当的代码简化了维护、调试,降低了复杂性,提高了可靠性。
这些指导原则应适用于每一个新的模块和新的程序。只有在代码重构(迁移到新API、大重构、……)的情况下,这些准则才适用于旧模块。
警告
这些指南是用新的模块和新的文件编写的。当修改现有文件时,文件的原始样式严格地取代任何其他样式指南。 换句话说,不要修改现有文件以应用这些指南,以避免破坏每一行的修订历史。有关更多细节,请参见pull request guide
模块结构 目录在重要目录中组织一个模块。这些包含业务逻辑;查看它们应该了解模块的用途。
- data/ : demo和数据xml
- models/ : 模型定义
- controllers/ : 包含控制器(HTTP路由)
- views/ : 包含视图和模板
- static/ : 包含Web使用的静态文件,分别为 css/, js/, img/, lib/, ...
其他可选目录组成模块。
- wizard/ : 重组瞬态模型(以前的osv_memory)和它们的视图
- report/ : 包含报表(RML报表[deprecated], 基于SQL视图(为报表)的模型和其他复杂的报表)。Python对象和XML视图包含在目录中
- tests/ : 包含Python/YML测试
对于视图的声明,从(前端)模版分裂后端视图到2个不同的文件中。
对于models,通过一组模型将业务逻辑拆分,在每一个集合中选择一个主模型,该模型将其名称赋予该集合。如果只有一个模型,则其名称与模块名相同。或每一组命名为的以下文件可以创建:
models/.py
models/.py
views/_templates.xml
views/_views.xml
例如,销售模块介绍sale_order
和sale_order_line
,在它们中sale_order
为主。所以 文件将命名为
models/sale_order.py
和views/sale_order_views.py
对于数据,按目的拆分:demo或data。文件名将是主模型的名字,使用 _demo.xml 或 _data.xml作为后缀
对于controllers, 唯一的文件应该命名为main.py。否则,如果需要从另一模块继承现有控制器,它的名字将是.py。与模型不同,每个控制器类应该包含在分离的文件中
对与static文件,由于资源可以在不同的上下文中使用 (前端,后端,两者都是),它们将包含在仅有的一个bundle中。因此,CSS/Less, JavaScript和XML文件使用bundle类型名字作为后缀。即: im_chat_common.css, im_chat_common.js在'assets_common' bundle中, 且im_chat_backend.css, im_chat_backend.js在'assets_backend' bundle中。如果模块只拥有一个文件,则该约定将是.ext (即: project.js)。不要链接到Odoo以外的数据(图片,库):不要对图片使用一个URL但是可以代替的拷贝它到我们的代码库中。
关于data, 按目的拆分它们:data或demo。文件名将是主模型的名字,以_data.xml或 _demo.xml作为后缀
关于向导,命名约定是:
.py
_views.xml
是占主导地位的瞬态模型的名字,就像模型。.py可以包含模型'model.action'和'model.action.line'
对于统计报告,它们的名字应该看起来像:
_report.py
_report_views.py
(通常是pivot和graph视图)
对于可打印报表,您应该具有:
_reports.py
(报表动作,表格格式定义, ...)_templates.xml
(xml报表模版)
完整的树应该看起来像
addons//
|-- __init__.py
|-- __manifest__.py
|-- controllers/
| |-- __init__.py
| |-- .py
| `-- main.py
|-- data/
| |-- _data.xml
| `-- _demo.xml
|-- models/
| |-- __init__.py
| |-- .py
| `-- .py
|-- report/
| |-- __init__.py
| |-- .py
| |-- _views.xml
| |-- _reports.xml
| `-- _templates.xml
|-- security/
| |-- ir.model.access.csv
| `-- _security.xml
|-- static/
| |-- img/
| | |-- my_little_kitten.png
| | `-- troll.jpg
| |-- lib/
| | `-- external_lib/
| `-- src/
| |-- js/
| | `-- .js
| |-- css/
| | `-- .css
| |-- less/
| | `-- .less
| `-- xml/
| `-- .xml
|-- views/
| |-- _templates.xml
| |-- _views.xml
| |-- _templates.xml
| `-- _views.xml
`-- wizard/
|-- .py
|-- _views.xml
|-- .py
`-- _views.xml
注
文件命名应该仅包含[a-z0-9_]
(小写字母数字和_
)
警告
使用正确的文件权限 : 文件夹755和文件644(针对非windows系统)
XML文件 格式为了在XML中声明记录,建议使用record 记号(使用) :
- 在
model
属性前放id
属性 - 对于字段声明,首先是
name
属性。然后将值放在field标记中,或者在eval
属性中,最后根据重要性排序其他属性(widget, options, ...) - 尝试按模型分组记录。在action/menu/views之间的依赖性的情况下,此约定可能不适用
- 使用下一点定义的命名约定
- 标记仅用于设置使用noupdate=1的不可更新的数据
view.name
object_name
Odoo支持自定义标签充当语法糖:
- menuitem: 使用它作为声明ir.ui.menu的快捷方式
- workflow: 标记向现有工作流发送信号
- template: 使用它声明一个仅仅需要视图的arch部分的QWeb视图
- report: 用于声明一个报表操作
- act_window: 如果记录符号不能做你想做的事,就用它
4个首选的标记在record标记之前是优先考虑的
命名xml_id安全, 视图和操作
使用以下模式:
- 对于菜单:
_menu
- 对于视图:
_view_
, view_type 是kanban
,form
,tree
,search
, ... - 对于操作: 主要的操作格式
_action
。另一些则用_
后缀,其中细节是一个小写字符串,简要解释操作。只有在为模型声明多个操作时才使用此方法 - 对于分组:
_group_
group_name是组的名称,通常是'user', 'manager', ... - 对于规则:
_rule_
concerned_group是连接的组的简洁名称('user' 对于'model_name_group_user', 'public' 对于公共用户, 'company'对于多公司规则, ...)
...
...
...
...
...
...
...
注
视图名称使用点标记my.model.view_type
或者my.model.view_type.inherit
而不是"这是我的模型的表单视图".
继承的XML
继承视图的命名模式是_inherit_
。模块只能扩展视图一次。用_inherit_
加后缀到原始的名称,其中current_module_name 是扩展视图的模块的技术名称
...
Python
PEP8选项
使用linter可以帮助显示语法和语义警告或错误。Odoo源代码试图尊重Python标准,但其中一些可以被忽略。
- E501: 行太长
- E301: 预计1空行,发现
- E302: 预计2条空行,发现1条
- E126: 悬停缩进延拓线
- E123: 闭合托架与开口托架线压痕不匹配
- E127: 为可视缩进显示过度缩进线
- E128: 为可视缩进显示连续的缩进线
- E265: 方块注释应以'# '开始
导入顺序如下
- 外部库(在python stdlib中对每行进行排序和拆分)
- odoo的导入
- 从Odoo模块导入(很少,只有在必要时)
在这3组中,导入的线按字母顺序排序。
# 1 : imports of python lib
import base64
import re
import time
from datetime import datetime
# 2 : imports of odoo
import odoo
from odoo import api, fields, models # alphabetically ordered
from odoo.tools.safe_eval import safe_eval as eval
from odoo.tools.translate import _
# 3 : imports from odoo modules
from odoo.addons.website.models.website import slug
from odoo.addons.web.controllers.main import login_redirect
惯用的Python编程习惯
- 每一个python文件应该把
# -*- coding: utf-8 -*-
作为第一行 - 总是喜欢可读性,相比与简介或者使用语言特性或习语
- 不要使用
.clone()
# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)
- Python字典:创建与更新
# -- creation empty dict
my_dict = {}
my_dict2 = dict()
# -- creation with values
# bad
my_dict = {}
my_dict['foo'] = 3
my_dict['bar'] = 4
# good
my_dict = {'foo': 3, 'bar': 4}
# -- update dict
# bad
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# good
my_dict.update(foo=3, bar=4, baz=5)
my_dict = dict(my_dict, **my_dict2)
- 使用有意义的变量/类/方法名称
- 无用变量:临时变量可以通过给对象命名,使代码更清晰,但这并不意味着你应该始终创建临时变量:
# pointless
schema = kw['schema']
params = {'schema': schema}
# simpler
params = {'schema': kw['schema']}
- 当它们更简单时,多个返回点是可以
# a bit complex and with a redundant temp variable
def axes(self, axis):
axes = []
if type(axis) == type([]):
axes.extend(axis)
else:
axes.append(axis)
return axes
# clearer
def axes(self, axis):
if type(axis) == type([]):
return list(axis) # clone the axis
else:
return [axis] # single-element list
- 了解你的嵌入部件:你至少应该对所有的Python构建有一个基本的了解 (http://docs.python.org/library/functions.html)
value = my_dict.get('key', None) # very very redundant
value= my_dict.get('key') # good
同样的, if 'key' in my_dict
和if my_dict.get('key')
有非常不同的含义,确保你使用的是正确的
- 学习列表理解:使用列表理解,字典理解,
map
,filter
,sum
, ... 的基本操作,它们使代码更容易阅读
# not very good
cube = []
for i in res:
cube.append((i['id'],i['name']))
# better
cube = [(i['id'], i['name']) for i in res]
- 集合也是布尔值:在Python中,许多对象在布尔上下文(如IF)中有“布尔值”值。其中有集合 (列表, 字典, 集合, ...) ,当它们为空时是“假”的,当包含项时是“真”的:
bool([]) is False
bool([1]) is True
bool([False]) is True
因此,你可以编写 if some_collection:
取代if len(some_collection):
.
- 在迭代器上迭代
# creates a temporary list and looks bar
for key in my_dict.keys():
"do something..."
# better
for key in my_dict:
"do something..."
# creates a temporary list
for key, value in my_dict.items():
"do something..."
# only iterates
for key, value in my_dict.iteritems():
"do something..."
- 使用dict.setdefault
# longer.. harder to read
values = {}
for element in iterable:
if element not in values:
values[element] = []
values[element].append(other_value)
# better.. use dict.setdefault method
values = {}
for element in iterable:
values.setdefault(element, []).append(other_value)
- 作为一个好的开发人员,编写代码 (方法上的文档字符串,代码中复杂部分的简单注释)
- 除了这些指南,你还可以发现以下的有趣链接: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html (有点过时,但相当相关)
- 避免创建生成器和装饰器:只使用Odoo API提供的那些
- 与Python一样,使用
filtered
,mapped
,sorted
, ... 简化代码阅读和性能的方法
让你的方法批量工
添加函数时,确保它可以处理多个记录。典型的,这种方法使用api.multi装饰器进行装饰(或者在旧API中写入id列表)。然后,您必须迭代self来处理每个记录
@api.multi
def my_method(self)
for record in self:
record.do_cool_stuff()
避免使用api.one
装饰器:这可能不会达到您所期望的,并且扩展这样的方法并不像 api.multi 方法那么简单,因为它返回一个结果列表(由记录集ID排序)。
对于性能问题,当开发一个“状态按钮”(例如)时,不要在api.multi方法中的循环中执行search或search_count。建议使用read_group方法,仅在一个请求中计算所有值
@api.multi
def _compute_equipment_count(self):
""" Count the number of equipement per category """
equipment_data = self.env['hr.equipment'].read_group([('category_id', 'in', self.ids)], ['category_id'], ['category_id'])
mapped_data = dict([(m['category_id'][0], m['category_id_count']) for m in equipment_data])
for category in self:
category.equipment_count = mapped_data.get(category.id, 0)
传播上下文
在新的API中,上下文是不能修改的 frozendict
。若要调用具有不同上下文的方法, 则应使用with_context
方法:
records.with_context(new_context).do_stuff() # all the context is replaced
records.with_context(**additionnal_context).do_other_stuff() # additionnal_context values override native context ones
在上下文中传递参数会产生危险的副作用。由于这些值是自动传播的,所以会出现一些行为。调用在上下文中使用default_my_field 键的模型create()
方法将为相关模型设置默认值my_field 。但是,如果固化此创建,其他对象 (例如sale.order.line, 在sale.order创建上) 具有字段名e my_field,它们的默认值也将被设置。
如果需要创建影响某个对象行为的关键上下文,则选择一个好名称,并最终通过模块的名称对其添加前缀以隔离其影响。一个很好的例子是 mail
模块的键:mail_create_nosubscribe, mail_notrack, mail_notify_user_signature, ...
不要绕过ORM
当ORM可以做同样的事情时,你不应该直接使用数据库光标!如果这样做,您将绕过所有ORM特征,可能是事务、访问权限等。
很有可能,你也使代码更难阅读,可能更不安全
# very very wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in (' + ','.join(map(str, ids))+') AND state=%s AND obj_price > 0', ('draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# no injection, but still wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in %s '\
'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# better
auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)])
请不要SQL注入!
在使用手动SQL查询时,必须注意不要引入SQL注入漏洞。当用户输入被错误地过滤或被严重引用时,该漏洞出现,允许攻击者向SQL查询引入不受欢迎的子句(例如绕过过滤器或执行UPDATE或DELETE命令)。
安全的最好方法是永远不要使用Python字符串连接(+)或字符串参数插值(%)将变量传递给SQL查询字符串。
第二个原因,几乎同样重要的是,数据库抽象层(psycopg2)的任务是决定如何格式化查询参数,而不是您的任务!例如,psycopg2知道,当你传递一个值列表时,它需要将它们格式化为逗号分隔的列表,以括号括起来!
# the following is very bad:
# - it's a SQL injection vulnerability
# - it's unreadable
# - it's not your job to format the list of ids
self.env.cr.execute('SELECT distinct child_id FROM account_account_consol_rel ' +
'WHERE parent_id IN ('+','.join(map(str, ids))+')')
# better
self.env.cr.execute('SELECT DISTINCT child_id '\
'FROM account_account_consol_rel '\
'WHERE parent_id IN %s',
(tuple(ids),))
这是非常重要的,所以在重构时也要小心,最重要的是不要复制这些模式!
这里有一个值得记忆的例子来帮助你记住这个问题 (但是不要在那里复制代码)。在继续之前,请务必阅读pyscopg2 的在线文档,以了解如何正确使用它:
- 查询参数问题(http://initd.org/psycopg/docs/usage.html#the-problem-with-the-query-parameters)
- 如何用psycopg2 传递参(http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries)
- 高级参数类型 (http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types)
在可能的情况下保持你的方法简短/简单
功能和方法不应该包含太多的逻辑: 有很多小的和简单的方法比用很少的大而复杂的方法更可取。一个好的经验法则是: - 它有不止一种责任 (详见http://en.wikipedia.org/wiki/Single_responsibility_principle) -它太大,不能装在一个屏幕上。
同时,相应地命名您的函数: 小的和正确命名的函数是可读/可维护代码的起点和更严格的文档。
此建议也与类、文件、模块和包有关 (也可参见 http://en.wikipedia.org/wiki/Cyclomatic_complexity)
绝不提交事务
Odoo框架负责为所有RPC调用提供事务上下。其原理是,在每个RPC呼叫开始时打开新的数据库游标,并在调用返回之前,在向RPC客户端发送应答之前提交,大致如下:
def execute(self, db_name, uid, obj, method, *args, **kw):
db, pool = pooler.get_db_and_pool(db_name)
# create transaction cursor
cr = db.cursor()
try:
res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
cr.commit() # all good, we commit
except Exception:
cr.rollback() # error, rollback everything atomically
raise
finally:
cr.close() # always close cursor opened manually
return res
如果在执行RPC调用期间发生任何错误,则事务以原子方式回滚,保存系统的状态。
类似地,系统还在测试套件的执行期间提供专用事务,因此可以根据服务器启动选项回滚或不回滚。
结果是,如果您手动调用cr.commit(),任何地方都有很高的机会,您将以不同的方式中断系统,因为您将导致部分提交,从而部分和不干净的回滚,导致其他问题:
- 不一致的业务数据,通常是数据丢失
- 工作流不同步,文档永久粘贴
- 无法快速回滚的测试,将开始污染数据库,并触发错误 (即使在事务期间没有错误发生,这也是true)
这里有一条非常简单的规则
除非您已显式创建了自己的数据库游标,否则应该从不调 cr.commit()
! 而你需要做的事情是例外的!
顺便说一下,如果您确实创建了自己的游标,那么您需要处理错误情况和正确的回滚,以及在完成该游标时正确关闭光。
与流行的信条相反,在下面的情况下,您甚至不需要调用cr.commit()
: - 在一个i额models.Model对象的_auto_init()方法中:在创建自定义模型时,通过addons初始化方法或ORM事务来处理这一点 - 在报表中:该commit()也由框架处理,因此您甚至可以从报表中更新数据库 - 在models.Transient 方法中:这些方法被准确的称为 models.Model 中的一个, 在事务中和在结束时使用相应的cr.commit()/rollback()
等。 (如果您有疑问,请参见上面的一般规则!)
所有的cr.commit()
从现在起调用服务器框架之外的内容,必须有一个明确的注释来解释为什么它们是绝对必要的,为什么它们确实是正确的,以及为什么它们不中断事务。 否则,他们可以并将被删除!
正确使用翻译方法
Odoo使用一个名为"下划线" _( )
的像文本一样的方法来指示代码中使用的静态字符串需要在运行时使用上下文语言进行翻译。 通过导入如下代码,可以在代码中访问此伪方法:
from odoo.tools.translate import _
当使用它时,必须遵循一些非常重要的规则,以避免使用无用的垃圾。
基本上,这种方法只适用于在代码中手工编写的静态字符串,它不适用于翻译字段值,例如产品名称等。必须使用相应字段上的平移标志来完成此操作。
该规则非常简单:对下划线方法的调用应该总是以 _('literal string')
的形式出现,而无其他:
# good: plain strings
error = _('This record is locked!')
# good: strings with formatting patterns included
error = _('Record %s cannot be modified!') % record
# ok too: multi-line literal strings
error = _("""This is a bad multiline example
about record %s!""") % record
error = _('Record %s cannot be modified' \
'after being validated!') % record
# bad: tries to translate after string formatting
# (pay attention to brackets!)
# This does NOT work and messes up the translations!
error = _('Record %s cannot be modified!' % record)
# bad: dynamic string, string concatenation, etc are forbidden!
# This does NOT work and messes up the translations!
error = _("'" + que_rec['question'] + "' \n")
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is out of stock!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is out of stock!" % product.name)
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is not available!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is not available!" % product.name)
# Instead you can do the following and everything will be translated,
# including the product name if its field definition has the
# translate flag properly set:
error = _("Product %s is not available!") % product.name
此外,请记住,译者必须处理传递给下划线函数的文字值,所以请尽量使它们易于理解,并将虚假字符和格式设置为最小。译者必须注意格式模式,如%s或%d,换行符等。需要保存,但重要的是用一种明智和明显的方式:
# Bad: makes the translations hard to work with
error = "'" + question + _("' \nPlease enter an integer value ")
# Better (pay attention to position of the brackets too!)
error = _("Answer to question %s is not valid.\n" \
"Please enter an integer value.") % question
一般来说,在Odoo中,当操作字符串时, 喜欢使用%
在.format()
中(在字符串中只替换一个变量时),且喜欢%(varname)
而不是位置(当需要替换多个变量时)。这使得社区翻译者的翻译变得更容易。
-
模型名称(使用点标记,模块名前缀):
- 定义Odoo模型时:使用名称的单数形式 (res.partner 和sale.order 而不是 res.partnerS 和saleS.orderS)
- 当定义一个Odoo 向导(wizard)是 : 使用
.
,其中related_base_model 是和瞬态关联的基本模型(定义在models/中), 而操作是瞬态完成的事情的短暂的名字。例如:account.invoice.make
,project.task.delegate.batch
, ... - 当定义报表模型时(SQL视图) : 使用
.report.
,基于瞬态约定
- Odoo Python类: 在api v8 (面向对象的样式)中的代码使用驼峰命名法,在旧的api (SQL 风格)中代码使用下划线小写标记法
class AccountInvoice(models.Model):
...
class account_invoice(osv.osv):
...
-
变量名:
- 为模型变量使用驼峰命名
- 常用变量使用下划线小写表示法
- 由于新API与记录或记录集一起工作而不是id列表,所以如果不包含id或id列表,则不要用_id或_ids 来作为变量名后缀
ResPartner = self.env['res.partner']
partners = ResPartner.browse(ids)
partner_id = partners[0].id
One2Many
和Many2Many
字段应该总是有 _ids作为后缀(例子:sale_order_line_ids)Many2One
字典应该有 _id 做为后缀(例子 : partner_id, user_id, ...)-
方法约定
- 计算字段:计算方法模式是 _compute_
- 搜索方法:搜索方法模式是 _search_
- 默认方法:默认方法模式是 _default_
- Onchange方法:Onchange方法模式是_onchange_
- 约束方法:约束方法模式是 _check_
- 操作方法:一个对象操作方法是以action_为前缀。它的装饰器是
@api.multi
, 但是因为它只使用一个记录,在方法的开始添加self.ensure_one()
-
模型中的属性顺序应该是
- 私有属性 (
_name
,_description
,_inherit
, ...) - 默认方法和
_default_get
- 字段声明
- 以与字段声明相同顺序的计算和搜索方法
- 约束方法 (
@api.constrains
) 和onchange方法(@api.onchange
) - CRUD方法 (ORM重写的)
- 操作方法
- 最后,其他业务方法。
- 私有属性 (
class Event(models.Model):
# Private attributes
_name = 'event.event'
_description = 'Event'
# Default methods
def _default_name(self):
...
# Fields declaration
name = fields.Char(string='Name', default=_default_name)
seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
store=True, readonly=True, compute='_compute_seats')
seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
store=True, readonly=True, compute='_compute_seats')
price = fields.Integer(string='Price')
# compute and search fields, in the same order of fields declaration
@api.multi
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
def _compute_seats(self):
...
# Constraints and onchanges
@api.constrains('seats_max', 'seats_available')
def _check_seats_limit(self):
...
@api.onchange('date_begin')
def _onchange_date_begin(self):
...
# CRUD methods (and name_get, name_search, ...) overrides
def create(self, values):
...
# Action methods
@api.multi
def action_validate(self):
self.ensure_one()
...
# Business methods
def mail_user_confirm(self):
...
Javascript 和CSS
对于javascript :
use strict;
建议所有的JavaScript文件- 使用linter (jshint, ...)
- 永远不要添加简化的JavaScript库
- 使用驼峰命名法进行类声明
- 除非您的代码应该在每个页面上运行,否则要使用网站模块的if_dom_contains函数来指定特定页面。指定一个元素,该元素对您的代码需要使用jQuery运行的页面是特定的
odoo.website.if_dom_contains('.jquery_class_selector', function () {
/*your code here*/
});
对于CSS :
- 使用o_作为你所有类的前缀,其中module_name是一个模块的技术名称('sale', 'im_chat', ...) 或模块保留的主要路径(主要是对网站模块,即 : 'o_forum' 为了website_forum 模块)。这个规则唯一的例外是web客户端:它简单的使用o_ 作为前最
- 避免使用 id
- 使用Bootstrap原生类
- 使用下划线小写表示法命名类
使用如下作为你提交的前缀
- [IMP] 为了改进
- [FIX] 用于错误修复
- [REF] 用于重构
- [ADD] 用于增加新资
- [REM] 用于资源的剔除
- [MOV] 用于移动文件(不要改变移动文件的内容,否则Git会失去追踪,并且历史将会丢失!), 或者简单地将代码从文件移动到另一个文
- [MERGE] 用于合并提交(仅用于前/后端口)
- [CLA] 用于签署Odoo 个人捐助许可证
然后,在消息本身中,指定由您的更改(模块名称, 库, 转换对象, ...) 影响的代码的一部分以及更改的描述。
- 始终包含有意义的提交消息:它应该是自解释的(足够长),包括已经被改变的模块的名称和变化背后的原因。不要使用"bugfix"或"improvements"这样的单词
- 避免同时影响多个模块的提交。试着将不同的提交分成不同的模块(如果我们需要分别恢复一个模块,这将是很有帮助的)
[FIX] website, website_mail: remove unused alert div, fixes look of input-group-btn
Bootstrap's CSS depends on the input-group-btn
element being the first/last child of its parent.
This was not the case because of the invisible
and useless alert.
[IMP] fields: reduce memory footprint of list/set field attributes
[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code.
Instead of using global ...
注
用长的描述来解释为什么不是什么,什么可以看到的差异
ps:有翻译不当之处,欢迎留言指正。
原文地址:https://www.odoo.com/documentation/10.0/reference/guidelines.html