1. 先把话说明白这次要解决的到底是个什么问题做了几期Odoo模块开发教程前面已经把模块骨架、数据模型、视图界面都跑通了很多跟到这一步的朋友都会遇到同一个坎模块能在开发者模式里加载菜单也能看到但一点进去就报“由于安全策略禁止访问”或者干脆提示对象不存在。其实不是代码写得不对而是你还没给模型配访问权限。这一篇就把“创建新插件模块”系列里最容易被忽略、却直接影响模块能不能用的部分讲透——设置访问权限。包括权限文件该怎么放、CSV里的几个字段到底代表什么、安全组security group和模型权限access rule是什么关系、改了权限之后怎么正确让模块生效以及升级之后遇到的不生效、报错、菜单消失这些典型问题怎么排查。这个内容不是什么高深理论属于典型的“不踩一次坑永远记不住”的实操环节。适合正在跟着教程写第一个Odoo模块、卡在权限这一步的人也适合那些模块已经写完但换了个用户登录就什么都点不了的朋友。读完你至少能自己搞定ir.model.access.csv以及围绕权限展开的一系列坑。2. 动手前先搞清Odoo的权限体系到底长什么样2.1 为什么单独一篇来聊权限很多从其他框架转过来的开发者习惯了后端接口里写权限注解、在中间件里做拦截到了Odoo里反而容易懵。因为Odoo的权限不是写在Python代码里的而是写在模块的配置文件和CSV文件里靠框架内置机制在运行时统一加载。这个设计有好有坏。好处是所有模块的权限规则都一目了然审计、迁移、交接都方便坏处是一旦文件结构不对、命名不规范或者模型ID写错权限就会静默失效而且日志里的报错往往并不直观。一个模块如果没有权限文件就算你视图写得再漂亮、模型字段定义得再完整实际用户登录进去依然什么都干不了。Odoo的安全策略是默认拒绝没有显式授权就等于没有权限这一点跟Linux文件系统的思路很像。2.2 Odoo权限体系的四个层级要理解访问权限得先知道Odoo里权限控制是分层的。从上往下分别是菜单可见性Menu控制用户能不能看到某个菜单项挂在安全组上模型访问权限Model Access控制用户能不能对某个模型执行读、写、创建、删除操作由ir.model.access.csv控制记录规则Record Rules在模型权限之上再做行级过滤比如业务员只能看自己的订单字段级权限Field Level控制某些字段对特定组可见或可编辑在视图XML里通过groups属性实现这一篇标题里的“访问权限”实际指的是第二层也就是模型访问权限。这一层是最基础也是最容易出问题的因为视图、菜单、按钮都依赖它。模型权限不对上层控制做得再好也没用。2.3 权限和身份验证的区别顺便说个新手常混淆的点登录认证Authentication和访问权限Authorization不是一回事。Odoo的登录是交给内置的res.users模型处理的只要账号密码对就能进系统。但进去之后能不能看某个菜单、能不能读某张表、能不能删某条记录这是访问权限管的。所以你会遇到一种现象用户能正常登录所有导航都正常但一打开你新加的菜单就报错。这就是典型的模型权限缺失。3. 核心实操权限文件到底怎么写3.1 先搭好文件结构我建议你按Odoo官方推荐的目录习惯来组织模块。一个最小的带权限模块大概是这样的my_module/ ├── __init__.py ├── __manifest__.py ├── models/ │ ├── __init__.py │ └── my_model.py ├── security/ │ └── ir.model.access.csv ├── views/ │ ├── my_model_views.xml │ └── menu_views.xml └── data/ └── demo_data.xml权限文件放在security目录下名字通常就叫ir.model.access.csv这是Odoo的约定。你也可以一个模型写一个权限文件但模块规模不大时放一个文件里反而更好维护。3.2manifest.py里必须声明权限文件这是最常见的遗漏点。你文件建了、CSV格式也写对了但升级模块后权限就是不生效十有八九是__manifest__.py的data列表里没有加上这个文件。{ name: My Module, version: 1.0, depends: [base], data: [ security/ir.model.access.csv, views/menu_views.xml, views/my_model_views.xml, ], installable: True, application: False, }注意顺序。安全权限文件要放在视图和菜单文件之前。因为加载视图时框架就要检查当前用户对相关模型有没有权限权限文件排后面容易出现加载阶段就报访问错误。3.3 CSV字段逐个拆解ir.model.access.csv本质上是个定义了多条访问规则的表格。每一行代表一条权限规则字段分别是字段含义id规则唯一标识模块内不重复即可name规则的文字描述建议写清楚是哪个模型、什么操作model_id:id要授权的模型格式是模块名.模型名group_id安全组留空表示对所有用户生效perm_read是否允许读取perm_write是否允许修改perm_create是否允许创建perm_unlink是否允许删除实际写出来长这样id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink access_my_model_user,my.model.user,model_my_model,base.group_user,1,1,1,1对着我的字段说明来解释这一行之后代码里引用这个权限规则时用ID规则叫my.model.user生效范围是my_module模块里的my.model模型所有登录用户base.group_user都能用增删改查四项全开。3.4 model_id:id这个字段最容易写错model_id:id看起来简单实际上踩坑最多。它是由两部分拼起来的你的模块名加上模型名中间用点号连接并且把模型名里的点换成下划线再加上model_前缀。举个例子模块叫my_module模型叫my.model那么权限文件里就该写model_id:id model_my_model如果你在Python代码里定义的模型是class MyModel(models.Model)对应_name my.model那模型ID一般就是model_my_model。注意这个值是框架自动生成的不需要你自己在XML里手动声明。网上有些人会在XML里写一个record标签去定义一个模型权限ID那是老版本的做法新版本里完全不需要。我第一次写这个字段时自作聪明写成了my_module.my_model结果升级后权限完全没加载排查了半天才发现是这里的问题。模型ID的格式必须是module.model_class_name的规则具体格式就是小写下划线别凭感觉改。还有个容易搞混的点如果你模型名里带了点比如x.y.z那id应该是model_x_y_z。命名虽然繁琐但只要记住一条规则——模型名里的点全部换成下划线前面加model_就不会错。3.5 group_id留空还是指定区别很大group_id这一列填什么决定了这条规则的适用人群。填了base.group_user代表所有内部用户填base.group_system代表只有管理员填你自定义的安全组ID那就只有那个组的人才有权限留空代表全局生效包括匿名用户这在实际业务里基本不会用。建议做法是一个模型最少配两条规则一条给员工用户组一条给管理员组。管理员经常需要比普通用户多一些权限比如删除权限可以只授给管理员。id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink access_my_model_user,my.model.user,model_my_model,base.group_user,1,1,1,0 access_my_model_manager,my.model.manager,model_my_model,base.group_system,1,1,1,1这样普通用户能查看、编辑、创建自己的数据但不能删除管理员则有全部权限。如果一开始图省事全给了base.group_user后面再想做权限收紧就得额外写迁移脚本不如第一次就配好。4. 实战过程把权限真正配起来并验证效果4.1 从创建模块到加载权限的完整流程为了方便你跟着操作我把从零开始的过程完整走一遍。先创建模块目录结构然后依次写模型文件、权限文件、视图文件最后升级模块验证。第一步创建模块目录mkdir -p my_module/models mkdir -p my_module/security mkdir -p my_module/views mkdir -p my_module/data第二步定义模型文件my_module/models/my_model.pyfrom odoo import models, fields class MyModel(models.Model): _name my.model _description My Model name fields.Char(stringName, requiredTrue) state fields.Selection( [(draft, Draft), (done, Done)], stringState, defaultdraft, ) description fields.Text(stringDescription)第三步写权限文件my_module/security/ir.model.access.csv这是本篇的核心id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink access_my_model_user,my.model.user,model_my_model,base.group_user,1,1,1,0 access_my_model_manager,my.model.manager,model_my_model,base.group_system,1,1,1,1第四步在__manifest__.py的data里声明权限文件位置放在视图文件前面。然后执行升级操作让模块重新加载。4.2 升级模块的正确姿势这里要特别提醒一个很多人都会犯的错修改了权限CSV文件之后不能只刷新浏览器页面必须让Odoo重新加载这个文件。开发环境里最直接的方法是重启服务并升级模块。如果你是用命令行启动的Odoo可以这样操作python odoo-bin -u my_module -d 你的数据库名-u参数会强制更新模块数据包括安全规则、视图、菜单等所有在data里声明的内容。如果你已经开着Odoo服务也没关系升级前先停掉服务再执行上面这条命令等它跑完再启动。有些朋友是在生产环境下不方便重启也可以从界面操作进入“开发者模式 - 应用 - 找到你的模块 - 点击升级”。这样同样会重新加载CSV文件。但我实际的经验是命令行升级的报错信息更原始排查问题更方便建议优先用命令行。升级成功后你可以在后台的“设置 - 用户与公司 - 群组”里找到你的规则。也可以通过菜单“开发者模式 - 技术 - 安全 - 访问权限”直接看到所有已加载的规则。4.3 验证权限是否真正的生效跑完升级别急着高兴一定要做验证。我建议分三步第一步验证规则是否加载。技术菜单里能看到刚才定义的access_my_model_user记录就算加载成功。看不到就要先确认CSV是不是被声明进了data列表。第二步验证规则是否绑定正确。点开那条规则确认模型那一栏显示的是my.model组那一栏显示的是员工。这里的任何不一致都可能导致权限不生效。第三步用非管理员账号实测。这个是很多人忽略的。管理员账号因为superuser的身份很多权限问题会被掩盖。只有用普通员工账号登录才能真正测出CSV配置得对不对。这第三步我建议养成本能习惯。不光测权限以后任何配置类的东西改完都拿受限账号过一遍。否则上线之后用户来找你说为什么我打不开这个菜单你再花时间排查效率差距就大了。5. 权限相关的进阶配置与设计思路5.1 安全组更精细的权限管理手段如果你只有ir.model.access.csv权限控制就算完成了吗对简单模块来说确实够了。但稍微像样一点的功能一般都会引入安全组。安全组是个什么角色呢可以把它理解成一个标签一组权限规则可以挂在这个标签下多个用户也可以被划进同一个标签里。Odoo的权限规则里可以指定组然后把这个组分配给一批用户这样权限的统一管理和调整都会方便很多。假设你模块里有一个“高级操作”只有特定角色才能执行。你可以这样定义安全组odoo data record idgroup_my_model_advanced modelres.groups field namenameMy Model / Advanced User/field field namecategory_id refbase.module_category_hidden/ /record /data /odoo然后权限文件里给这个组分配权限access_my_model_advanced,my.model.advanced,model_my_model,my_module.group_my_model_advanced,1,1,1,1这组规则放在一个叫security.xml的安全组定义文件里。注意这个文件也要在manifest里注册。它的意义在于某些只能由指定角色操作的模型你可以完全不给普通用户权限。这里有个实际用到的技巧安全组还可以控制操作的可见性。比如你视图里的某个按钮可以用groups属性只让特定用户看到。这样实现的不只是权限还是整个界面的适配比在代码里写if判断要优雅得多。5.2 菜单可见性的绑定逻辑很多人问为什么CSV权限都好了但特定用户还是看不到菜单要记住菜单可见性不是自动跟随模型权限的。菜单需要在视图XML里通过groups属性跟安全组绑定。menuitem idmenu_my_model_root nameMy Module sequence10/ menuitem idmenu_my_model_list nameMy Model List parentmenu_my_model_root actionaction_my_model_list groupsmy_module.group_my_model_advanced/如果用户的组不在groups列名里菜单也不会显示。反过来CSV里没有权限的模型就算菜单显示出来了点了也会报错。这两个维度必须同时满足功能才能正常工作。5.3 记录规则做行级控制模型权限解决的是“能不能操作这类数据”记录规则解决的是“能操作哪些数据”。如果你想实现业务员只能看自己创建的记录那就得用记录规则。record idrule_my_model_personal modelir.rule field namenameMy Model: Personal Records/field field namemodel_id refmodel_my_model/ field namedomain_force[(create_uid, , user.id)]/field field namegroups eval[(4, ref(base.group_user))]/ /record这一段的意思是普通员工只能看到create_uid等于自己的记录也就是自己创建的数据。管理员不受这个规则限制因为Odoo里的管理员会跳过记录规则检查。域名表达式里使用的user.id是当前登录用户这属于Odoo源码层面的写法直接照抄就行不要试图在模型字段里加一个“用户ID”那种冗余的字段来实现同样的效果维护成本差异会很大。5.4 字段级权限再往细了分可以精确到字段。比如一个模型里的cost_price字段只有采购组能看其他用户连字段都看不见。这个在视图XML里加个groups就行field namecost_price groupsmy_module.group_my_model_advanced/这个逻辑跟菜单控制是一样的没有那个组的用户连字段都不会出现在视图里。6. 常见问题与排查技巧实录6.1 升级后菜单依然不显示先确认是不是菜单本身绑定了安全组而当前用户不在组里。这一步你可以在开发者模式下用管理员账号查看菜单配置或者直接检查XML里的groups属性。还有一个可能是模块升级没用-u参数视图文件没有更新不过这种情况一般会伴随一堆其他文件也不生效的迹象。我的排查顺序通常是先删除浏览器的开发者模式缓存配置注意不是清浏览器缓存再检查菜单XML的groups最后看CSV规则。6.2 后台一直报访问错误报错信息类似于“由于安全策略禁止访问/创建/写入/删除任何数据”说明该模型没有任何权限规则或者规则没加载成功。排查时你先去技术菜单里确认规则是否存在。如果存在就检查model_id:id这一列。这是最值得怀疑的地方不少人对不上模块名和模型名的命名。6.3 duplicate key报错如果你升级时遇到ERROR duplicate key value violates unique constraint这说明你CSV或XML里的ID和已有记录重复了。常见原因是你复制粘贴了别人的权限文件里面带的ID跟你自己模块的ID撞了。解决办法是换一个全新的、有辨识度的ID不要图省事直接沿用教程里的ID。6.4 权限配置没问题但界面还能添加记录这个现象看起来很奇怪但原因往往很简单你是在管理员身份的界面上测试的。管理员用户本身就是最高权限再叠加开发者模式的隐身规则很多限制都绕过了。想验证权限务必切到一个受限的普通用户账号上去操作。6.5 清缓存技巧Odoo里有几个层级的缓存模块的CSV和XML文件加载是一次性的改完这些文件后如果不升级光清界面缓存没用。有一个快速的开发技巧命令行带-i参数把模块重新安装一遍比-u更彻底。虽然时间长一点但排查权限问题时值得。python odoo-bin -i my_module -d 你的数据库名6.6 规则对所有人生效的风险如果你在CSV里把group_id留空就相当于公开了这个模型。所有能访问到这个模型的外部用户都能操作。实际开发中我只在极少数情况下会这么用比如某个字典表需要所有人只读。它可能引发越权风险建议即便是只读也指定给具体的一个组。6.7 别忘了权限文件的行尾和编码CSV文件看起来简单Windows下编辑尤其容易翻车。如果保存成了带BOM的UTF-8或者行尾带的换行符不对Odoo在读取时可能会报错或者在字段值里出现看不见的字符。建议统一用UTF-8无BOM格式文件保存前留意一下编辑器右下角的编码信息。7. 实操心得权限这事要养成一套固定动作每个模块开发到最后我都会强制自己走一遍固定流程写模型、建安全组、配CSV权限、声明到manifest、加菜单groups、升级、换受限账号测试。这套流程走完权限环节基本不会再出问题。最容易被低估的是换受限账号这一步。很多人开发时全程用管理员所有功能都正常以为万事大吉结果真到一个普通用户手里界面空荡荡的规则它一概不认识。这件事我踩过不止一次现在都写在交付清单上了。另外提一个建议项目里完全可以建一个专门的测试员工账号权限上就挂在base.group_user下面不需要特殊权限。每次模块升级后切这个账号过一遍。配合开发者模式的功能既能看到模块加载情况也能模拟真实的用户环境。访问权限在Odoo里属于那种“不会做也能跑、做错了看似没问题、上线就出丑”的环节。多花十分钟配清楚比上线后连夜打补丁强太多了。