第一章 概念总论

篇别:第一篇 基础知识

本章学习目标

  • 能说明 Odoo 模块、模型、Mixin 的区别与典型用途。
  • 能读懂并编写简单的 domain、context,并理解视图修饰与权限的关系。
  • 能列举 向导、定时任务、Discuss 相关模型 在业务中的常见用法。
  • 能独立维护一份合理的 __manifest__.py,并解释 Odoo 19 架构层面的主要变化。

本章对 Odoo 19 的核心概念进行全景式梳理,帮助开发者建立系统化的认知框架。


1.1 模块(Module)概念

1.1.1 知识要点

Odoo 功能以 模块 为单位分发。每个模块是一个 Python 包,根目录至少包含:

  • __manifest__.py:模块清单(元数据、依赖、数据文件列表等);
  • __init__.py:包初始化,通常 from . import models 等。

模型类型速查

类型 基类 是否建表 典型用途
普通模型 models.Model 是(默认) 订单、产品、客户等业务实体
抽象模型 models.AbstractModel + _abstract = True 字段/方法复用,不被直接实例化
临时模型 models.TransientModel 是(临时表,会清理) 向导、一次性配置
Mixin 多为抽象模型,通过 _inherit 拼到业务模型上 否(Mixin 自身) 消息、活动、阶段条等可插拔能力

历史上文档中的 BaseModel 泛指模型基类能力;NumericBrowse 等旧称可忽略,以 models.Model 与记录集 API 为准。

1.1.2 案例

案例 A:最小业务模块目录

my_library/
  __init__.py
  __manifest__.py
  models/
    __init__.py
    book.py          # models.Model → 图书记录,持久化
  wizards/
    borrow_wizard.py # models.TransientModel → 借书向导

案例 B:Mixin 的典型组合

class LibraryLoan(models.Model):
    _name = 'library.loan'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    # 获得 chatter、关注者、待办活动,而无需复制 mail 模块源码

1.1.3 截图占位

图 1-1 自定义模块在文件系统中的标准目录结构(展开 models、security、views)

拍摄说明:在 IDE 或文件管理器中展开一个真实自定义模块根目录。

1.1.4 本节练习

  1. 判断题TransientModel 的数据会永久保留在同一数据库中供审计使用。( )
  2. 简答题:说明 AbstractModel 与「用 _inherit 拼到 res.partner 上的 mail.thread」在「是否单独建表」上的区别。
  3. 实操题:新建模块 demo_concept,内含一个持久化模型 demo.concept.item(仅 name 字段)与一个向导模型,写出 __init__.pymodels 包的最小导入链。

参考答案提示:1. 错(会定期清理)。2. AbstractModel 不建业务表;mail.thread 通过继承扩展已有表结构。3. 见 1.1.2 目录结构与第二章模型定义。


1.2 表达式与配置

1.2.1 知识要点

  • domain:列表形式的查询条件。示例:[('state', '=', 'draft')];前缀 &|! 组合逻辑。
  • context:字典,影响默认值、语言、公司、active_id 等;代码中用 record.with_context(lang='zh_CN', default_type='x')
  • attrs / states:旧式表单中控制 invisiblereadonlyrequired;新版本逐步由 OWL + 视图属性 承接,维护老模块时仍会见到。
  • groups:字段、按钮、菜单上的 可见性 门槛,值为用户组 XML ID。
  • 视图层 invisible/readonly/required:仅影响界面时,不能替代 ACL;RPC 仍可能读写,须后端约束与权限配合。

1.2.2 案例

案例 A:动作里带 domain 与 context

<record id="action_loan_draft" model="ir.actions.act_window">
    <field name="name">草稿借阅单</field>
    <field name="res_model">library.loan</field>
    <field name="domain">[('state','=','draft')]</field>
    <field name="context">{'default_state': 'draft', 'search_default_my': 1}</field>
</record>

案例 B:Python 中临时切换上下文

records.with_context(active_test=False).search([])
# 常见:需要包含 archive 的记录时

1.2.3 截图占位

图 1-2 开发者模式下某窗口动作的 domain 与 context 字段(设置 → 技术 → 动作)

1.2.4 本节练习

  1. 填空:domain 中「与」的前缀操作符是 __
  2. 改错题:「菜单设置了 groups,所以普通用户绝不可能通过 RPC 访问该模型。」错在哪里?
  3. 实操题:写一个 domain,表示「状态为 donecancel,且 partner_id 为当前表单上的 partner_id」(提示:用变量时需在 Python 里拼 domain,XML 里用 user_id 等安全表达式)。

参考答案提示:1. &。2. 菜单只藏入口,ACL/记录规则才是模型访问边界。3. 如 [('state', 'in', ['done', 'cancel']), ('partner_id', '=', partner_id)](在方法内组装)。


1.3 核心组件

1.3.1 知识要点

  • WizardTransientModel + 表单视图,完成导入、确认、批量操作等 一次性 流程。
  • Mixin:可复用模型片段(消息线程、活动、评级等)。
  • Cronir.cron 按间隔调用模型方法,适合同步、清理、提醒。
  • mail.thread / mail.activity.mixin / mail.followers:Discuss 体系的核心;关注者决定 谁收到通知

1.3.2 案例

案例 A:向导返回窗口动作

def action_apply(self):
    self.ensure_one()
    # ... 业务处理 ...
    return {
        'type': 'ir.actions.act_window',
        'res_model': 'library.loan',
        'view_mode': 'list,form',
        'domain': [('id', 'in', self.loan_ids.ids)],
    }

案例 B:Cron 调用模型方法

在「设置 → 技术 → 自动化 → 已计划的动作」中,将 模型 设为 library.loan方法 设为 cron_send_due_reminder(需在模型上实现 @api.model 方法)。

1.3.3 截图占位

图 1-3 ir.cron 表单:模型、方法、间隔、用户

1.3.4 本节练习

  1. 选择题:以下哪类最适合「用户点菜单填表 → 生成正式业务单据」?(A)Model (B)TransientModel (C)AbstractModel
  2. 简答题:说明 mail.threadmail.activity.mixin 各解决什么用户可见问题。
  3. 实操题:给某业务模型加上 mail.thread,并在表单视图加入 message_ids 的 chatter 区(可照抄标准 sale/order 视图结构)。

参考答案提示:1. B。2. thread 侧重消息流与跟踪;activity 侧重待办与截止日期。3. 参考 mail 模块 form 中的 div class="oe_chatter"


1.4 底层机制

1.4.1 知识要点

  • ir.actions.*:动作在数据库中注册;菜单、按钮、绑定动作都通过 action idxml id 引用。
  • 注册表(Registry):启动时加载模型、视图、访问 rights;二次开发以 声明式 XML/Python 为主。
  • 旧资料中的 fields_view_get 等内部入口,在新版中由 视图引擎与 ORM 替代;迁移以 官方 ORM Changelog 为准。

1.4.2 案例

案例 A:按钮打开已定义动作

<button name="%(my_module.action_book_list)d" type="action" string="全部图书"/>

案例 B:Python 中触发动作

action = self.env.ref('my_module.action_book_list').read()[0]
return action

1.4.3 截图占位

图 1-4 技术菜单下「窗口动作」列表与某条记录的 res_model、view_mode

1.4.4 本节练习

  1. 简答题ir.actions.act_windowres_model 的作用是什么?
  2. 实操题:在 XML 中定义 ir.actions.act_windowir.ui.menu,使用 ref 将菜单绑定到该动作。

参考答案提示:1. 指定打开的业务模型。2. 见第九章动作与第十章菜单案例。


1.5 Odoo 19 架构概览(新增)

1.5.1 知识要点

  • Python 版本:以官方 19.0 安装文档为准;升级项目时同步 Docker/CI 镜像 与依赖。
  • PEP 420 命名空间:包布局更规范;自定义模块包名 勿与标准库、odoo 子包冲突。
  • WebClient:浏览器端 OWL 应用;与后端通过 JSON-RPC 等 交互。
  • Assets:静态资源按 bundle 组织,支持懒加载与缓存指纹。

1.5.2 案例

案例:在 manifest 声明一段后端专用脚本(示例)

# __manifest__.py 片段
'assets': {
    'web.assets_backend': [
        'my_module/static/src/js/**/*.js',
        'my_module/static/src/scss/**/*.scss',
    ],
},

1.5.3 截图占位

图 1-5 浏览器开发者工具 Network:某次 JSON-RPC 请求与响应概要

1.5.4 本节练习

  1. 简答题:为何生产环境不建议长期开启 --dev=all
  2. 实操题:列出你当前环境 python -V 与 Odoo 文档要求的版本是否一致,并记录一项需升级的依赖。

参考答案提示:1. 暴露调试信息、资产未合并影响性能与安全。2. 以本机输出为准。


1.6 模块清单(Manifest)详解(新增)

1.6.1 知识要点

__manifest__.py 常用键:

作用
name / version 显示名与版本号
depends 依赖列表,决定加载顺序
data / demo 安装/演示数据文件
assets 前端资源
installable 是否出现在应用列表
application 是否作为应用根展示
license 许可证

依赖:禁止循环依赖;最小依赖 便于复用。生命周期:安装 → -u 升级 → 卸载;升级会重载视图与执行迁移。

1.6.2 案例

案例 A:最小可安装 manifest

{
    'name': 'My Library',
    'version': '19.0.1.0.0',
    'depends': ['base'],
    'data': ['security/ir.model.access.csv', 'views/book_views.xml'],
    'installable': True,
    'license': 'LGPL-3',
}

案例 B:错误依赖的后果

声明 depends: ['sale'] 却仅用 base 模型 → 模块过重、安装慢;未声明 mail_inherit mail.thread → 安装报错或运行期缺表。

1.6.3 截图占位

图 1-6 应用界面中模块卡片(名称、版本、作者来自 manifest)

1.6.4 本节练习

  1. 判断题demo 列表中的文件在生产库升级时一定会被执行。( )
  2. 填空:控制模块是否在应用列表出现的键是 __
  3. 实操题:为你自己的模块补充 application: Truecategory,在应用列表中观察分类变化。

参考答案提示:1. 错(仅安装 demo 时或显式加载策略下,依版本与配置而定,生产勿依赖 demo)。2. installable(或同时 application 控制是否根应用)。3. 以界面为准。


本章综合练习

  1. 画一张示意图:用户点击菜单 → ir.ui.menu → ir.actions.act_window → ir.ui.view → models.Model,标注数据方向。
  2. 列举三项:仅改视图 invisible 无法解决的安全问题。
  3. 拓展:阅读官方 ORM Changelog,摘录一条与「环境 env」相关的变更到学习笔记。

本章对应白皮书目录:第一章 概念总论。