Odoo开发规范
本文来源:https://www.jianshu.com/p/e892bf01f036
Odoo开发规范
模块结构
文件夹列表及对应作用
- data/:演示和数据文件
- models/:模型定义
- controllers/:控制器(包含HTTP路由)
- views/:视图和模版
- static/:web资源,包括css/, js/, img/, lib/, ...
- wizard/:向导及其视图
- report/:报表
- tests/:单元测试代码
文件命名
业务model放置在一个文件里,如果模块只包含一个model,它的名字就与模块名一致。如:
- models/<main_model>.py
- models/<inherited_main_model>.py
- views/<main_model>_templates.xml
- views/<main_model>_views.xml
- data/<main_model>_demo.xml
- data/<main_model>_data.xml
例如:销售模块包含sale_order和sale_order_line两个模型,并且sale_order是主模型,所以文件将被命名为models/sale_order.py和 views/sale_order_views.py。
对于数据文件命名,按用途进行命名:demo或者data。例如:data/sale_order_demo.xml
和data/sale_order_data.xml
每个模块的控制器都放在一个文件中,命名为main.py
。如果是从另一个模块继承的,则将其命名为<module_name>.py
。
统计报表命名:
- report/<report_name>_report.py
- report/<report_name>_report_views.py
可打印报表:
- report/<print_report_name>_reports.py
- report/<print_report_name>_templates.xml
addons/<module_name>/
|-- __init__.py
|-- __manifest__.py
|-- controllers/
| |-- __init__.py
| |-- <inherited_module_name>.py
| `-- main.py
|-- data/
| |-- <main_model>_data.xml
| `-- <inherited_main_model>_demo.xml
|-- models/
| |-- __init__.py
| |-- <main_model>.py
| `-- <inherited_main_model>.py
|-- report/
| |-- __init__.py
| |-- <main_stat_report_model>.py
| |-- <main_stat_report_model>_views.xml
| |-- <main_print_report>_reports.xml
| `-- <main_print_report>_templates.xml
|-- security/
| |-- ir.model.access.csv
| `-- <main_model>_security.xml
|-- static/
| |-- img/
| | |-- my_little_kitten.png
| | `-- troll.jpg
| |-- lib/
| | `-- external_lib/
| `-- src/
| |-- js/
| | `-- <my_module_name>.js
| |-- css/
| | `-- <my_module_name>.css
| |-- scss/
| | `-- <my_module_name>.scss
| `-- xml/
| `-- <my_module_name>.xml
|-- views/
| |-- <main_model>_templates.xml
| |-- <main_model>_views.xml
| |-- <inherited_main_model>_templates.xml
| `-- <inherited_main_model>_views.xml
`-- wizard/
|-- <main_transient_A>.py
|-- <main_transient_A>_views.xml
|-- <main_transient_B>.py
`-- <main_transient_B>_views.xml
XML文件
格式
当定义一个记录的xml时,需要<record>
标记:
id
属性放在model
属性前- 字段(
field
)定义中,name
属性放在第一个,其他属性根据重要性的高低顺序排放 - 字段的值放在
<field>
标签内,或者放在eval
属性中。 - 标签
<data>
仅用于设置不可更新的数据noupdate=1
,如果整个xml文件都是不可更新数据,则noupdate=1
属性可以设置在<odoo>
标签上,而不需要<data>
标签。
<record id="view_id" model="ir.ui.view">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<tree>
<field name="my_field_1"/>
<field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" />
</tree>
</field>
</record>
Odoo定义了一些标签作为快捷方式:
- menuitem:作为
ir.ui.menu
的快捷方式 - template: 表示只需要
arch
视图部分的QWeb视图 - report: 用于定义报表
action
- act_window:当record用不了的时候用它
xml_id命名
权限(Security)、视图(View)和动作(Action)使用的命名规则:
- 菜单(menu):
<model_name>_menu
- 视图(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.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用一个下划线方法来表明某字段需要翻译,该方法通过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方法
@api.model
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
- 用下划线+小写来命名
# 错误的写法
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开发规范的更多相关文章
- 我的公司培训讲义(1):.NET开发规范教程
这是1年多以前我在公司所做讲座的讲义,现在与园友们分享,欢迎拿去使用.一起讨论.文中有若干思考题,对园友们是小菜一碟.另有设计模式讲义一篇,随后发布.博文上了首页,感谢博客园团队推荐,也感谢所有园友的 ...
- 转载:移动web开发规范
本文来源:http://blog.csdn.net/joueu/article/details/44329825 以下是规范建议,均是日常在开发当中的的一些经验,仅供参考. 移动web开发规范 一.头 ...
- php与数据库代码开发规范
php与数据库代码开发规范 1/25/2016 6:00:31 PM php对各类变量命名规范 目录名 文件命名 局部变量命名 使用英文动词名词,用下划线作为单词的分割,所有字母均使用小写 目录 up ...
- 【敏捷开发】Android团队开发规范
这里说的开发规范分成目录规范,项目和包名的命名规范,类,方法,变量和常量的命名规范这几种. 目录规范 目录规范——在开发中整体文件夹组织结构. Requirement——需求文档文件夹 Design— ...
- 从零开始编写自己的C#框架(3)——开发规范
由于是业余时间编写,而且为了保证质量,对写出来的东西也会反复斟酌,所以每周只能更新两章左右,请大家谅解,也请大家耐心等待,谢谢大家的支持. 初学者应该怎样学习本系列内容呢?根据我自己的学习经验,一般直 ...
- 从零开始编写自己的C#框架(3)——开发规范(转)
由于是业余时间编写,而且为了保证质量,对写出来的东西也会反复斟酌,所以每周只能更新两章左右,请大家谅解,也请大家耐心等待,谢谢大家的支持. 初学者应该怎样学习本系列内容呢?根据我自己的学习经验,一般直 ...
- Web前端开发规范文档(google规范)
(Xee:其实没什么规范约束,但是养成一种好习惯,何乐而不为?) 区分大小写 xhtml 区分大小写,xhtml要求 标签名 属性名 值都要小写,并且要有双引号和 标签闭合. css 元素名称以及i ...
- 从" ThinkPHP 开发规范 "看 PHP 的命名规范和开发建议
稍稍水一篇博客,摘抄自Think PHP 的开发规范,很有引导性,我们可以将这些规范实践到原生 PHP 中. 命名规范 使用ThinkPHP开发的过程中应该尽量遵循下列命名规范: 类文件都是以.cla ...
- WEB前端开发规范
WEB前端开发规范 规范目的 为提高团队协作效率, 便于后台人员添加功能及前端后期优化维护, 输出高质量的文档, 特制订此文档.本文档如有不对或者不合适的地方请及时提出, 经讨论决定后方可更改. 基本 ...
随机推荐
- 【NX二次开发】指定矢量控件,记住上次选择的方向
block UI控件如果有RetainValue属性,就用这个属性.没有这个属性可以参考下面这种方法.以矢量控件为例: 1.在apply_cb回调中,将控件值保存到文本中 double TopForT ...
- GlusterFS更换Brick
故障环境还原 GlusterFS集群系统一共有4个节点,集群信息如下 # 分别在各个节点上配置hosts.同步好系统时间,关闭防火墙和selinux [root@glusterfs-master-8 ...
- 『无为则无心』Python基础 — 5、Python开发工具的安装与使用
目录 1.Pycharm下载 2.Pycharm安装 3.PyCharm界面介绍 4.基本使用 (1)新建Python项目 (2)编写Python代码 (3)执行代码查看结果 (4)设置PyCharm ...
- 如何回答面试中问到的Hibernate和MyBatis的区别
这边主要是写给那些准备去面试的(没什么经验的)应聘者看的,为了在面试中更好的回答这个问题,我做一个简单的梳理和总结. 作为一名职场新人,经历过多次的面试,由于在简历中提及了Hibernate和MyBa ...
- Ruby升级的最新方法/CocoaPods安装
今天安装cocoapods时候出现了下面的提示 Error installing pods:active support requires Ruby version >= 2.2.0//这个需求 ...
- JS里的异步实例化
JS里的异步构造函数 众所周知,Js的构造函数是不能加上async/await来实现异步实例化的,一般当需要一个对象的属性是异步的结果时可以这样写: //! 一个需要指定时间后返回的异步函数 func ...
- 3.QT屏幕分辨率适配
需求:qt的窗口.组件.字体需要适配屏幕分辨率. 思路:qt是根据显示器的物理长度或者宽度于分辨率的关系来计算dpi 实现: #if(QT_VERSION >= QT_VERSION_CHECK ...
- 26、linux下安装MongoDB
26.1.MongoDB介绍: 1.什么是MongoDB: MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统.在高负载的情况下,添加更多的节点,可以保证服务器性能. Mo ...
- React中使用react-file-viewer,实现预览office文件(pdf,word,xlsx等文件)前端实现
最近做一个项目要求在前端浏览器可以直接打开office文件(pdf,doc,xlsx等文件).pdf浏览器可以直接打开(可以直接用a标签href="文件地址"或者iframe标签s ...
- Vue 动态组件和异步组件
基础案例 动态组件切换类比"bilibili-个人中心"的横向菜单切换不同的标签页的功能. 在Vue中可以使用 component 标签,并加一个特殊的属性(attribute) ...