为何Odoo无法读取为模型添加的额外字段?
核心原因
Odoo启动时优先加载Python代码定义的字段,同时仅会读取ir.model.fields中state为base(原生字段)或manual(模块安装字段)的记录,custom状态的字段会被默认忽略。你在Technical->Models里看到字段,只是数据库记录存在,但Odoo运行时的模型对象并未加载这些字段。
可行解决方案
1. 通过模块Data文件定义字段(推荐)
这是最合规的方式,完全无需“绕过”限制:直接在模块的XML数据文件里定义字段,可自定义字段名(无需x_前缀),并将state设为manual,Odoo会自动加载这些字段到模型中。
示例XML代码:
<odoo> <record id="field_res_partner_my_custom_field" model="ir.model.fields"> <field name="name">my_custom_field</field> <!-- 自定义字段名,无x_前缀 --> <field name="model">res.partner</field> <!-- 目标模型 --> <field name="field_description">我的自定义字段</field> <field name="ttype">char</field> <!-- 字段类型,如char、integer等 --> <field name="state">manual</field> <!-- 关键:设为manual,Odoo会加载 --> <field name="required" eval="False"/> <field name="readonly" eval="False"/> </record> </odoo>
将该XML文件添加到模块__manifest__.py的data列表中,安装/升级模块后,字段会被正常加载,视图中直接引用即可。
2. 动态注入字段到模型
如果必须通过数据库直接创建字段(而非Data文件),可以在模块加载时,从ir.model.fields读取目标字段,动态注入到模型的_fields字典中。
示例Python代码(可放在模块的models/__init__.py中):
from odoo import models, fields from odoo.modules.registry import Registry def inject_custom_fields(): registry = Registry.get('base') with registry.cursor() as cr: # 查询目标模型的非custom状态字段(根据实际情况调整条件) cr.execute(""" SELECT name, ttype, field_description, required, readonly, default FROM ir_model_fields WHERE model = 'res.partner' AND state != 'custom' """) field_records = cr.fetchall() Model = registry['res.partner'] for name, ttype, desc, required, readonly, default_val in field_records: if name not in Model._fields: # 根据字段类型创建对应的字段对象 field_class = getattr(fields, ttype.capitalize()) field = field_class( string=desc, required=required, readonly=readonly, default=default_val ) # 将字段添加到模型的_fields字典 Model._fields[name] = field # 清除字段缓存,确保Odoo能识别新字段 Model._fields_get_cache.clear() # 在模块加载时执行注入逻辑 inject_custom_fields()
注意:需确保在Odoo模型加载完成后执行,且要适配不同模型的需求。
3. 仅解决视图显示问题(临时方案)
如果只是需要在视图中展示字段,不涉及Python业务逻辑调用,可以重写模型的fields_view_get方法,动态将字段添加到视图的字段定义中:
class ResPartner(models.Model): _inherit = 'res.partner' def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): res = super().fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) # 查询需要显示的自定义字段 custom_fields = self.env['ir.model.fields'].search([ ('model', '=', self._name), ('state', '=', 'custom') # 根据你的字段状态调整 ]) for field in custom_fields: if field.name not in res['fields']: res['fields'][field.name] = { 'string': field.field_description, 'type': field.ttype, 'required': field.required, 'readonly': field.readonly, 'default': field.default } return res
此方案仅能让视图渲染字段,无法在Python代码中通过record.name访问该字段,仅适合临时展示需求。
4. 修改Odoo核心加载逻辑(不推荐)
如果一定要保留custom状态的字段并让Odoo加载,可以修改Odoo核心代码odoo/models.py中的_load_fields方法,移除对state != 'custom'的判断。但这种方式会影响全局,且Odoo升级后会被覆盖,不建议使用。
内容的提问来源于stack exchange,提问作者Ehab

