Odoo高级服务端开发技巧

这是Odoo系列文章的第九篇,完整目录请见最好用的免费ERP系统Odoo 11开发指南

以下开发均假设读者已完成第八篇的代码,并且所有代码更新后均需自行更新方会在客户端看到变化。如未阅读该篇,请参考代码:Chapter 8

本文主要内容有

  • 修改运行指定动作用户
  • 以变更的上下文调用方法
  • 执行原生 SQL 语句
  • 为用户编写向导
  • 定义 onchange 方法
  • 在服务端调用 onchange 方法
  • 基于 SQL 视图定义模型

修改运行指定动作用户

在书写业务逻辑代码时,常会需要在不同权限上下文中操作动作,比如使用管理员权限绕过权限检查。下面我们来看一下普通用户如何使用 sudo()来修改公司电话号码。默认仅 Administration/Access Rights 用户组的用户可修改 res.company 记录。

注意:使用 sudo()时的操作是不可追踪的,所以使用 update_phone_number 后查看到的最后修改人仍是管理员,OCA的 base_suspend_security 可用于突破这一限制。

扩展知识

使用 sudo()不加参用户的上下文会变成 Odoo 超级管理员,该用户不受任何权限控制列表(acl)和记录集的权限规则限制。默认该用户有一个 company_id 字段设置为实例的主公司(ID 为1),这对于多公司的场景会存在问题:

  • 如若不小心,该环境中创建的新记录会被关联到超级管理员的公司
  • 不当操作会导致在该上下文中搜索到的记录有可能与当前数据库中的任一公司关联,进而导致向用户泄漏信息,甚至会在经意间因把不同公司的记录相互关联而导致数据库的损坏

小贴士:使用 sudo()时,反复确认调用 search()时不依赖标准记录集过滤结果,并确保在执行 create()时不使用当前用户字段 如 company_id 所计算的默认值。

使用 sudo()也会创建一个新的 Environment 实例,该环境初始带有一个空的记录集缓存 ,它与 self.env 的缓存是独立开来的。这可能会导致伪造数据查询,请避免在循环内创建新环境,并且越靠外层越好。

以变更的上下文调用方法

上下文是记录集环境的一部分,用于传递时区、用户界面语言、及动作中指定的上下文参数等信息。标准插件中的很多方法都使用上文来根据这些值来调整行为,有时需要变更记录集上下文来从方法调用获取预期结果或从可计算字段获取预期值。以下讨论在给定的 stock.location 中读取 product.product 的仓储级别。

以下会使用到 stock 和 product 两个 addon

以上 self.with_context()传递了一些参数,它返回一个新的带有键值的 self 版本(product.product 记录集),两个键分别为:

  • location:在product.product 方法计算 qty_available 字段的帮助文档部分有提及
  • active_test:使用该键并赋值 False,search()方法不会为搜索域自动添加(‘active’,’=’,True),它可以确保后面获取到所有产品(包括 disable 状态的)

扩展知识

也可为 self.with_context()传入字典,此时字典会覆盖原有环境成为新的上下文,上述对应代码可修改为

同样的使用 with_context()会创建一个新的 Environment 实例,该环境初始带有一个空的记录集缓存 ,它与 self.env 的缓存是独立开来的。这可能会导致伪造数据查询,请避免在循环内创建新环境,并且越靠外层越好。

执行原生 SQL 语句

大多数情况下,可以使用 search()方法执行操作,但有时这并不够,比如使用域的句法无法达到要求,或者一些查询需要多次调用 search()而导致效率低下。

以下我们将使用原生 SQL 查询来读取按国家分组的 res.partner 记录。

扩展知识

self.env.cr 是 包裹psycopg2游标(cursor)的装饰器,以下是常用的一些方法:

  • execute(query, params):参数为元组,替换查询语句中的%s 生成查询再执行(不要自己进行替换,这会使代码面临 SQL注入的风险)
  • fetchone():以元组的形式从数据库中返回一行
  • fetchall():以元组列表形式返回数据库的所有行
  • fetchalldict():以列名和值组成的键值对字典列表返回数据库的所有行

处理原生 SQL 查询时应注意:

  • 这会越过应用的权限限制,确保使用 search([‘id’, ‘in’, tuple(ids)])来过滤掉用户无权访问的记录
  • 任何修改都会绕过插件设置的约束,除 NOT NULL, UNIQUE 和 FOREIGN KEY 约束外,这些是在数据库级别强加的,重新计算触发的字段也是如此,因而可能会导致数据库的崩溃

为用户编写向导

我们在第五篇中曾介绍过 models.TransientModel 基类,该类与很多常规类相似,不同之处在于会在数据库中定期清理,所以才会被称为临时模型。一般用于创建向导或对话框,由用户在界面中填写然后再向数据库的持久记录操作。

下面我们向之前的模块添加记录借书的向导

然后在 xml 添加对应的菜单设置即可。TransientModel 的不同之处在于

  • 数据库中记录会定期移除,因而临时模型数据表不会越变越大
  • 无法对临时模型设置权限,任何人都可以创建记录,但只有创建者才能查看和使用该记录
  • 不就为临时模型指向常规模型的字段设置 One2many 类型,这样会在常规模型中添加列来与临时数据关联。这种情况可以使用 Many2many,但可以为在临时模型之间使用 Many2one 和 One2many 字段。

xml 文件中 button 类型设置为 object 表明在点击按钮时会调用name 属性所赋值的方法,操作中的target=’new’会在当前表单之上显示一个对话框。

扩展知识

以下可用于增强向导的功能:

使用上下文计算默认值

以上的向导要求用户填写姓名,web 客户端的特性可以让用户少打一些字,在操作执行时,上下文可以更新一些值给向导使用

Key Value
active_model 与操作相关的模型,通常是屏幕上显示的模型
active_id 表明单条记录处于活跃状态,提供出该记录的 ID
active_ids 在选择多条记录时,则会是一个 ID 列表(树状视图中选择多条),在表单视图中得到[active_id]
active_domain 向导所操作的额外的域

这些值可用于计算模型的默认值,甚至直接通过按钮给方法调用。假如在 library.member 模型的表单视图中有一个按钮启动向导,向导创建的上下文中会包含{‘active_model’:’library.member’, ‘active_id’:<member id>},这时可以使用如下方法定义 member_id 字段来计算默认值

向导和代码复用

在方法中我们可以将 for wizard 设为自循环,假设 len(self)为1,可以在方法最前面调用 self.ensure_one()

推荐使用这段代码,因为这样可以通过为向导创建记录在其它部分的代码中复用这个向导,放到一个单独的记录集中然后调用记录集中的 record_loans()。确实在此处代码有些琐碎并且无需通过所有的不同成员借用某些书的循环。但在 Odoo 实例中,有些操作更为复杂,通常有向导来做“对的事”会比较好。使用这类向导时,确保查看上下文中任何使用 active_model/active_id/active_ids 键的源代三,这时应传入自定义的上下文。

重定向用户

 

正在更新中…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.