Odoo开发规范
来源:https://www.jianshu.com/p/e892bf01f036
- 视图(view):
<model_name>_view_<view_type>
,view_type
可能的取值有:kanban
,form
,tree
,search
- 动作(action):
主动作命名为
<model_name>_action
,其他的动作命名为<model_name>_action_<detail>
,其中<detail>
使用小写字母简单描述动作 - 组(group):
<model_name>_group_<group_name>
,group_name可能的取值包括:user
,manager
,... - 规则(rule):
<model_name>_rule_<concerned_group>
,concerned_group可能的取值包括: 模型用户规则user
, 公共用户规则public
,多公司用户规则company
<!-- views and menus --> <record id="model_name_view_form" model="ir.ui.view"> ... </record> <record id="model_name_view_kanban" model="ir.ui.view"> ... </record> <menuitem id="model_name_menu_root" name="Main Menu" sequence="5" /> <menuitem id="model_name_menu_action" name="Sub Menu 1" parent="module_name.module_name_menu_root" action="model_name_action" sequence="10" /> <!-- actions --> <record id="model_name_action" model="ir.actions.act_window"> ... </record> <record id="model_name_action_child_list" model="ir.actions.act_window"> ... </record> <!-- security --> <record id="module_name_group_user" model="res.groups"> ... </record> <record id="model_name_rule_public" model="ir.rule"> ... </record> <record id="model_name_rule_company" model="ir.rule"> ... </record>
视图名称(name)使用点表示法:my.model.view_type
或者
my.model.view_type.inherit
继承XML的命名
继承视图的命名规则:<base_view>_inherit_<current_module_name>
,其中_inherit_<current_module_name>
是扩展视图的模块的技术名称。
<record id="inherited_model_view_form_inherit_my_module" model="ir.ui.view"> ... </record>
Python
Odoo源代码基本准守Python标准PEP8,但是忽略其中一些规则:
- E501:行太长了
- E301:预计有1个空行,找到0
- E302:预计有2个空行,找到1
- E126:延长线过度缩进以用于悬挂缩进
- E123:关闭支架与开口支架线的压痕不匹配
- E127:延伸线过度缩进以进行视觉缩进
- E128:用于视觉缩进的缩进的延续线
- E265:阻止评论应以'#'开头
Import
import 顺序
- 外部库
- 导入odoo
- 导入odoo的模块
在每组中的导入按字母顺序排序
# 1 : 导入python库 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文件都应该以
# -*- coding: utf-8 -*-
作为第一行。 - 简单易读的代码
Odoo中编程
- 避免创建生成器和装饰器:仅使用Odoo API已有的
- 使用filtered,mapped,sorted方法来提升代码可读性和性能。
- 使用更易理解的方法名
让你的方法可以批量处理
当添加一个函数时,确保它可以处理多重数据,如通过api.multi()装饰器,可以在self上进行循环处理
@api.multi def my_method(self) for record in self: record.do_cool_stuff()
避免使用api.one装饰器,因为它可能不会像你想象中一样工作。
为了更好的性能,比如当定义一个状态按钮时,不在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中,context变量是不能修改的。可以通过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
尽量使用ORM
当ORM可以实现的时候尽量使用ORM而不要直接写sql,因为它可能会绕过orm的一些规则如权限、事务等,还会让代码变得难读且不安全。
# 错误的写法,注入风险,代码效率低 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()] # 不会被注入,但仍然是错误的写法 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()] # 推荐的写法 auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)])
防止注入
不要用python的+号连接符、%解释符来拼sql
# 错误的写法 self.env.cr.execute('SELECT distinct child_id FROM account_account_consol_rel ' + 'WHERE parent_id IN ('+','.join(map(str, ids))+')') # 推荐的写法 self.env.cr.execute('SELECT DISTINCT child_id '\ 'FROM account_account_consol_rel '\ 'WHERE parent_id IN %s', (tuple(ids),))
不要手动提交事务
odoo有自己的一套机制用于事务处理
正确的使用翻译方法
odoo用一个下划线方法来表明某字段需要翻译,该方法通过from
odoo.tools.translate import
_
导入。一般情况下该方法只能被用在代码里的规定字符串的翻译,不能用于动态字符串的翻译,翻译方法的调用只能是_('literal
string'),里面不能加其他的。
# 好的方式,简洁 error = _('This record is locked!') # 好的方式,包含格式的字符串 error = _('Record %s cannot be modified!') % record # 好的方式,多行文字的字符串 error = _("""This is a bad multiline example about record %s!""") % record error = _('Record %s cannot be modified' \ 'after being validated!') % record # 错误的方式:试图在字符串格式化后进行翻译 # 这样没有作用,而只会把翻译搞乱 error = _('Record %s cannot be modified!' % record) # 错误:动态字符串,不能这样翻译 error = _("'" + que_rec['question'] + "' \n") # 错误:字段值将会被系统字段翻译,而不会获取预期结果 error = _("Product %s is out of stock!") % _(product.name) # 错误的方式:试图在字符串格式化后进行翻译 error = _("Product %s is out of stock!" % product.name)
符号和习惯
- 模型名-使用.分隔,模块名做前缀
-
定义odoo模型时,使用单数形式的名字如
res.user,res.partner
-
定义wizard时,命名格式为
<related_base_model>.<action>
,related_base_model
是关联模型名称,action
是功能简称,如account.invoice.make
-
定义报表模型时,使用
<related_base_model>.report.<action>
,和wizard一样
-
定义odoo模型时,使用单数形式的名字如
- python类-使用驼峰命名方式
class AccountInvoice(models.Model): ... class account_invoice(osv.osv): ...
- 变量名
- 模型变量使用驼峰命名方式
- 普通变量用下划线+小写字母
- 由于新api中记录是集合形式,当变量不包含id时不以id作后缀
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_<field_name>
- 搜索方法 -
_search_<field_name>
- 默认方法 -
_default_<field_name>
- onchange方法 -
_onchange_<field_name>
- 约束方法 -
_check_<constraint_name>
- action方法 - 一个对象的动作方法一般以action_开头,它的装饰器是@api.multi,如果它只使用单条计算,可在方法头添加self.ensure_one()
- 计算字段 -
计算方法一般是
-
模型中属性顺序
- 私有属性:_name, _description, _inherit, ...
- 默认方法和_default_get
- 字段声明
- 计算和搜索方法和字段声明顺序一致
- 约束方法(@api.constrains)和onchange方法(@api.onchange)
- CRUD方法
- action方法
- 其他业务方法
class Event(models.Model): # 私有属性 _name = 'event.event' _description = 'Event' # 默认方法 def _default_name(self): ... # 字段声明 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') # 计算和搜索方法,与字段申明顺序一致 @api.multi @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register') def _compute_seats(self): ... # 约束方法和onchange方法 @api.constrains('seats_max', 'seats_available') def _check_seats_limit(self): ... @api.onchange('date_begin') def _onchange_date_begin(self): ... # CRUD方法 def create(self, values): ... # action方法 @api.multi def action_validate(self): self.ensure_one() ... # 其他业务方法 def mail_user_confirm(self): ...
Javascript和CSS
- 在所有javascript文件中使用use strict;
- 使用linter
- 不添加压缩javascript库
- 类名使用驼峰命名
- 如果javascript代码需要全局运行,在website模块中声明一个if_dom_contains 方法
odoo.website.if_dom_contains('.jquery_class_selector', function () { /*your code here*/ });
- 将所有的class命名为o_<module_name>,如o_forum
- 避免使用id
- 使用bootstrap的class
- 用下划线+小写来命名
小礼物走一走,来简书关注我
作者:luohuayong
链接:https://www.jianshu.com/p/e892bf01f036
來源:简书