Odoo代码规范

作为全球第一开源ERP,开源让您根据本企业业务特性,灵活定制,实现动态IT系统,可随业务变化优化ERP,进而优化管理运营。

对中大型企业,odoo须开发才可落地,良好开发规范,有助于团队协助,敏捷开发上线。

odoo开发规范

Odoo 编码指南。Odoo 代码质量提高可读性、简化维护、有助于调试、降低复杂性并可靠性。应该应用于每个新模块开发和所有新开发。

警告
stable(稳定)版中修改现有文件,原始文件样式将严格取代任何其他样式准则。不要修改odoo正式发布的已有文件或代码,以用这些准则。避免中断代码行的修订历史记录。差异应保持最小。详细信息,参阅odoo官方 pull request guide指南。

修改master(主开发)版本中现有文件,仅针对 revision(正在修订)版代码或大数文件,将准则应用于现有代码。仅当现有文件结构发生重大更改时,才修改。首先执行 move commit,才应用与其相关更改。

模块结构说明

  • 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代码准守PEP8,但忽略一些规则:

  • E501:行太长
  • E301:预计有1个空行,找到0
  • E302:预计有2个空行,找到1
  • E126:延长线过度缩进用于悬挂缩进
  • E123:关闭支架与开口支架线的压痕不匹配
  • E127:延伸线过度缩进以进行视觉缩进
  • E128:用于视觉缩进延续线
  • E265:阻评论应以'#'开头

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用下划线方法表明某字段需翻译,通过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一样
  • 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 开发技术栈,成为高级odoo开发基本技能要求,开发框架
  • 用pycharm搭建odoo 17,16,15,14, 13,12, 11,10 开发调试环境,下载绿色版64位odoo更快更简单
  • odoo17 免费常用及高级widget大全,社区及企业版共计100+
  • odoo17,16,15,14,13,12,11,10 实现多级树状视图,前端开发实例教程,引入ztree.js
  • odoo官方开发教程,狠狠看,狠狠写代码!
  • odoo敏捷开发-代码生成器一键生成模块

联系方式

手机:13822161573 微信:txsolarterms QQ:419396409

  • 48
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值