Odoo代码规范

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

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

odoo开发规范

本文介绍 Odoo 编码指南。其目的是提高 Odoo 应用程序代码的质量。事实上,正确的代码提高了可读性、简化维护、有助于调试、降低复杂性并提高可靠性。这些准则应该应用于每个新模块的开发和所有新开发。

警告

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

警告

修改master(主开发)版本中的现有文件时,仅可针对 revision(正在修订时)版本中的代码或大多数文件,将这些准则应用于现有代码。换句话说,仅当现有文件结构发生重大更改时,才修改它。在这种情况下,首先执行 move commit,然后才应用与其相关的更改。

模块结构说明

  • data/:演示数据
  • models/:模型
  • controllers/:控制器(含HTTP路由)
  • views/:视图和模板
  • security/:权限文件
  • static/:web资源,css/, js/, img/, lib/, ...
  • wizard/:向导及视图
  • report/:报表
  • tests/:单元测试
  • __manifest__.py:清单文件

命名规范

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

对于数据文件命名,按用途进行命名:demo或者data。例如:data/sale_order_demo.xmldata/sale_order_data.xml
每个模块的控制器都放在一个文件中,命名为main.py。如果是从另一个模块继承的,则将其命名为<module_name>.py

与模型不同,每个控制器类应包含在一个分离的文件中。例如,采购模块在  controllers/portal.py  中实现其客户门户的一部分,并在 controllers/purchase.py上实现其自身的典型路由。

对于静态文件,由于资源可以在不同的上下文中使用(前端、后端),因此它们将仅包含在一个捆绑包中。因此,CSS/Less、JavaScript 和 XML 文件应该捆绑一个其类型的后缀名称。

即:im_chat_common.css,用于"asset_common"捆绑的 im_chat_common.js,以及用于"asset_backend"捆绑的 im_chat_backend.js。如果模块只拥有一个文件,则约定将<module_name>.ext(如:project.js)。不要链接odoo外部资源数据(图像、库):不要使用图像的 URL,而应将其复制到我们的代码库中。

{
    'name': 'My Module',
    'version': '1.0',
    'depends': ['base'],
    'assets': {
        'web.assets_backend': [
            'my_backend.js',
            'my_backend.css',
        ],
        'web.assets_frontend': [
            'my_module_frontend.js',
            'my_module_frontend.css',
        ],
    },
    # ... 其他字段 ...
}

关于数据,按用途拆分它们:数据或演示。文件名将是主模型名称,后缀为 _data.xml 或 _demo.xml。

关于向导和瞬态模型,命名约定是 :

  • <main_transient>.py
  • <main_transient>_views.xml

其中<main_transient>是占主导地位的瞬态模型的名称,就像模型一样。<main_transient>.py 可以包含模型"transient_model.action"和"transient_model.action.line"。

统计报表命名:

  • <report_name_A>_report.py
  • <report_name_A>_report_views.py (一般提供 pivot 和 graph 视图)

可打印报表:

  • <print_report_name>_reports.py (报表  actions, 格式定义, …)
  • <print_report_name>_templates.xml (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属性中。最后将其他属性(widget、options...)按重要性排序。
  • 尝试按模型对记录进行分组。如果操作/菜单/视图之间存在依赖关系,则此约定可能不适用。
  • 标签<data>仅用于设置不可更新的数据noupdate=1,如果整个xml文件都是不可更新数据,则noupdate=1属性可以设置在<odoo>标签上,而不需要<data>标签。
  • priority 优先级 可通过计算调整不同场景下的视图显示。
<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, 或 子菜单 <model_name>_menu_do_stuff .
  • 视图(view): <model_name>_view_<view_type>,view_type可能的取值有:kanbanformtreesearch
  • 动作(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’ for the ‘model_name_group_user’, ‘public’ for public user 公共用户, ‘company’ for multi-company rules 多公司规则, …).
  • 所有的菜单项统一放在menu.xml文件中
  • 每个菜单项都需要配置权限组
  • 菜单项要设置sequence,sequence的间隔预留大一些,如10、20、30...
  • 每个模型都要配置search视图
  • 动作窗口要指定视图
<!-- 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>

注意

视图名称使用 . 连接关键语,my.model.view_type 或 my.model.view_type.inherit ,来标记ad of “This is the form view of My Model”.

继承XML的命名

继承视图的命名规则为

  • #. Extension 扩展模式:使用与要扩展的原始视图相同的 xml id,并使用 _inherit 后缀 :如 视图view project.project_view_form 被扩展 为 project_forecast.project_view_form_inherit.
  • #. Primary 主模式:保持原有 xml id。
<record id="module2.model_view_form_inherit" model="ir.ui.view">
    <field name="inherit_id" ref="module1.model_view_form"/>
    ...
</record>

<record id="module2.model_view_form" model="ir.ui.view">
    <field name="inherit_id" ref="module1.model_view_form"/>
    <field name="mode">primary</field>
    ...
</record>

权限文件编写

ir.model.access.csv文件:文件名固定

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_resource_manager,hr.employee.resource.manager,resource.model_resource_resource,hr.group_hr_manager,1,1,1,1
access_hr_resource_calendar_user,hr.employee.resource.calendar.user,resource.model_resource_calendar,hr.group_hr_user,1,1,1,1
access_hr_resource_calendar_attendance_user,hr.employee.resource.calendar.attendance.user,resource.model_resource_calendar_attendance,hr.group_hr_user,1,1,1,1
access_hr_contract_manager,hr.contract.manager,model_hr_contract,hr_contract.group_hr_contract_manager,1,1,1,1
  1. id:权限的ID  access_hr_contract_manager(access_+模型名+角色名)
  2. name:权限的name hr.contract.manager(模型+角色名)
  3. model_id:id;模型的ID model_hr_contract(model_+模型名)
  4. group_id:id;群组的ID hr_contract.group_hr_contract_manager(模型名+group+用户名)
  5. perm_read:查看权限
  6. perm_write:修改权限
  7. perm_create:增加数据权限
  8. perm_unlink:删除权限

清单文件编写

name: 模块名,以tx_开头

author:作者,固定为 广东同欣智能科技有限公司

website:网址,固定为 Home | 精益美业 (beauty-lean.com)

version:版本,固定为 当前odoo开发版本

summary:模块简述,尽可能简洁

description:模块描述,尽可能全面描述模块功能

maintainer:运维者,开发者名称

category:分类

Python

PEP8 选项

Odoo源代码基本准守Python标准PEP8,但是忽略其中一些规则:

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

import 顺序

  • 外部库
  • 导入odoo
  • 导入odoo模块

在每组中的导入按字母顺序排序

# 按需导入,无用的删除
# 1 : 导入python库
from base64 import b64encode
import re
from time import Time
from datetime import datetime
# 2 :  imports of 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 -*- 作为第一行。
  • 使用"""""给方法加注释及使用#给具体代码加注释
  • 简单易读的代码
  • 不要使用.clone()
# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)

Odoo中编程

  • 避免创建生成器和装饰器:仅使用Odoo API已有的
  • 使用filteredmappedsorted, … 方法来提升代码可读性和性能。
  • 使用更易理解的方法名
方法批量处理数据

当添加一个函数时,确保它可以处理多条数据,典型用法是通过 api.model 装饰器,可以在self上进行循环处理

@api.model
def my_method(self)
    for record in self:
        record.do_cool_stuff()

为了更好的性能,比如当定义一个状态按钮时,不在api.model 循环里用search和search_count方法,而用read_group一次计算

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)

Odoo 11是最后一个使用`api.multi()`装饰器的版本。从Odoo 12开始,`api.multi()`被弃用,并推荐使用`api.model`装饰器来代替。
`api.model`装饰器用于定义模型方法,这些方法可以在记录集上调用,而不是单个记录。在Odoo 12及以后的版本中,`api.model`成为处理多记录操作的标准方式。
在Odoo 12之前,`api.one()`和`api.multi()`被用来区分单记录和多记录操作。然而,从Odoo 12开始,`api.one()`也被弃用,并且`api.model`成为了处理模型方法的唯一方式,无论它们是单记录还是多记录操作。现在,方法是否作为单记录或多记录操作取决于它是如何被调用的,而不是它的装饰器。
因此,为了保持代码的现代性和兼容性,建议在Odoo 12及更高版本中使用`api.model`装饰器,并避免使用`api.multi()`和`api.one()`。

上下文环境

新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),))
扩展的思考

函数和方法不应包含太多的逻辑:使用大量小而简单的方法比使用少量大型和复杂的方法更可取。一条好的经验法则是,只要方法具有多个责任,就将其拆分(参考 http://en.wikipedia.org/wiki/Single_responsibility_principle)

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

# 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 调用开始时,打开一个新的数据库游标,并在调用return返回时commit,即在commit前先将结果返回至 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()  ,您很有可能会以各种方式破坏系统,因为您将导致部分提交,从而导致部分和不干净的回滚,从而导致问题:

  1. 业务数据不一致,通常是数据丢失
  2. 工作流取消同步,文档永久卡住
  3. 无法干净地回滚的测试,并且将开始污染数据库并触发错误(即使事务期间未发生错误也是如此)

所以请遵照以下非常简单的规则:

绝对不要自己调用 cr.commit()除非 您已显式创建自己的数据库游标!或者你有特殊的原因必须这样做。

如果您确实创建了自己的游标,则需要处理错误情况和适当的回滚,并在完成该游标后正确关闭游标。

与普遍的看法相反,在以下情况下,您甚至不需要调用cr.commit() :

  • 在模型的 _auto_init() 方法中。模型对象:这是由加载项初始化方法,或由 ORM 事务在创建自定义模型时处理
  •  在报表中: commit()  也由框架处理,因此您甚至可以在报表内更新数据库
  •  在模型中。瞬态方法:这些方法与常规模型完全一样。模型,在事务中,并在末尾使用相应的 cr.commit()/rollback()
  • 其它情况(如果您有疑问,请参阅上面的一般规则!

所以,服务器框架之外的所有 cr.commit()  调用都必须有一个明确的注释,解释它们为什么是绝对必要的,为什么它们确实是正确的,以及为什么它们不中断事务。否则,他们应该被删除!

正确的使用翻译方法

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):
    ...

变量名

  • 模型变量使用驼峰命名方式
  • 普通变量用下划线+小写字母
  • 如果变量包含记录 id 或 id 列表则将变量名称后缀为 _id 或 _ids。不要使用 partner_id 来包含一条 res.partner 的记录(_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_iduser_id

  • 方法命名

    • 计算字段 - 计算方法一般是_compute_<field_name>
    • 搜索方法 - _search_<field_name>
    • 默认方法 - _default_<field_name>
    • onchange方法 - _onchange_<field_name>
    • 约束方法 - _check_<constraint_name>
    • action方法 - 一个对象的动作方法一般以action_开头,它的装饰器是@api.model
  • 模型中属性顺序

    • 私有属性:_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.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方法
    def action_validate(self):
        self.ensure_one()
        ...

    # 其他业务方法
    def mail_user_confirm(self):
        ...
接口编写
@http.route('/tx_map/map_base_info/test', methods=['GET'], type='http', auth='public')
    def test(self):
        origin_url = request.httprequest.base_url
        base_url = origin_url[:origin_url.index(urlparse(origin_url).path)]
        return request.make_response(json.dumps({'url': base_url}), headers={'Content-Type': 'application/json'})
  • 路由定义:/模块名/模型名/具体方法

  • methods:指定请求类型,如GET、POST、PUT、DELETE...

  • type:路由类型

  • auth:认证方式,user为已登录的普通用户,public为公开无需登录,admin为管理员用户

  • 方法名与具体方法相同

Javascript和CSS

请按如下约定结构组织你的代码:

  • static: 所有一般静态资源文件
  • static/lib: 库文件,如: jquery  库可放在 addons/web/static/lib/jquery
  • static/src: 源文件
  • static/src/css
  • static/src/fonts
  • static/src/img
  • static/src/js
  • static/src/scss
  • static/src/xml: 所有qweb templates 文件
  • static/tests: 所有测试相关文件

Javascript 编码规范

  • 在所有javascript文件中使用use strict;
  • 不添加压缩javascript库
  • 类名使用驼峰命名
/** @odoo-module **/
import {someFunction} from './file_b';

export function otherFunction(val){
    return someFunction(val+3);
}
odoo.define('@web/file_a',function(require){
'use strict';
let __exports={};
const { someFunction }=require("@web/file_b");
__exports.otherFunction =function otherFunction(val){
    return someFunction(val+3):
}
return __exports;
)};

CSS 编码规范

  • 将所有的class命名为o_<module_name>,如o_form_view
  • 避免使用id
  • 使用bootstrap的class
  • 用下划线+小写来命名
.o_form_view{
    .o_iban {
        display: inline-flex;
        margin-left: -15px;
        margin-top: 5px;
    }
    .o_iban_fail {
        cursor: help;
    }
    .o_iban_input_with_validator {
        padding-right: 20px !important;
    }
}

1 ERP 简介 1 1.1 Odoo 历史 1 1.2 ERPⅡ 或商业智能化 2 1.2.1 什么是商业智能 4 2 Odoo 框架简介 7 2.1 python 模块分析 8 2.2 python2 还是 python3 8 3 Odoo 的安装和配置 9 3.1 PostgreSQL 数据库 10 3.2 Ubuntu14.04 下可能缺失的软件包 11 3.3 网页显示 node.js 方面 11 3.4 其他问题 12 3.5 通过命令行运行时的配置 12 3.5.1 –xmlrpc-port=8888 12 3.5.2 –addons-path=addons 12 3.5.3 数据库的一些配置 13 3.5.4 –save 13 3.6 将安装环境封装起来 13 3.7 文档编译 14 4 初入 Odoo 17 4.1 管理数据库 17 4.2 登录界面 18 4.3 Administrator 首选项 19 4.4 导入一个翻译 20 4.5 新的 Demo 用户 20 4.6 模块管理 21 4.7 修改公司信息 21 4.8 打开技术特性支持之后 22 4.9 进销存和财务系统的抽象讨论 22 4.9.1 以采购部门为例 23 4.10 安装和配置模块 24 5 创建自己的模块 27 5.1 快速生成模块骨架 27 5.1.1 python 模块的 init 文件 28 5.1.2 作为 Odoo 模块的说明文件 29 5.2 安装自定义模块 32 5.2.1 模块文件夹管理 32 5.3 一个简单的演示模块 33 5.3.1 controllers 33 5.3.2 views 33 5.3.3 models 35 5.3.4 security 37 5.3.5 美化网页 38 5.4 加分项: 通过 pgadmin3 来查看数据库 39 5.4.1 安装 39 5.4.2 连接服务器 39 5.4.3 图形化查询 40 6 Odoo 开发基础: 请假模块第一谈 43 6.1 纯理论讨论 43 6.2 定义模型 45 6.3 加入菜单 46 6.3.1 act_window 的属性 48 6.3.2 menuitem 的属性 48 6.4 视图优化 48 6.4.1 修改 tree 视图 49 6.4.2 修改 form 视图 49 6.5 完整的 views.xml 51 6.6 给模块加个图标 53 7 Odoo 开发基础: 工作计划模块第一谈 55 7.1 数据访问权限管理 61 7.1.1 access rule 62 7.1.2 record rule 62 8 扩展现有模块-继承机制 65 8.1 给模块增加 field 65 8.2 修改已有的 field 66 8.3 重载原模型的方法 66 8.3.1 什么是 Recordset 67 8.3.2 Odoo 里面的 domain 语法 68 8.3.3 recordset 的 search 方法 69 8.4 视图 xml 文件的继承式修改 70 8.4.1 视图元素添加 71 8.4.2 原视图元素属性修改 71 8.5 多态继承 72 8.6 修改其他数据文件 73 8.6.1 删除记录 73 8.6.2 更新数据 73 8.7 委托继承 74 9 理解模型内的数据文件 75 9.1 理解外部 id 75 9.2 使用外部 id 77 9.3 导出或导入数据文件 77 9.4 快捷输入标签 78 9.5 用 field 标签设置值 78 9.5.1 eval 语法 78 9.5.2 ref 属性 79 9.5.3 One2many 和 Many2many 的 eval 赋值 79 10 Odoo 开发基础: 请假模块第二谈 81 10.1 本例涉及到的数据库表格简介 89 10.2 工作流概念入门 89 10.2.1 定义工作流对象 90 10.2.2 创建节点 91 10.2.3 创建连接 91 11 Odoo 模型层详解 93 11.1 _name 93 11.2 各个表头属性 93 11.3 name 字段 94 11.4 具体模型的数据 94 11.5 模型间的关系 95 11.6 工作流 95 12 Odoo 视图层详解 97 13 附录 99 13.1 Odoo 里老的 API 99 13.2 PostgreSQL 数据库命令行操作 99 13.2.1 命令行数据库备份 99 13.3 反向代理 (reverse proxy) 99 13.3.1 安装 ngnix 软件 100 13.3.2 强制 https 连接 102 13.3.3 nginx 优化 102 13.3.4 轮询机制 102 13.4 跟踪项目源码初始化进程 102 13.4.1 base 模块 104 13.4.2 web 模块 105 13.4.3 web_kanban 模块 105 13.5 配置会计科目 105 13.5.1 配置会计科目类型 105 13.5.2 配置会计科目 106 13.6 分录 106 13.7 新建业务伙伴 106 13.7.1 新建业务伙伴标签 106 13.7.2 新建客户 106 13.8 创建新的产品 107 13.9 设置会计年度 107 13.10向供应商下单 107 13.11会计学入门 107 13.11.1财务报表 108 13.11.2原始凭证 108 13.11.3账户 108 13.11.4分类帐 109 13.11.5会计科目表 109 13.11.6报告期间 110 13.12参考资料 110
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值