第四十六章 高级开发
篇别:第十四篇 高级主题
本章学习目标
- 在 多公司 环境下正确配置 记录规则、默认值、报表上下文,并能解释 切换公司 对
search结果 的影响。 - 使用
Monetary、汇率、公司币 处理 多货币单据与报表,区分 交易折算与期末重估 的职责边界。 - 了解 自定义字段(含 Studio 动态字段) 的 生命周期、导出与 Git 协作 风险。
- 掌握 Studio 的 适用场景与禁区,能将 导出模块 纳入 受控分支。
- 了解 Odoo Spreadsheet 与 Pivot/列表数据 的 一致性校验 思路。
- 能按 官方文档索引 阅读
account、payment等标准模块 的 状态机与扩展点。 - 使用
website模块 完成 路由、QWeb 页面、SEO 元数据 的 最小站点页。 - 了解 移动端浏览器访问 时的 交互优化与条码等能力边界。
- 能在 报表/PDF 或 独立接口 中嵌入 二维码(内容规范、纠错与依赖)。
- 了解 Odoo Sign(电子签名) 的 模板—请求—签署 链路与 自定义扩展点。
- 掌握 PDF/打印物水印 的 常见实现路径(引擎差异与合规)。
- 能通过 按钮/动作 触发 Word(.docx) 的 服务端生成与下载(依赖与权限)。
导读:从「会用模块」到「敢改核心」
前各章已覆盖 ORM、视图、安全、前端、API、部署。本章把 多公司/多货币(与 第四十五章 呼应但侧重 产品功能与 Studio/网站)、低代码与电子表格、标准模块阅读法、网站与移动端 串成 高级主题闭环;46.9~46.12 补充 二维码、电子签名、水印、Word 输出 等 交付物常见需求。实施时务必 对照企业版许可与官方文档:Studio、Spreadsheet、Sign、部分网站/电商能力 可能 因版本与订阅而异。法律效力 以 当地法规与审计要求 为准,技术实现不能替代法务评审。
46.1 多公司
46.1.1 知识要点
- 用户与公司:用户可关联 多个
res.company;界面 当前公司 影响 默认值、company_id隐式过滤、部分报表范围。 - 记录归属:业务表常见
company_id/company_ids;共享主数据(产品、伙伴)常company_id为空 或 多公司规则 显式允许。 - 记录规则:
ir.rule中company_ids元组 与('company_id', 'in', [...])组合;错误规则 可导致 「切换公司后数据消失」 或 串库。 with_company/sudo:代码中 显式切换 计算上下文;滥用sudo可 绕过多公司隔离(第四十五章、第三十章)。- 测试:同一用户、不同当前公司 各执行一次
search_count,断言差异符合产品说明。
46.1.2 案例
记录规则(示意):library.loan 仅本公司:
<record id="library_loan_rule_company" model="ir.rule">
<field name="name">借阅记录:多公司</field>
<field name="model_id" ref="model_library_loan"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
(model_library_loan XML ID 需与贵模块一致;domain 以业务为准。)
实操对比:设置 → 用户 勾选 两家公司 → 切换公司菜单 → 同一列表菜单 的 search_count 截图对比。
46.1.3 截图占位

46.1.4 本节练习
- 实操:编写
library.loan记录规则:用户仅见本公司 借阅单(测试用户 两家公司 各验一次)。 - 简答:
company_id为空 的图书记录在 多公司规则 下通常表示什么? - 判断:切换公司 只影响前端显示,不改变
env.company。( )
参考答案提示:2. 全局共享 或 待分配(依产品定义)。3. 错;当前公司 会 变,影响 默认与部分 domain。
46.2 多货币
46.2.1 知识要点
- 字段:
Monetary+currency_field表达 单据币种金额;公司本位币 多为company_id.currency_id。 - 汇率:
res.currency.rate按日期;历史凭证 必须用 过账日/业务日 对应汇率,勿用今日汇率重算去年账。 - 日常交易:销售/采购 外币价 → 按规则折算 应收账款/应付账款 与 公司币报表列。
- 期末重估:未结外币余额 按 期末汇率 调整;与日常折算分工不同,一般由 会计模块 提供 向导/分录。
- 自定义模块:避免 手写 浮点乘汇率 后 直接比较相等;优先 复用会计工具方法 或
Decimal策略(与团队规范一致)。
46.2.2 案例
报表列「公司币金额」:amount_currency(外币)+ balance(公司币) 在 account.move.line 中的分工(阅读标准模块字段说明)。
46.2.3 截图占位

46.2.4 本节练习
- 简答:期末重估 与 日常交易折算 的分工?
- 实操:在 测试库 创建 外币价目 与 一张销售单,观察 公司币列 与 汇率日期(若已装 sale)。
- 判断:所有业务模型 都应自建
amount_company浮点字段 存公司币。( )
参考答案提示:1. 日常 按交易日汇率;期末 未结余额重估。3. 错;应跟会计/标准模式。
46.3 自定义字段
46.3.1 知识要点
- Studio 动态字段:元数据在
ir.model.fields;导出为模块 后变为 普通 XML/Python,可入 Git。 - 手写 vs Studio:同一模型 混用时需 统一负责人,避免 同义字段重复(
x_studio_*与自定义名)。 - 升级:Studio 导出模块 升级顺序 与
depends;生产 上 Studio 直接改 需变更流程(禁止无评审直接改生产)。 - 卸载:删除字段可能 丢数据;先做备份与迁移脚本。
46.3.2 案例
合并维护策略:Studio 仅用于原型 → 导出 studio_customization → 重命名/重构 → 后续只改 Git 模块,关闭生产 Studio 随意改(策略示例)。
46.3.3 截图占位

46.3.4 本节练习
- 简答:Studio 导出模块 与 纯手写模块 如何 合并维护?
- 判断:删除 Studio 字段后 PostgreSQL 列立刻无痕迹。( )
- 实操:在 测试库 添加 自定义 Char 字段 → 导出 → 对比
ir.model.fields与 XML。
参考答案提示:2. 不一定;可能保留列 直至 手动迁移。
46.4 Studio
46.4.1 知识要点
- 适合:快速原型、非关键扩展、顾问现场配置。
- 不适合:复杂状态机、强合规审计、需高测试覆盖 的核心逻辑——应 下沉淀为 Python 模块。
- 权限:Studio 管理员 接近 结构改库能力;生产 应 收紧组。
- 导出:自定义视图/字段/自动化 打包为模块;diff 审查 后再合并。
46.4.2 案例
清单:导出模块内 views/、data/、security/ 各打开 一文件,标出 可删的临时 xpath。
46.4.3 截图占位

46.4.4 本节练习
- 实操:用 Studio 加字段 并 导出
custom_module纳入 Git(测试库)。 - 简答:两条 不建议用 Studio 做 的事?
- 综合:Studio 改的
sale.order视图 与 手写继承 冲突时 谁优先?如何查?
参考答案提示:3. priority 与加载顺序;设置 → 技术 → 视图 看 继承链。
46.5 Odoo Spreadsheet(新增)
46.5.1 知识要点
- 定位:自助分析、管理层看板;与 Pivot 视图 共享 部分数据理念(以版本文档为准)。
- 一致性:同一 domain、同一公司上下文 下 Spreadsheet 汇总 应与 Pivot 可对账;差异 多来自 时区、归档过滤、缓存。
- 权限:电子表格内数据 仍受 ORM 权限 约束;分享链接 需 合规评审。
- 扩展:数据源插件 / 公式集 随版本变化;定制前读官方 Spreadsheet 开发说明。
46.5.2 案例
校验脚本思路:导出 Pivot CSV 与 Spreadsheet 同条件单元格 对比(允许小额舍入差)。
46.5.3 截图占位

46.5.4 本节练习
- 案例:用表格做 「按类别图书库存」 并与 Pivot 一致性校验(记录 一条 差异原因假设)。
- 简答:Spreadsheet 适合 与 不适合 作为 唯一真相源 的场景?
参考答案提示:2. 探索适合;过账与法务 以 业务单据/会计为准。
46.6 标准模块参考(新增)
46.6.1 知识要点
- 阅读法:模型
_name→ 状态字段 →def action_*/button_*→ 报表与约束。 account.move:state(draft/posted/cancel 等)、过账方法、与account.move.line关系。payment:支付提供商、token、回调 与 第三十八章 衔接。- 文档:官方每个应用一篇 Application/Accounting;源码
addons/account为 最终真相。
46.6.2 案例
笔记模板:模型名|关键状态|不可逆操作|常用扩展点(_inherit 钩子)。
46.6.3 截图占位

46.6.4 本节练习
- 拓展:阅读
account.move的state流转 相关 方法名列表(笔记 5 条)。 - 简答:为何扩展 支付 时 应优先读
paymentprovider 架构 而非 只 copy 控制器?
参考答案提示:2. 安全、回调验签、多提供商抽象 已在核心中处理。
46.7 网站功能开发(新增)
46.7.1 知识要点
- 模块:
website;页面多为 QWeb 模板 + Controller 路由。 - 路由:
@http.routetype='http'website=True;SEO slug 与 多语言hreflang(进阶)。 - SEO:
<meta name="description">、标题、canonical;sitemap 由 网站配置 生成(以版本为准)。 - 电商:
website_sale扩展点多;与库存、定价、多公司 组合需 整体验收。
46.7.2 案例
from odoo import http
from odoo.http import request
class LibraryWebsite(http.Controller):
@http.route("/library/hours", type="http", auth="public", website=True)
def hours_page(self, **kw):
return request.render("my_library.library_opening_hours", {})
QWeb 页 内设置 <t t-call="website.layout"> 与 meta(以官方网站开发文档为准)。
46.7.3 截图占位

46.7.4 本节练习
- 实操:发布 「图书馆开放时间」 静态信息页并设 slug。
- 简答:Portal 与 纯 public 页面 在 auth 上的典型差异?
- 判断:网站页 无需 考虑 多公司路由。( )
参考答案提示:3. 错;定价、库存、可见分类 可能 跟公司。
46.8 移动端开发(新增)
46.8.1 知识要点
- 形态:响应式 Web 为主;专用 App 多为 WebView / 品牌包装(以 Odoo 移动策略为准)。
- 交互:大触控目标、减少横向滚动、表单分步;列表虚拟滚动 由 web 模块处理,自定义勿破坏。
- 条码:部分浏览器 API 需 HTTPS;硬件扫描枪 常 模拟键盘输入,与 IoT 不同。
- 离线:有限;关键业务 勿假设离线可用。
46.8.2 案例
条码(思路):<input> 聚焦 + 快速回车提交 适配 激光枪;摄像头扫码 用 浏览器 Media API(需权限与兼容矩阵)。
46.8.3 截图占位

46.8.4 本节练习
- 简答:移动端优先优化哪 三类 交互?
- 实操:用 Chrome 设备模拟 测 表单「保存」按钮 是否 拇指可达(记录 一条 改进)。
- 拓展:Mobile JavaScript API(若文档提供)与 桌面 web client 的 差异 一句话。
参考答案提示:1. 触控、网络弱、可视区域小。
46.9 二维码生成(说明与实现)
46.9.1 知识要点
- 用途:URL、单据号、访客登记、支付参数 等 快速录入;与 一维条码(EAN、Code128)分工不同——二维码信息密度高,适合 短 URL 或结构化字符串。
- Odoo 中的位置:QWeb 报表 内嵌 PNG/SVG;或 Controller/字段 返回
data:image/png;base64,...;库存条码 多与stock/硬件 联动,QR 常 自管生成逻辑。 - 依赖:常用
qrcode+Pillow(requirements.txt 声明);勿 在 未固定版本 的生产环境 隐式依赖。 - 安全:QR 内容若为 URL,须 HTTPS、防短链劫持;敏感令牌 不宜 长期印在 公开报表(可撤销、短时有效 或 内部扫码)。
- 打印质量:纠错级别(如
ERROR_CORRECT_M)、模块大小 与 打印机 DPI 匹配,避免 现场扫不出。
46.9.2 案例(服务端生成 + 报表展示)
模型方法(示意:为图书记录生成 借阅查询链接 的 QR 图):
import base64
import io
import qrcode
def _library_qr_png_b64(self, url: str) -> str:
buf = io.BytesIO()
qrcode.make(url, box_size=4, border=2).save(buf, format="PNG")
return base64.b64encode(buf.getvalue()).decode()
QWeb(片段):<img t-att-src="'data:image/png;base64,' + doc.qr_b64"/>(qr_b64 用计算字段或 report 上下文传入)。
表单按钮:ir.actions.server 或 object 方法 打开 带 QR 的报表 ir.actions.report(与 第十三章 QWeb 报表衔接)。
46.9.3 截图占位

46.9.4 本节练习
- 简答:二维码内容 为 内部 API 密钥 时 至少两条 风险?
- 实操:在 测试模块 为 一条业务记录 生成 含
https://测试页 的 QR 报表 PDF。 - 判断:所有条码场景 都应使用 QR。( )
参考答案提示:1. 泄露、不可吊销、打印扩散。3. 错;一维码 在 零售扫描 仍常见。
46.10 电子签名(Sign)功能与实现思路
46.10.1 知识要点
- 产品:
sign应用(多为 企业版)提供 PDF 模板、签名字段、签署顺序、邮件邀请、证书/审计轨迹(以版本文档为准)。 - 数据流:
sign.template定义版式 →sign.request发起 → 签署人 Portal/邮件链接 → 状态机(signed/refused/canceled 等)→ 归档 PDF。 - 扩展:自定义字段类型、与业务单据关联(
res_model/res_id或 在sign.request上增加 Many2one)、签署完成后message_post/ 回写业务state(@api.model_create_multi/write约束 防绕过)。 - 合规:电子签名法 因地而异;技术要点 包括 身份验证、意愿表达、防篡改、可追溯——需法务与实施共同定稿,勿仅依赖「勾选项」。
- 自研替代:无 Sign 许可时,外接 DocuSign/法大大 等 Webhook + 附件回写 是常见模式(与 第三十八章 Webhook 衔接)。
46.10.2 案例(签署完成回写业务)
思路:继承 sign.request(或 监听消息/自动化),在 state == signed(字段名以源码为准)时 record.sudo().write({'signed': True}) ——sudo 须再校验 发起人权限 与 单号匹配。
46.10.3 截图占位

46.10.4 本节练习
- 简答:Sign 模板 与 普通 QWeb PDF 报表 的 本质差异(一条)?
- 判断:电子签名 等价于 用户在表单点了「确认」按钮。( )
- 综合:列出 签署完成后 应写入 chatter 的 两条 审计信息。
参考答案提示:1. Sign 含 法律意义上的签署流程与证据链(产品化)。2. 错。
46.11 水印功能(说明与实现)
46.11.1 知识要点
- 目的:「草稿」「机密」「样张」 等 视觉提示;不能 单独作为 防泄密 手段(PDF 仍可被提取文本)。
- 实现路径(择一或组合):
- wkhtmltopdf / Chromium PDF:CSS
background-image/@page水印层(矢量文字 可用 绝对定位opacity)。 - 后处理:PyPDF / pypdf 合并「仅水印页」或 每页叠加 透明 PNG(注意性能与大 PDF)。
- ReportLab:Canvas 旋转平铺
drawString(适合 完全代码生成的 PDF)。
- wkhtmltopdf / Chromium PDF:CSS
- Odoo 接入点:继承
ir.actions.report._render_qweb_pdf(或 版本对应的 hook)在 返回bytes前 调 水印函数;仅对特定报表report_name判断,避免 全站变慢。 - 多语言/多公司:水印文案 翻译 与
company_id标识 常需 动态传入。 - 性能:大批量打印 时 后处理 可能 倍增耗时;优先 在 渲染阶段 带水印 或异步 job。
46.11.2 案例(伪代码:后处理叠水印)
def _apply_watermark_pdf(pdf_bytes: bytes, text: str) -> bytes:
# 使用 pypdf 等将带透明度的水印 Form XObject 合并到每一页(示意)
# 具体 API 以所选库文档为准
return pdf_bytes
调用点:自定义 report 的 _render_qweb_pdf 末尾 return self._apply_watermark_pdf(super()._render_qweb_pdf(...), _("草稿"))。
46.11.3 截图占位

46.11.4 本节练习
- 简答:为何 水印 不能 当作 唯一保密措施?
- 实操:选定 一种 PDF 栈(wkhtmltopdf 或 pypdf),在 测试报表 上 叠一层半透明文字。
- 判断:所有报表 统一 加水印 不影响 性能。( )
参考答案提示:1. 可复制文本/截图。3. 错;CPU/IO 与 体积 可能上升。
46.12 点击自动生成 Word(.docx)实现
46.12.1 知识要点
- 依赖:
python-docx(Apache 2.0)常见;复杂版式 可用 模板.docx+docxtpl(Jinja)(额外依赖)。 - 权限:下载 走
ir.actions.act_url或 HTTPContent-Disposition: attachment;auth='user'且check_access业务记录。 - 流程:按钮 → object 方法 生成
BytesIO.docx →base64.b64encode存 TransientModel 向导 字段 或 直接request.make_response(Controller 更利于 大文件流式)。 - 与报表分工:QWeb → PDF 适合 版式固定、打印;Word 适合 客户再编辑、法务改稿——勿 用 docx 冒充 正式归档 PDF(除非流程规定)。
- 安全:动态段落 若含 用户 HTML,须 strip_tags / 白名单,防 XXE(解析 XML 时 使用 安全解析器配置)。
46.12.2 案例(按钮 + 临时下载)
import io
from docx import Document
from odoo import models
class LibraryBook(models.Model):
_inherit = "library.book"
def action_export_docx(self):
self.ensure_one()
doc = Document()
doc.add_heading(self.name, level=0)
doc.add_paragraph(self.description or "")
buf = io.BytesIO()
doc.save(buf)
att = self.env["ir.attachment"].create({
"name": "%s.docx" % (self.name or "export"),
"type": "binary",
"raw": buf.getvalue(),
"res_model": self._name,
"res_id": self.id,
})
return {
"type": "ir.actions.act_url",
"url": "/web/content/%s?download=true" % att.id,
"target": "new",
}
说明:需保证 调用者对 ir.attachment 与业务记录 有 create/write 权限;否则 单独 ACL 或 系统用户代建 + 受限 token 下载。大文件可改 Controller + stream。定期清理 临时附件可用 计划任务 或 向导 TransientModel 承载。
向导模式:TransientModel 存 binary 字段,form 上「下载」按钮 attrs 仅当 file 非空。
46.12.3 截图占位

46.12.4 本节练习
- 简答:PDF 报表 与 Word 导出 各适合 一种 业务场景?
- 判断:python-docx 生成的文件 天然带 与 Odoo 相同 的 打印按钮版式。( )
- 实操:为 自定义模型 增加
action_export_docx,经附件或 Controller 下载 可打开 的.docx。
参考答案提示:1. 固定版式/归档→PDF;可编辑交付→Word。2. 错;版式独立设计。
本章综合练习
- 大作业:设计完整 「图书借阅」 交付:模型、权限、视图、报表、网站公告页、REST 查询接口、测试用例(可 分阶段 提交)。
- 答辩题:多公司 + 网站电商 同时启用时,库存与定价一致性 可能 哪里翻车?(不少于 3 点)
- 综合:Studio 导出模块 与 手写
sale扩展 并存时,Code Review 重点 5 条。 - 阅读:列出 第四十六章 与 第四十五章 的 分工(各一句:何时读哪章)。
- 判断:高级开发等于 可以任意
sudo写多公司数据。( )
参考答案提示:5. 错。
本章对应白皮书目录:第四十六章 高级开发。下一章 第四十七章 常见应用场景 将多公司、索引、库存追溯、context 与批量写入等收束为场景化自查。